Writing module code

 

Modules

If ARM assembler is the heart of the computer in physical terms, then modules are the heart of RISC OS.
With the exception of the "RISC OS" module and the "UtilityModule", every other part of RISC OS is replacable...

The major benefit of modules is that they can link into parts of the operating system to provide new SWI calls, handle ServiceCalls, deal with exceptions, or simply sit on an event waiting for something to happen. For example, on my current setup:

 

The RISC OS module?

There is a hidden module on all machines. In Arthur, it was called "Arthur". In RISC OS, it is called - nonsurprisingly - "RISC OS".
This module will not show up with *Modules or *ROMModules. If you try *Help Commands then you will see it listed first.

A quick perusal of the Arthur 1.20 module gives us the following error messages:

RISC OS 3.7 offers messages such as:

RISC OS 4 apparently includes references to chocolate. :-)

This isn't listed as a module as it isn't a module, really. It has no start code, initialisation code, finalisation code, service call handler... It doesn't need any, it is the base level of RISC OS.

Upon this, all the other modules are loaded. These all come together to form the operating system known as RISC OS.

 

SWIs

A SWI is a SoftWare Interrupt. It causes the system to search for a chunk of code pertaining to the SWI call numbered (name to number translation is a language-dependent issue). The exact mechanics of how this works is described here. What you need to know is when the user calls your SWI, the OS calls your SWI handler with a number from 0 to 63 corresponding with that requested by the user.

All modules have a chunk address. This is divisible by 64, and means that each module can have 64 SWIs. The WindowManager has many, the UtilityModule has none.

The chunk numbers are, typically, allocated on a request basis. As far as I am aware, you can complete the required entries in !Allocate, and email it to RISC OS Ltd (I think the address is allocate@riscos.com), and they will reply yes or no.
It is entirely possible to allocate the chunks yourself on an ad-hoc basis (I tend to reuse the &16F00 chunk for development code), but you should not complain if it clashes with something, neither should you EVER release such a module - it could cause untold chaos.

 

32-bit issues

When running in 32-bit, a 32-bit version of RISC OS will make no use of 26-bit modes (if they are available, newer processors such a Xscale (StrongARM 2) don't support 26-bit at all). Although a 26-bit mode may be available, there will be no run-time selection of which mode is to be used, and the memory mapping may preclude the use of 26-bit mode.

An effective way to handle mode issues is to use a macro. Code can therefore be compiled for 26-bit only, or 32-bit only simply by using the macros. Then, in (say) an obey file, you can choose which module version to load depending upon what OS is running. If a 32-bit OS is running, you load the 32-bit module, and so on.

The general API is pretty much the same. The main difference is that R14 on entry is just the return address, with no flags. To preserve flags, CPSR should be stacked on entry and restored on exit. Note that you are now preserving flags across the duration of your call, rather than restoring the caller's flags (which were previously passed in R14).

SWIs are no longer required to preserve N, Z, and C. They may still set/clear to return results, but 32-bit code shouldn't assume that SWIs preserve flags. Requiring SWIs to do so would impose an unacceptable delay on the SWI system. This, incidentally, respecifies ALL of the SWIs, and it makes it impossible for non-kernel SWIs to depend on NZCV flags on entry.

Many existing APIs don't actually require flag preservation, so you can usually get away with:

More 32-bit issues are included for each module entry point.

Any given system will be either 26-bit or 32-bit. It won't vary from one mode to another. Thus it is suggested that if your code supports both modes internally, you need only check the current mode in the initialisation code, rather then at the start of each entry point.

A snippet of code to check if you are in a 26-bit mode or a 32-bit mode is:

  TEQ     PC, PC
This sets the flags so a comparison is EQ if in a 32-bit mode (because the PC is only the PC, thus it is equal), and NE if in a 26-bit mode (because one PC is purely the PC, the other is PC with flags, thus the two are not equal).
To clarify:
  ; Check processor mode
  TEQ     PC, PC
  BEQ     mode_32bit ; branch to 32-bit code
  ; else we are in 26-bit mode

 

Instantation

A word used very often when talking about modules.
It is important, because a well written module will offer several instantations of itself, so it is performing the same task in several ways, with only one copy of the module code loaded into memory.
The best example of this is the FileCore module. If you type *Modules, you are likely to see:
  FileCore%ADFS
  FileCore%RAMFS
  FileCore%IDEFS
  FileCore%Base
This shows us that FileCore is providing services to three different filing systems, using three different instantations of itself. With only one FileCore module loaded.

 

Workspace

RISC OS generously allocates one word of workspace to each module instantation. Usually, the module will require more workspace, so it is expected that you will claim using OS_Module to allocate a chunk of the RMA, setting your private word to be a pointer to the claimed workspace. When the module is called through an entry point, RISC OS sets R12 to point to this private word, so you can easily figure out the true workspace address using:
  LDR  R12, [R12]
RISC OS works on the above assumption, and provides a few default actions for you. For example, when a module is killed RISC OS will attempt to free claimed workspace using the private word after your finalisation code has been called.
Additionally, it alters your workspace pointer following reallocation with RMTidy (though, this isn't really important as RMTidy has never really worked in practice - modules get really upset.

Please note that workspace allocated through OS_Module, 6 will always start at address &xxxxxxx4. The PRM says: "This enables code written for time-critical software (eg sound voice generators and FIQ handlers) to be aligned within the module body". Try it. Type *Modules and look at the workspace column. I've programmed RISC OS for over a decade and never realised that before!

 

Errors

You must obey certain rules, namely:

 

The module header

A module is defined by a set of words at the very start. These are:
Offset   Contains offset to
 
&00 Start code
&04 Initialisation code
&08 Finalisation code
&0C Service call handler
&10 Title string
&14 Help string
&18 Help and command keyword table
&1C SWI chunk base number (optional)
&20 SWI handler code (optional)
&24 SWI decoding table (optional)
&28 SWI decoding code (optional)
&2C Messages file name (optional)
&2C Module flags (optional)
All modules must have the first seven fields. However, if they are not required then the offset may be zero.
The title string should not be zero. If it is, RISC OS will call the module "<Untitled>", and it will not be possible to reference the module.
The SWI handler fields are optional, and are only used if they contain values that are considered to be 'sensible' for the SWI handler use (ie, number is divisible by four, etc).

 

This is an example of the header fields for FPEmulator version 4.14 (02/10/2000), as viewed in !Zap:
00000000 : 0000 : 00000000 : Start offset
00000004 : ¬B00 : 000002AC : Initialisation offset
00000008 : œC00 : 0000039C : Finalisation offset
0000000C : àC00 : 000003E0 : Service call handler offset
00000010 : 8000 : 00000038 : Title string offset
00000014 : xu00 : 00007578 : Help string offset
00000018 : 0000 : 00000000 : Help and command keyword table offset
0000001C :DD0 : 00040480 : SWI chunk base number
00000020 : Ð000 : 000000D0 : SWI handler code offset
00000024 : 8000 : 00000038 : SWI decoding table offset
00000028 : 0000 : 00000000 : SWI decoding code offset
0000002C : 0000 : 00000000 : Messages filename offset

 

Start code

This is used by OS_Module with Run or Enter codes.

  On entry:
    R0  = pointer to command string, including module name
    R12 = private word for the currently preferred instantiation

  On exit:
    Exit using OS_Exit, or by starting another application
    without having set up an exit handler.

  Processor status:
    IRQ is enabled on entry.
    FIQ is enabled.
    Processor is in USR mode.

  Re-entrancy:
    Entry point is not re-entrant.

  32-bit issues:
    Unchanged; flag preservation not required,
    only V is examined on exit
This is the offset to code to call if the module is to be entered as the current application. An offset of zero implies the code cannot be started this way (ie, it is purely a service module, filing system, et cetera).

The field need not actually be an offset. If it cannot be interpreted as such, then calling Run will actually execute what is assumed to be an instruction. This allows applications to have a branch at this position to allow them to be run directly, ie for testing.

This is called by the *RMRun command.

One would assume that the Run field is mostly outdated. Way back when, Acorn thought that all major pieces of software should be implemented as modules. Now, the current thinking is a standard application written in C or another suitable high level language (suitable, unfortunately, doesn't include BASIC - which is fair enough when considering DTP programs or web browsers, as the interpretation does put a speed penalty on BASIC programs, not to mention the lack of structs and other nicities).

Whenever a module is entered in this way, it becomes the preferred instantiation.

 

Initialisation code

This is used by OS_Module to initialise the module.

  On entry:
    R10 = environment string (command tail)
    R11 = where module has come from:
                      0 - Loaded from filing system (ie, *RMLoad)
             >&30000000 - Loaded from podule (R11 is podule base address)
           Other values - Reincarnated, <R11> other incarnations exist
    R12 = Pointer to private word (if non-zero, implies reinitialisation)
    R13 = Supervisor stack

  On exit:
    Must preserve processor mode and interrupt state.
    R0 - R6, R12, R14 and flags (except V) can be corrupted.
    R7 - R11 and R13 must be preserved.
    Use R14 to return (ie, MOV PC,R14).

  Processor status:
    IRQ and FIQ are enabled.
    Processor is in SVC mode.

  Re-entrancy:
    Entry point is not re-entrant.

  32-bit issues:
    Undefined, would assume flag preservation not required.
This is called when module loaded, or after an RMA 'tidy'. It is defined that a module will not be called via any other entry point until after the initialisation code has been called. Therefore, you should, here, set up sufficient (ie, claim workspace) to make it safe to call any of the other entry points.
Note that during initialisation, your module is not on the active modules list. If you need to call your own SWIs, you must set up the expected environment and branch to the entry point directly...or otherwise branch directly to the appropriate sections of code. Whichever you find easiest to deal with.

The PRM says:
"An offset of zero means the module does not need any initialisation. The operating system does not provide any default actions."
It is difficult to follow exactly what "An offset" is referring to. My guess is the contents of R10.

If the module is being re-entered after an RMTidy, the private word may be non-zero. This is the contents of the private word prior to finalisation, relocated (if necessary) by the operating system.

Typically, you'd claim workspace and set the private word to point to it. Otherwise, you would link on to vectors or declare yourself if necessary (ie, a filing system).

A module can refuse to initialise. If an error is generated (set R0 to indicate the error and return with V set), the operating system will automatically remove the module from the RMA.

 

Finalisation code

Used for reinitialising, deleting, tidying, clearing, and also when a module of the same name is loaded the old one is killed.
  On entry:
    R10 = Fatality indicator
            0 - non-fatal finalisation
            1 - fatal finalisation
    R11 = 'dynamic' incarnation number.
    R12 = Private word, for this instantiation.
    R13 = Supervisor stack.

  On exit:
    Must preserve processor mode and interrupt state.
    R0 - R6, R12, R14 and flags can be corrupted.
    R7 - R11 and R13 must be preserved.
    Use R14 to return (ie, MOV PC,R14).

  Processor status:
    IRQ is not altered.
    FIQ is enabled.
    Processor is in SVC mode.

  Re-entrancy:
    Entry point is not re-entrant.

  32-bit issues:
    Unchanged; flag preservation not required,
    only V is examined on exit
You should not enable interrupts if they are off unless you can cope with being entered via the service call handler at that stage.

If the call is fatal, the workspace will be freed and the workspace pointer is set to zero. If the call is non-fatal (ie, a tidy), then the workspace/pointer will be relocated - assuming they were allocated with OS_Module's Claim.

If the module generates an error, it remains in the RMA and is assumed to be initialised. The only way to remove a module in this state is via a module killer utility, or hard-reset.

If you are refusing to die, set R0 and return with V set.
Somehow, this behaviour is different to that of the above paragraph. The PRMs could have been clearer on this.

The module is 'de-linked' during a finalisation call. You should not call your own SWIs or commands.

If there is no finalisation entry, workspace is freed (if pointer is non-zero) and the module is quit by the operating system.

R11 contains the dynamic instantitation number. It is called this because it is the position of the instantiation in the instantiation list. However, position in the chain can vary, and the length of the instantiation list can also change. In other words, this is pretty useless really.
By the way, I've used the word instantiation five times in this paragraph!

 

Service call handler

The service call handler is used to deal with service calls (duh!).

  On entry:
    R1  = Service number.
    R12 = Private word, for this instantiation.
    R13 = Supervisor stack.

  On exit:
    R1 can be zero to claim service.
    R0, R2-R8 may be used to pass back a result, depending
      upon service call. Registers must not be corrupted
      unless they are returning values.
    R12 may be corrupted.
    Other constraints depend upon the service.
    Use R14 to return.

  Processor status:
    IRQ is not altered.
    FIQ is enabled.
    Processor is in SVC or IRQ mode (depending on service).

  Re-entrancy:
    Entry point may be re-entered by RISC OS. You must be
    able to handle this.

  32-bit issues:
    Unchanged; flags ignored on exit
This allows service calls to be recognised and dealt with. An offset of zero implies that the module is not interested in service calls.

You can:

It is important that you reject unwanted service calls as quickly as possible. The PRM gives the following example (which, note, is NOT 32-bit compliant, but can be made so fairly easily):


.service_call_handler
  TEQ     R1, #Service_<1>
  TEQNE   R1, #Service_<2>
  TEQNE   R1, #Service_<3>
  MOVNES  PC, R14                  ; reject unrecognised asap

  STMFD   R13!, { registers, R14 }
  LDR     R12, [R12]               ; if workspace pointer required

  TEQ     R1, #Service_<3>         ; find out which we've got
  BEQ     svc_3
  TEQ     R1, #Service_<2>
  BEQ     svc_2

.svc_1
  code to handle service call 1    ; if not 3 or 2, must be 1
  LDMFD   R13!, { registers, PC }^ ; and return
                                     (not 32-bit friendly)
.svc_2
  code to handle service call 2
  LDMFD   R13!, { registers, PC }^ ; and return
                                     (not 32-bit friendly)
.svc_3
  code to handle service call 3
  LDMFD   R13!, { registers, PC }^ ; and return
                                     (not 32-bit friendly)
You may be able to change the LDMFD R13!, { ... }^ to a 32-bit friendly version. Refer to the documentation for the service call required to see if flags should be preserved.

Some service calls indicate errors by setting registers on exit (the V set convention cannot be used). Others (like unknown OS_Byte) can either claim it (no error will be given) or ignore it (error will be given if nothing claims it). Thus, if you wish to provide things like unknown OS_Bytes and generate errors for stuff like invalid parameters, you should use the OS_Byte vector instead.

Only R0-R8 can be passed to a service call.

 

Title string

Used by OS_Module for referring to the module by name, and printed by the *Modules command.
It is a null terminated string which should be fairly short, yet descriptive. You should follow the system convention for module names (ie, "WindowManager", "SharedCLibrary", "Debugger").

 

Help string

This is a null terminated string printed by *Help before any information from the module, for example *Help Modules or *Help <modulename>.
The string should not contain any control characters, save space and tab (which will tab to the next multiple of eight column).
The format should be:
<modulename>[<TAB>(if necessary)]v.vv (DD MMM YYYY)
For example:
IDEFS           1.11e (20 Oct 1999)
ROM Patches     2.02 (24-Feb-97)
IDEFSFiler      1.09 (07 May 1998)
MyProtection    0.01 (19 Apr 1999)
Extended assem  1.77 (01 Mar 1998) © Darren Salt
DiscAccess      0.01 (14 Oct 2000) [http://www.heyrick.co.uk/assembler/]
SerialDriver    0.41 (20 Jan 1995) for issue 2 cards (slot0)
Font Manager    3.37 (05 Mar 1996) (Background blending)
TaskKiller      1.06 (14 Jan 1996) © Justin Fletcher
HTTP Support    1.21 (16 Nov 1999) (+128-bit SSL3) © ANT Ltd, 1995-9
HotBlanker      1.00 (19 Dec 1995) © DizzyWizard Software 1995
As you have noticed, there is some other stuff following. However the inportant things are present, the module number (which should be the x.xx form, nothing else) and the date (which should be day-as-a-number (with leading zero, if necessary), month-as-three-letters, and a full four-digit year). The first two examples are breaking the rules slightly.
Before a purist complains to me that adding comments to the help string is incorrect, I just wish to point them in the direction of the RISC OS 3 Printer Driver module. If it is wrong, then Acorn broke their own rules!
If you want to be pedantic: BASICTrans 2.00, Font Manager 3,37, and SoundDMA 1.53 as well.

You can use spaces in module names. Indeed, a fair few modules do this... "MOS Utilities", "Podule Manager", "Parallel Device", "Int'l Keyboard", and so on.

 

Help and command keyword table

This supplies information on the *Commands provided, and is used by OSCLI, *Status, *Configure, and *Help.

It is a table consisting of a list of keywords with pointers to associated help text. In the case of commands, entry address, parameter limits, et cetera.
The table consists of a sequence of entries, terminated by a zero byte. Each entry has the following format:
Purpose
String to match, null terminated
(ie, the keyword)
(align to a word boundary)
Offset of code
(from module start)
Information word
Offset of invalid syntax message
from module start, null terminated
Offset of help text from module start,
null terminated
The string to match should contain valid characters. For example, OSCLI strings (*Commands) should not contain characters which have special meaning within filenames. Generally, it is best to use letters. Case does not matter in matching, but for tidiness you should use a format similar to that for module names. A brief look at *Help Commands will give you the idea.

The code offset is used for commands. If this is zero, then it means that the string only has help text associated with it. This is shown in the DiscAccess module.
The code is entered with:

  On entry:
    R0  = Pointer to command tail (which you can't overwrite).
    R1  = Number of parameters as counted by OSCLI.
    R12 = Pointer to private word for this instantiation.
    R13 = Supervisor stack.
    R14 = Return address.

  On exit:
    R0 = error pointer, if anything went wrong.
    R7 - R11 must be preserved.

  Processor status:
    IRQ and FIQ are enabled on entry.
    Processor is in SVC mode.

  Re-entrancy:
    Entry point is not re-entrant.

  32-bit issues:
    Unchanged; flag preservation not required,
    only V is examined on exit

R1 is set to the number of parameters as counted by OSCLI. Thus, space-seperated entities are counted as parameters unless they are within quotes. For example:

Command tail                Parameters
one two three four          4
one "two three" four        3 ("two three" is counted as one parameter)
one,two,three,four          1 (commas, no spaces)

The information word contains limits on the number of parameters, and flags:

  On entry:
  Byte
    0 = Minimum number of parameters (0 - 255).
    1 = OS_GSTrans map for first 8 parameters.
    2 = Maximum number of parameters (0 - 255).
    3 = Flags
Byte one works as a bitmap selecting which of the first eight parameters are passed to OS_GSTrans and which are passed directly to the module. Bit zero is the first parameter, bit seven is the eighth. If the bit is set, the parameter is GSTrans'd before being passed to the module. If the bit is clear, it is passed directly to the module.

The flags are as follows:

Bit 31 = 1
The match string is a filing system command, and therefore should be matched after OSCLI has failed to find the command as a 'normal' command.
By way of example, open a TaskWindow and enter:
  Set Alias$ADFS Help
and then type:
  Help
and you'll see help text. The ADFS command still exists, but something else has 'claimed' it first.
(use Unset Alias$ADFS to restore normality)
OSCLI only looks for filing system commands in the current filing system, which is why Eject won't work, but CDFS:Eject will.
Commands that need this flag set are filing system specific commands such as Logon, Mount, Devices et cetera.
 
Bit 30 = 1
The string is to be matched by Status and Configure. The code should then scan the command tail and either return a status string, or alter CMOS RAM as appropriate. The code is called with R0 set as follows:
  On entry:
    R0 = 0  *Configure issued with no option. Print a syntax
            string and return.

    R0 = 1  Status option has been used. Print the current
            configuration status for the option.
If R0 is neither zero nor one, then the option has been called by Configure, R0 is therefore a pointer to the command tail with leading spaces skipped. You should decode the arguments and set the option as appropriate. If the command tail is not correct, you should return with V set and R0...
    R0 = 0  Bad configure option
    R0 = 1  Numeric parameter needed
    R0 = 2  Configure parameter too large
    R0 = 3  Too many parameters
    R0 > 3  R0 is a pointer to an error block for Configure
            to return.

 
Bit 29 = 1
The Help refers to a piece of code to call for that keyword, instead of an offset of a text string. The code is called as follows:
  On entry:
    R0  = Points at a buffer.
    R1  = Buffer length.

    R1 - R6, R12 can be corrupted.
On return, if R0 is non-zero, it is assumed to point to a zero-terminated string to pretty-print.
An example of this is *Help Station provided by the Econet module, which returns the current station number of the machine.
 
Other bits
Should be zero.

 

SWI chunk base number

This is the base of chunk numbers for the module. This is not a pointer, it is an absolute value. For example:
  &406C0  +0  Hourglass_On
          +1  Hourglass_Off
          +2  Hourglass_Smash
          +3  Hourglass_Start
          +4  Hourglass_Percentage
          +5  Hourglass_LEDs
          +6  Hourglass_Colours
The Hourglass module's SWI chunk is &406C0. The module can provide 64 SWIs, but it only provides seven. If you tried to call an invalid SWI in the Hourglass chunk, then it would return an error:
  >SYS &406CC

  SWI value out of range for module Hourglass
  >

 

SWI handler code

This is called to handle SWIs belonging to the module.
  On entry:
    R0 - R8 are passed from SWI caller by RISC OS.
    R9  = Corrupted on entry.
    R11 = SWI number, in range 0 to 63.
    R12 = Pointer to private word for this instantiation.
    R13 = Supervisor stack.
    R14 = Contains the flags of the SWI caller.

  On exit:
    R0  - R8 are returned to SWI caller by RISC OS.
    R10 - R12 may be corrupted.

  Processor status:
    IRQ is in the same state as it was when SWI called.
    FIQ is enabled.
    Processor is in SVC mode.

  Re-entrancy:
    Your module may issue SWIs to itself. If it does, it
    must handle them.

  32-bit issues:
    See text.
Your code should not explicitly enable interrupts, if they were disabled on entry; as there is likely to be a good reason for this (ie, your SWI was called from an interrupt handler!).

You must preserve R14 if you plan to call any SWIs as you will be called in SVC mode.

When your handler is called, the SWI number is reduced to a number from zero to sixty three and passed in R11. You should deal with the checking as quickly as possible. Suggested code (from the RISC OS 3 PRM) is:

.SWIentry
    LDR    R12, [R12]            ; get workspace pointer
    CMP    R11, #(EndOfJumpTable - JumpTable) / 4
    ADDLO  PC, PC, R11, LSL #2   ; dispatch if in range
    B      UnknownSWIerror       ; unknown SWI

.JumpTable
    B      MySWI_0
    B      MySWI_1
    .....
    B      MySWI_n
.EndOfJumpTable

.UnknownSWIerror
    STMFD  R13!, {R14}
    ADR    R0, ErrToken
    MOV    R1, #0
    MOV    R2, #0
    ADR    R4, ModuleTitle       ; from module header
    SWI    "XMessageTrans_ErrorLookup"
    LDMFD  R13!, {R14}
    ORRS   PC, R14, #Overflow_Flag

.ErrToken
    EQUD   &1E6                  ; same as system message
    EQUS   "BadSWI"              ; token to look up
    EQUB   0
    ALIGN
This code is not 32-bit compliant!
The address calculation relies upon there being exactly one instruction between the ADDLO and the B MySWI_0.

R14 contains the flags of the SWI caller, except V has been cleared. To return without updating the flags, use:
  MOVS PC, R14
Otherwise alter the link register with code such as:
  ORRS PC, R14, #Overflow_Flag
Note that this is a 32-bit unfriendly operation.
Note that all flags returned are passed on to the caller, so conditional code should be written with this in mind.

Bit 17 in the given SWI number is irrelevant. The module will be called with a range 0 to 63, and it will not know if the X-form SWI has been called. You should treat it as if X has been set, and thus set R0 and return with V set upon an error occuring. Any error returned is either passed on to the caller (if X set) or dealt with by RISC OS possibly terminating the caller task (if X clear).

On 26-bit systems, R14 is a return address (inside the kernel) with the user's NZCIF flag, V clear, mode set to SVC. On exit, the current NZCV flags are then passed back to the SWI caller. Hence, MOVS PC, R14 preserves the SWI caller's NZC flags and clears V. The NZ and C flags on entry are undefined (and are NOT the caller's), and V is clear. Flag preservation and manipulation is simple.

On 32-bit systems, R14 is a return address only. There is no way of determining the caller's flags, so you are not expected to preserve them. The NZC and V flags you exit with will be passed back to the caller. This isn't as horrid as it sounds, as it wasn't possible to specify SWI flags on entry in BASIC or most of the C libraries.

If you are writing a new module, simply specify that all your SWIs corrupt flags. Then you can return with MOV PC, R14 regardless of the mode you are operating in.
If you are converting a module, it is recomended that the same binary work on 26-bit systems. You should preserve flags in 26-bit mode, if you did so previously. On a 32-bit system, you need not worry about preserving flags.

 

SWI decoding table

This is a pointer to a list of SWI names.
You can (with the following offset) provide code to convert SWI names to numbers or vice versa, but it is much easier to generate a SWI table and let RISC OS do it for you.

When OS_SWINumberFromString or OS_SWINumberToString are called, then if the SWI decoding table is present then the string required is read and the conversion performed. If the SWI decoding table offset is zero, then the SWI decoding code entry is called.

The table format is:

  SWI group prefix (usually the module name or something similar)
  Name of the 0th SWI (null terminated)
  Name of the 1st SWI (null terminated)
  ...
  Name of the nth SWI (null terminated)
  Null byte to terminate.
The strings are NOT word aligned.

You'll have noticed that all SWIs are in the form of a service description, then an underscore, then a facility description, ie Wimp_CreateIcon. Thus, we are requesting the CreateIcon facility of the Wimp service. In this case, "Wimp" would be the group prefix and "CreateIcon" would be one of the SWIs available.
RISC OS will convert this to the familiar Service_Facility format for us.

The ShellCLI module provides us with two SWIs, Shell_Create and Shell_Destroy. Thus it's SWI decoding table would be:

   EQUS   "Shell"
   EQUB   0
   EQUS   "Create"
   EQUB   0
   EQUS   "Destroy"
   EQUB   0
   EQUB   0
In the case of OS_SWINumberToString, if we passed &405C1 then it would be converted into Shell_Destroy (SWI base chunk &405C0, second SWI). If bit 17 was set (ie, &605C1) then an X would be prepended to give us XShell_Destroy.

If the SWI decoding table does not have enough entries for the requested SWI, then it will be tried in decimal.

It is not acceptable to leave a blank for any SWIs you don't wish to be callable by name (ie, for registration functions or the like). You may then either use a sequential number, so you SWI would become something like MyModule_12, or you could use "NOP". More than one SWI may be 'NOP', so the SWI can only be called by number.
For example, the complete set of SWIs supported by RAMFS is:

  RAMFS_
        DiscOp
        NOP
        Drives
        FreeSpace
        NOP
        DescribeDisc
The reason for the two NOPs is due to the way FileCore handles compliant filing systems.

 

SWI decoding code

This is the entry for code to convert to and from SWI number and/or string.
  On entry:
    R12 = Pointer to private word for this instantiation.
    R13 = Supervisor stack.
    R14 = Return address.

    Text to number:
     R0 = Any number less than zero.
     R1 = Pointer to string to convert (control terminated).

    Number to text:
     R0 = SWI number ANDed with 63 (ie, offset from 0 to 63).
     R1 = Pointer to output buffer.
     R2 = Offset within output buffer at which to place text.
     R3 = Size of buffer.

  On exit:
    R12 preserved.
    
    Text to number:
     R0 = Offset into chunk if SWI recognised, else < 0.
     R1 - R6 preserved.

    Number to text:
     R2 = Updated by length of text.
     R0 - R1, R3- R6 preserved.

  Processor status:
    IRQ and FIQ are enabled.
    Processor is in SVC mode.

  Re-entrancy:
    Entry point is not re-entrant.

  32-bit issues:
    Unchanged; flag preservation not required,
    only V is examined on exit in number to text conversion
This is used when a SWI is not defined in the SWI decode table (or when the table does not exist). If the SWI cannot be decoded and there is no table, then return with registers unaltered and RISC OS will provide a suitable default.

When converting from number to text, RISC OS will append a null at the position after the length you have returned (in R2).

As a small observation, I can imagine it may be useful if you plan to craft a little bit of magic, but for the main course it is probably easiest to just use the decoding table facilities provided by RISC OS.
I have just examined over a hundred modules in my !System directory. Only two modules have SWI decoding code:

 

Messages file name

Gleaned from examining newer modules, this appears to be an optional pointer to the full (in ResourceFS) filename of a Messages file.
It appears that you need to open the Messages file in the usual way, so I'm not exactly sure what function this serves?

 

Module flags

This is an offset to the module flag word(s). The first word is:
  Bit 0 : Module is 32-bit compatible
  All other bits reserved (thus, should be zero).
Modules that are not 32-bit compatible will not be loaded by a 32-bit version of RISC OS. If the flags word is not present, it is assumed that the module is 26-bit.

 

 


Return to assembler index
Copyright © 2001 Richard Murray