Showing changes from revision #9 to #10:
Added | Removed | Changed
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 present, as the last one is the offset to the 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 required applicable should be zero zero. to ensure they are seen as invalid.
Most fields are offsets, some of these must be aligned, and the all offset offsets must point within the module length itself of or the operating module system to will be reject valid. the module.
Offset | Hex | Purpose |
---|---|---|
+0 | &00 | Offset to Start code |
+4 | &04 | Offset to Initialisation code |
+8 | &08 | Offset to Finalisation code |
+12 | &0C | Offset to Service Call handler |
+16 | &10 | Offset to Title string |
+20 | &14 | Offset to Help string |
+24 | &18 | Offset to Help and Command Keyword table |
+28 | &1C | Offset to SWI |
+32 | &20 | Offset to SWI |
+36 | &24 | Offset to SWI |
+40 | &28 | Offset to SWI |
+44 | &2C | Offset to Messages file name |
+48 | &30 | Offset to Module 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:
Pointer to command string (including 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 should exit by calling OS_Exit.
Note As that this the was field started need not actually be an offset. If it cannot be interpreted as an offset, it will instead be executed. This permits the first current instruction application, to be a branch which may be useful to directly run the application code during development, as if it was must a exit normal by application calling and not a module.OS_Exit.
This If entry treated is as important an for having modules that can provide Wimp tasks.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.
When a module is initially loaded, reloaded, or after a tidy (in other words, following the Zero, or aligned offset to code; b31 set if compressedRun, 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 and no others will be called beforehand.
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 When that a at this point, the module is not initially yet loaded, on reloaded, the or list after of active modules so you cannot call your own SWIs. If you need to do such a thing, tidy jump (in directly other to words, your following own the code as is appropriate.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.
You 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.not generate errors. 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.
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 | ||
R14 | return address |
The processor is in SVC mode with interrupts enabled. This entry point is not re-entrant.
On exit:
Should preserve processor mode and interrupt state. Flags can be corrupted – RISC OS only examines the V flag to see if an error occurred.
Registers R0
-R6
, R12
and R14
can also be corrupted.
Return using MOV PC, R14
with, optionally, V set if flagging an error.
<= | R0-R6 | can be corrupted |
R12,14 | can be corrupted | |
V set | if R0 is an error block |
If Should this preserve entry processor point mode is and zero, interrupt it state. means Flags that can the be module corrupted does – not require any special initialisation.RISC OS only examines the V flag to see if an error occurred.
If this entry point is zero, it means that the I/O module base does or not instantiation require value initialisation. (R11
) 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 [this only applies to RiscPC and Iyonix class hardware]. Finally, any other value means that the module is being reincarnated and there are R11
other instantiations of the module.
R11, the I/O base or instantiation value, has the following meanings: If it is zero, the private module word was ( loaded fromROMR12
) , contains from a non-zero filing value, system, or was already present in memory. If it is > &03000000 then the module is was being loaded from an expansion card andreR11
initialised. 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 R12 R11 will other point instantiations to of your the claimed module. workspace.
It is normal to claim workspace and then write the address of this workspace 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]
.
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).
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 to release claimed workspace.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 | ||
R14 | return address |
The processor is in SVC mode with interrupts enabled. This entry point is not re-entrant.
On exit:
Should preserve processor mode and interrupt state. Flags can be corrupted – RISC OS only examines the V flag to see if an error occurred.
Registers R0
-R6
, R12
and R14
can also be corrupted.
Return using MOV PC, R14
with, optionally, V set if flagging an error.
<= | R0-R6 | can be corrupted |
R12,R14 | can be corrupted | |
V set | if unable to quit, R0 is error block |
If Should this preserve entry processor point mode is zero, it means that the module does not require any special finalisation and that interrupt state. Flags can be corrupted –RISC OS can only just examines go ahead and terminate the module. 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 once after the finalisation, finalisation has occurred,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) once if the it workspace is has likely been to released. be confused for allocated memory.
The title string is an offset to a null terminated module title. The title should be short and descriptive and unique and contain plain alphanumeric characters. It should not contain spaces or control characters.Unaligned pointer; must not be zero
It The title string is recommended an that offset it is formatted in a manner similar to the a built-in null modules; terminated for module example: title. “SpriteUtils”, The “Desktop”, title “Filer”, should “FileSwitch”, be short, descriptive and “TaskManager”. unique, and should contain plain alphanumeric characters. It must not contain spaces or control characters.
The matching of module names is not case sensitive.
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.
The help string is an offset to a null terminated string that is printed out by Unaligned pointer; must not be zero*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 with the *RMEnsure
command.
The help string must is not an contain offset any control characters other than Tab (which will align to the next eighth column) or character 31 (which behaves like a hard formatted space). null The terminated help string may that contain is spaces. 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.
In The order string must not contain any control characters other than Tab (chr 9 aligns to make the output next of eighth column) or character 31 (which behaves like a hard space). The help string may contain spaces.*Help Modules
(which lists all available modules) look tidy, it is recommended that you use the following standard format:
In order for module_name Tab[Tab] v.vv (DD Mmm YYYY)
*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):
The module name, which is generally the same as the title string (but can contain a space between words), is followed by one or two tab characters to align to make the title be 16 characters.module_name<1 or 2 Tab chrs>vv.vv (DD Mmm YYYY)
This is then followed by the version number which is simply a single digit major version, followed by a full stop, followed by a two digit minor version; for example “1.23
” or “0.50
”.
Following is the date of module creation, which consists of a 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 standard help string is followed by some sort of copyright indication, extended version number, or other content. This is unofficial but is tolerated by RISC OS.
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.
This is the base number of the Zero, or a multiple of 64 less than 1<<24; b17 completely ignored, but should be zeroSWI chunk used by 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 SWI chunk has been issued.
When This you is the base number of theregister for a module allocation, if you request a SWI chunk you for will the be module, allocated if a the base module number, provides such SWIs. as This is used by&4E440
or &57180
. This is the number that goes into the SWI chunk base number field and it indicates to RISC OS that to that enable the module to be called when aSWI number within allocated its and range the has following been 63 issued. ‘belong’ to your module. TheSWI decoding table (below) provides a way of translating SWI names to numbers.
Because many modules provide SWIs and they must all be unique, you should never release a module without a properly allocated SWI chunk.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.
…
The Zero, or aligned offset to codeSWI decoding table is a simple table providing a list of names for the SWIs available. If this field is zero, RISC OS will try calling the SWI decoding code (below), but generally providing a list of SWI names is sufficient.
The Called list when is one a series of names your each 64 followed SWIs by has a been single issued. null On byte. entry: They do not need to be word aligned. A second null byte ends the list.
If you recall, SWIs are laid out as a “group” name, followed by an underscore, followed by an individual name for the SWI. For example OS_WriteC
, ADFS_DiscOp
, and Sound_Control
.
=> | 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 |
Therefore, On the exit: first entry is the group name. This is usually the same as the module name, and it will be used as the prefix to all of the SWIs available in the module. Then follow a list ofSWI names, each null terminated, followed by a null to end the list.
For example, the first few SWIs of my TTXHelper module are:
<= | 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 |
switable
= "TTXHelper", 0
= "AutoDetect", 0
= "ScanFlash", 0
= "ConvertToISO", 0
= "ConvertToANSI", 0
= "ExtractLine", 0
EQUB 0
ALIGN
You should return error &1E6,“No such SWI” or suitably worded equivalent for any unimplemented SWIs. A typical Handler is:
(there are others, but this is sufficient for explanation)
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
The module’s SWI chunk is &57180
, so RISC OS can look this up and know that TTXHelper_AutoDetect is SWI &57180, TTXHelper_ScanFlash is SWI &57181, TTXHelper_ConvertToISO is SWI &57182, TTXHelper_ConvertToANSI is SWI &57183, and finally SWI &57184 is TTXHelper_ExtractLine.
You do not need to include entries for the “X” prefixed form of SWIs (such as XOS_Module
), this is handled automatically by RISC OS.
If there are gaps in the Zero, or unaligned offset to name tableSWI numbering, it is typical to use the name “NOP” (for no-operation) for any gap entries. Be aware that this would make modulename_NOP
be a valid SWI, so your code should cope sensibly should anybody decide to call it. Since you would typically need to include these in your jump table (if writing in assembler) it is simplest if you make all of the NOPs jump to the “invalid SWI” error message.
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.
… TheSWI 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:
In order to facilitate internationalising modules, it is possible to provide a pointer to a Messages file that RISC OS can use to look up the output from the help and command table.
If the Messages file name (which is the full null terminated pathname of a Messages file) is present and bit 28 of the help and command information word is set, the output is not literal text to be printed, it is a token to be looked up in the Messages file here referred.
switable = "TTXHelper",0 = "AutoDetect",0 = "ScanFlash",0 = "ConvertToISO",0 = "ConvertToANSI",0 = "ExtractLine",0 = 0
This Note Messages the file final name empty string to end the table. Also remember to align before using any code labels.only relates to the help and command table. All other use of Messages within the module needs to be dealt with by the module author.
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 unaligned 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.
This is an offset to the module flags word.Zero (26bit only) or aligned offset to flags word
The flags are: word comprises:
Bit 0 | Module is 32 bit compatible |
Bits 1-31 | Reserved (should be zero) |
Modules not flagged as 32 bit compatible will not be loaded by a RISC OS 5. If no flags word is present, 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.
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, however but the information presented here is what superseded is by correct this nowadays. document.