Modules (filetype &FFA) lie at the heart of RISC OS. A module extends the operating system, or replaces part of it.
For example, while we are used to periodically updating our ROM image on current hardware, on older machines the ROM was a physical ROM so it was common to load a later version of the SharedCLibrary which would replace the one supplied with RISC OS. Likewise, the “nested Wimp” would also be loaded from disc and would entirely replace the older and less capable Wimp supplied in ROM.
Modules can also be used to extend system functionality. For instance, Fat32FS is an extended and more capable version of DOSFS. SparkFS provides a filing system interface to archive files, where they can be opened as if regular directories. A USB-MIDI module exists to permit simpler communication with MIDI keyboards using a USB interface. The options are nearly endless.
Modules written for earlier versions of RISC OS will not work on RISC OS 5. This is due to incompatibilities in the ARM programmer’s model, namely RISC OS 5 uses a 32 bit Program Counter with a separate Status Register, while older versions use a combined 26 bit PC plus PSR. The two are fundamentally incompatible.
It is, however, possible to create a 26/32 agnostic module if you set the flag word to indicate that your module is 32 bit safe, and then you include the expected function exit (preserves flags or does not) according to whether or not the system is using a combined or separate PSR.
Information on writing modules for the older 26 bit versions of RISC OS may be found here.
Modules consist of code and data in a single file that is prefixed by a header. The header provides offsets to various entry points and data.
A 26 bit module must have at least the first seven fields.
A 32 bit module must have all fields present, as the last one is the offset to a flags word containing the 32 bit flag (Note: Other flags may be used in future at present only the bit for 32 bit status is used).
Fields that are not applicable should be zero.
Most fields are offsets, some of these must be aligned, and all offsets must point within the length of the module to be valid.
Offset | Hex | Purpose |
---|---|---|
+0 | &00 | Offset to Start code (or Branch instruction) |
+4 | &04 | Offset to Initialisation code (or compression offset) |
+8 | &08 | Offset to Finalisation code (and b31 flag) |
+12 | &0C | Offset to Service Call handler |
+16 | &10 | Unaligned offset to Title string |
+20 | &14 | Unaligned offset to Help string |
+24 | &18 | Unaligned offset to Help and Command Keyword table |
+28 | &1C | SWI Chunk Base Number |
+32 | &20 | Offset to SWI Handler code |
+36 | &24 | Unaligned offset to SWI Name Table |
+40 | &28 | Offset to SWI Decoder code |
+44 | &2C | Offset to Messages file name |
+48 | &30 | Offset to Module feature flags |
Legacy versions of RISC OS only required the first seven fields (those prior to the SWI chunk). The rest were optional, depending on necessity. RISC OS 5 requires all fields to be present.
Zero, aligned offset to code, or ARM machine code instruction if any of bits 31-25 or 1-0 are set
Calling OS_Module with either Run or Enter will enter a module through this offset, as will the *RMRun
command.
On entry:
=> | R0 | Pointer to command string (excluding module name) |
R12 | Private word for the currently preferred instantiation | |
R14 | Not a return address – the module must exit with OS_Exit |
The processor is in User mode with interrupts enabled. This entry point is not re-entrant.
This call does not return.
On exit:
As this was started as the current application, it must exit by calling OS_Exit, OS_ExitAndDie, or implicitly by starting another application.
If treated as an ARM instruction, it can only sensibly be a B
branch or possibly BL
(in order to point R14 near the start of the module). It is not sensible to continue execution into the Initialisation offset, so some kind of branch must be achieved, and the other potential construction of LDR PC,[PC,#offset]
is definitely not recommended. A simple offset is best.
BASIC is an example of a module with a Start offset, as are some common Wimp applications built as modules.
Zero, or aligned offset to code (if b31 clear); aligned length of compressed module (if b31 set), and flags
When a module is initially loaded, reloaded, or after a tidy (in other words, following the Run, Enter, or ReInit calls) the Initialisation entry is called. This is expected to set up the module so that all of the other entry points are able to operate. This entry point is the first one to be called.
Typically you would use this entry point to claim some workspace, set up data tables, hook into vectors and events, register as a filing system, etc.
Note that at this point, the module is not yet on the list of active modules so you cannot call your own SWIs – you must jump directly to your own code if necessary.
You must not generate errors so use the X form of SWIs. If there is a problem that prevents the module from initialising, R0
should point to a standard error block and the module should return with the V flag set. The module will then be removed.
Registers R7
-R11
and R13
must be preserved.
On entry:
=> | R0 | Pointer to the environment string (any initialisation parameters given when the module was loaded) |
R11 | I/O base or instantiation number (see below) | |
R12 | Private word for this instantiation (see below) | |
R13 | SVC stack | |
R14 | return address |
The processor is in SVC mode with interrupts enabled. This entry point is not re-entrant.
On exit:
<= | R0-R6 | can be corrupted |
R12,14 | can be corrupted | |
V set | if R0 is an error block |
Should preserve processor mode and interrupt state. Flags can be corrupted – RISC OS only examines the V flag to see if an error occurred.
If this entry point is zero, it means that the module does not require initialisation.
R11, the I/O base or instantiation value, has the following meanings: If it is zero, the module was loaded from ROM, from a filing system, or was already present in memory. If it is > &03000000 then the module was loaded from an expansion card and R11
is the synchronous base address of the card (RiscPC and Iyonix class hardware). Any other value means that the module is being reincarnated and there are R11
other instantiations of the module. Modules that cannot usefully be instantiated must check R11 and error if inappropriate.
If the private word (R12
) contains a non-zero value, then the module is being reinitialised and the contents are as your module left them.
Typically, modules claim workspace and write the address to R12 with STR Rxx,[R12]
, so that when RISC OS later calls the module via the other entry points and provides the private word in R12, you can extract your workspace address from this by using LDR R12, [R12]
. However, the Kernel places no interpretation on the contents of the private word except after finalisation (see below).
The value may contain flags indicating the meaning of the initialisation word. Only 2 flags are currently defined. Any flags which are not understood by RISC OS will cause the module to be rejected as invalid, due to the offset being outside the module.
Bit 31 | Module is compressed; initialisation word contains the length of the module |
Bit 30 | Module architecture type is present in the module feature flags, and contains a non-0 (not ARM) value |
When the module architecture flag (bit 30) is set, versions of RISC OS which do not understand the flag will not initialise the module. Versions of RISC OS which do understand the flag will interpret the module architecture type and reject modules for architectures they do not understand. Currently only RISC OS Pyromaniac 7.22 and later support this flag.
Zero, or aligned offset to code; b31 set if contents of private word must not be freed
This is the opposite of the initialisation code, it is called prior to a module being removed from memory. The module should remove itself from vectors, events, etc and release claimed workspace. It is not necessary to free the workspace pointed to by the private word – the Kernel will automatically attempt to free that after finalisation if it is non-zero, unless b31 of the Finalisation offset is set.
The finalisation entry is called by the ReInit, Delete, and Clear calls as well as the associated commands *RMReInit
, *RMKill
, and *RMClear
; and also when a newer version of a module replaces an older version, the older one would be finalised prior to the new one being loaded.
If the module does not want to quit, it should set R0 to point to an error block, and return with the V flag set. The finalisation will be aborted and the module will still be active. This should be a last resort, as it can inconvenience the user.
Registers R7
-R11
and R13
must be preserved.
On entry:
=> | R10 | Fatality indication – is ‘1’ as finalisation is always fatal. |
R11 | Instantiation number | |
R12 | Private word for this instantiation | |
R13 | SVC stack | |
R14 | return address |
The processor is in SVC mode with interrupts enabled. This entry point is not re-entrant.
On exit:
<= | R0-R6 | can be corrupted |
R12,R14 | can be corrupted | |
V set | if unable to quit, R0 is error block |
Should preserve processor mode and interrupt state. Flags can be corrupted – RISC OS only examines the V flag to see if an error occurred.
If this entry point is zero, it means that the module does not require any special finalisation and the Kernel can terminate the module.
The fatality indication in R10
had meaning a long time ago when it was possible to use *RMTidy
to tidy up gaps in the module area. It would do this by temporarily ‘soft’ finalising all modules, relocating everything, then ‘soft’ reinitialising the modules afterwards. However *RMTidy
has not been feasible for a very long time, so it should be assumed that all finalisation attempts are terminal.
R11
provides the dynamic instantiation number, which may not be the same as the value given in R11
on initialisation.
If the Finalisation offset’s b31 is clear and the private word (R12
) contains a non-zero value after finalisation, RISC OS will consider the value to be the module’s claimed workspace and will attempt to free it. Thus, it is good form to write zero to the module’s private word (the original R12 on entry) if it is likely to be confused for allocated memory.
Unaligned pointer; must not be zero
The title string is an offset to a null terminated module title. The title should be short, descriptive and unique, and should contain plain alphanumeric characters (A-Za-z0-9!_). It must not contain spaces or control characters.
It is recommended that it is formatted in a manner similar to the built-in modules; for example: “SpriteUtils”, “Desktop”, “Filer”, “FileSwitch”, and “TaskManager”. The matching of module names is not case sensitive.
The module name is used with OS_Module calls that take module names (Enter, ReInit, or Delete) as well as the associated commands (*RMReInit
and *RMKill
), as well as being listed by *Modules
).
It is also used as a de facto SWI prefix if the module has a SWI Base number, but no Name Table or Decoder. In this case it must not start with an X.
Unaligned pointer; must not be zero
The help string is an offset to a formatted null terminated string that is printed out by *Help
prior to any information from the module such as the list of commands available.
The help string also provides the module version number that is used by the *RMEnsure
command.
The string must not contain any control characters other than Tab (chr 9 aligns to the next eighth column) or character 31 (which behaves like a hard space). The help string may contain spaces, but must not contain digits after the first 16 characters.
In order for *RMEnsure
to work correctly you must use the following format (one or two tabs may be required to pad to column 16 in *Help Modules
depending on Title length):
module_name<1 or 2 Tab chrs>vv.vv (DD Mmm YYYY)
The module name, which is generally the same as the title string (but can contain spaces), is followed by one or two tab characters to pad to column 16. This is then followed by the version number which is a 4.4 decimal number, eg 1.23
or 1234.5678
. Modules typically only use two decimal digits, but up to four are supported. It is possible to follow the version number with letters such as “1.23a”, but these are ignored by *RMEnsure
. There follows a space and the version date in parentheses: two digit day of the month (with leading zero if necessary), a three character month name (in English), and a four digit year, for example “(12 Apr 2015)
”.
Sometimes the help string is followed by a short copyright indication, extended version number, or other content, eg " © Author yyyy" or " [beta test version]"
Zero, or unaligned pointer
The command table contains *Commands
, help available through *Help
, and configuration options accessed by *Configure
and *Status
. It comprises one or more structures of the following form, terminated by a single zero byte (the first structure need not be aligned, but the rest implicitly are):
bytes | null terminated string, padded to word boundary |
aligned offset | offset from module start to Code, or 0 |
byte | the minimum number of parameters, or 0 |
byte | one bit per parameter if it is to be GSTransed |
byte | the maximum number of parameters, or 255 |
byte | b7 Filing System Command (low priority) |
b6 Configuration Option | |
b5 Help offset is code pointer | |
b4 Help and Syntax strings are tokens for the Messages file (see below) | |
unaligned offset | offset from module start to Syntax string |
offset | unaligned offset from module start to Help string, |
or aligned offset to Help code, if b5 is set |
Entries with a zero Code offset provide help only (but that help can be dynamically generated, see later). Otherwise, the Code is entered as follows:
On entry:
=> | R0 | ptr to command parameters or terminator, or if flags b6 was set: |
0 = *Configure YourOption with no parameters |
||
1 = *Status YourOption – display current configuration |
||
R1 | number of parameters | |
R12 | ptr to private word | |
R13 | SVC stack | |
R14 | return address |
On exit:
<= | R0 | error ptr if V set, or for configuration options: |
0 = “Bad *Configure option” |
||
1 = “Numeric parameter required” | ||
2 = “Parameter too large” | ||
3 = “Too many parameters” | ||
R1-R6 | can be corrupted |
If flags b5 is set, the Help Code is entered on *Help
like this:
On entry:
=> | R0 | ptr to buffer for your convenience |
R1 | length of that buffer | |
R12 | private word | |
R13 | SVC stack | |
R14 | return address |
Your code can display help text itself, or return a pointer to the provided buffer or your own:
On exit:
<= | R0 | passed to OS_PrettyPrint if not zero |
R1-R6 | can be corrupted |
NOTE As this table is terminated by a single zero byte, be careful to align before an adjacent code label. Failing to do so is a very common error.
Zero, or a multiple of 64 less than 1<<24; b17 completely ignored, but should be zero
This is the base number of the SWI chunk for the module, if the module provides SWIs. This is used by RISC OS to enable the module to be called when a SWI within its range has been issued.
If you request a SWI number, you will be allocated a “chunk” of 64 SWIs, starting at a Base Number such as &4E440
. This is the number that goes into the SWI chunk base number field and together with the SWI Handler offset indicates that the module provides SWIs. The SWI Name Table (below) provides a simple way of translating between SWI names and numbers.
Because many modules provide SWIs and must all be unique, you must never release a module without a properly allocated SWI chunk.
Zero, or aligned offset to code
Called when one of your 64 SWIs has been issued. On entry:
=> | R0-R9 | the caller’s registers – your parameters |
R11 | sub-chunk number (SWI index) 0-63, no other values possible | |
R12 | private word | |
R13 | SVC stack | |
R14 | return address |
On exit:
<= | R0-R9 | as appropriate; R0 can be error block if V set |
R10-R12 | can be corrupted | |
CPSR | flags returned to caller; if V set then R0 is error block |
You should return error &1E6,“No such SWI” or suitably worded equivalent for any unimplemented SWIs. A typical Handler is:
Handler LDR R12, [R12] CMP R11, #lastindex+1 ADDCC PC, PC, R11, LSL#2 B BadSWI B SWIzero B SWIone ... BadSWI STMFD R13!, {R1-R7,R14} ADR R0, BadSWIerr MOV R1, #0 MOV R2, #0 ADR R4, Title SWI XMessageTrans_ErrorLookup LDMFD R13!, {R1-R7,PC} BadSWIerr & &1E6 = "BadSWI",0
Zero, or unaligned offset to name table
This field is ignored unless the SWI Base Number and SWI Handler offsets are valid. SWI names comprise a prefix; an underscore; and then a name or numeric index such as “OS_WriteC” or “FooBar_63”. The prefix is often the same as the module Title (it does not have to be), but it cannot begin with an “X”. If your module Title is long, please take pity on your programmers and consider adopting a shorter SWI prefix. Module Titles and full SWI names must be unique, but SWI prefixes do not necessarily have to be – various modules share “Sound_” for example. This is difficult to coordinate and is discouraged.
The SWI Name Table is a list of consecutive null-terminated strings, starting with the SWI prefix (the part before the underscore) followed by the names (without prefix), and ending with a single null byte. If the Name Table field is zero, RISC OS will call the SWI Decoder code (below) if present, and otherwise default to a numeric index attached to the module Title. If there are insufficient names in the table for the requested SWI number, the Kernel will append a numeric index to your SWI prefix.
An example SWI Name Table:
switable = "TTXHelper",0 = "AutoDetect",0 = "ScanFlash",0 = "ConvertToISO",0 = "ConvertToANSI",0 = "ExtractLine",0 = 0
Note the final empty string to end the table. Also remember to align before using any code labels.
Unnamed SWIs at the end of the range can be omitted (and would be given numeric names automatically), but you cannot have empty strings within the table, so if the SWI corresponding to “ConvertToISO” in the above example were not defined the name should instead be “3” as that is its zero-based index.
The error-returning form of a SWI is indicated by b17 of the SWI number, and by prefixing the full name by “X”. However, this is never included in the Name Table, it is automatic.
Zero, or aligned offset to code
If the module has a valid SWI Base Number and SWI Handler offset but no SWI Name Table, then code can be provided to assist OS_SWINumberToString and OS_SWINumberFromString. R0 selects between the two functions, being 0-63 for the former, and negative for the latter. Other values will not occur.
NumberToString
On entry:
=> | R0 | 0-63 sub-chunk SWI number for this module (Base Number matched) |
R1 | ptr to buffer for the name, combined with R2 | |
R2 | offset into the buffer to start (“X” may already have been written) | |
R3 | length of the buffer (i.e. offset from R1 to stop at) | |
R12 | private word | |
R13 | SVC stack | |
R14 | return address |
On exit:
<= | R2 | updated to point past the characters inserted into buffer, Kernel will terminate for you |
R4-R6 | can be corrupted | |
R9,R10 | can be corrupted | |
R12 | can be corrupted | |
V set | if buffer overflow – no error block required |
This is called by OS_SWINumberToString to provide a name for a given SWI number. Your code will only be called if the requested SWI number is one of the 64 starting from your SWI Base number, which cannot be < &200.
If the full SWI name fits into the buffer between R2 and R3, then write the unterminated name and return the updated R2. If the buffer is too small, return with V set but no error block in R0. There is no way to refuse to name a SWI, so build unused SWI names in the usual fashion – your SWI prefix; underscore; decimal index (R0 on entry).
NumberFromString
On entry:
=> | R0 | negative (-1) to distinguish from the above case |
R1 | ptr to ctrl-terminated string to recognise, no initial X | |
R12 | private word | |
R13 | SVC stack | |
R14 | return address |
On exit:
<= | R0 | 0-63 sub-chunk number if SWI name recognised, else negative |
R1 | can be corrupted if recognised | |
R2-R6 | can be corrupted | |
R9,R12 | can be corrupted |
Unlike NumberToString, your Decoder will be called for all unrecognised SWIs. However, the value you return in R0 is ORRed with the module’s SWI Base Number before being returned. Although this is intended to allow the module to only recognise its 64 SWI names, it can be seen that a module could in extremis return names for half of all SWI numbers.
Zero, or aligned offset to null-terminated filename
In order to facilitate internationalising modules, it is possible to provide a null-terminated filename for the Messages file for RISC OS to look up tokens from the Help and Command Table.
If the Messages filename is present and bit4 of a flag byte in the above Command Table is set, the help and syntax text are tokens to be looked up.
This Messages file only relates to the Help and Command table. All other internationalisation within the module needs to be dealt with by the module author.
Zero (26bit only) or aligned offset to flags word
The flags word comprises:
Bit 0 | Module is 32 bit compatible (26 bit only if bit is clear) |
Bit 1 | Module is initialised before other modules (early initialisation) |
Bits 2-3 | Reserved (should be zero) |
Bits 4-7 | Module architecture type |
Bits 8-31 | Reserved (should be zero) |
If no flags word is present the module feature flags should be assumed to have a value of 0.
Modules not flagged as 32 bit compatible will not be loaded by a RISC OS 5.
If the 32 bit compatible flag is clear, the module is assumed to be 26 bit compatible only. Note that the 32 bit compatibility flag is only a marker, the actual module code is not guaranteed to be 32 bit safe by the presence of this flag. Incorrectly coded modules (with 26 bit only elements) will be loaded when this flag is present. It is the responsibility of the module author to ensure the code is compatible.
Early initialised modules are supported by RISC OS Select 3 and later. Modules with this flag set will be loaded before all other modules. This flag is used to ensure that extension modules provided by the PoduleManager (or other extensions) can be initialised before the main modules, and can correctly mask built in modules. In prior versions of RISC OS (and in RISC OS 5), the PoduleManager is treated specially.
The module architecture type is used to indicate the type of code which is present within the module. RISC OS will reject any architecture type which is does not understand. This flag must be used in conjunction with bit 30 of the module initialisation word in the header.
Currently defined module architecture types are:
0 | ARM 32 bit (A32) |
1 | ARM 64 bit (A64) |
2 | x86 64 bit (x86-64) |
3-14 | Reserved |
15 | Python (RISC OS Pyromaniac) |
For more complete information on modules, please refer to the chapter on Modules in book 1 of the Programmer’s Reference Manuals (it’s a very big chapter!) but please note that the information provided there is nearly a quarter century and several major RISC OS versions out of date. The chapter is useful to provide a detailed overview of how modules work and interact, but is superseded by this document.