RISC World

Modules for Beginners

Modules, what are they and what can they do?


From the users point of view an operating system should provide the following:

An easy to use graphical user interface.
Stability and good crash protection.

However for the systems designer the operating system should be structured to allow for easy updating and servicing. A modular design helps designers and application programmers to modify the system to drive new hardware without the need to re-write large amounts of code.

An example of this can be seen when new filing systems have been introduced. My IDE hard drive and Zip drive uses the filing system module which has it calls modified so they can in effect treat the drives like any other 'normal' disc drive. The same approach is taken when an extension to the graphic interface is required.

It will come as no surprise that RISC OS has a modular construction.

The RISCOS Structure

RISC OS consists of a relatively small kernel of code, which consist of routines such as SWI calling, vector, and other low level routines.

The rest of the operating system consists of blocks of code as 'add ons'. These are called modules. Some of these reside in ROMS but others are softloaded into RAM. This can be seen by holding down the ctrl key pressing F12 key to open the Task Window. Typing Modules at the * prompt will result in a list of the modules currently loaded. I will return to this listing in more detail in a later article. But for now it can be easily seen that each module has a name.

Looking at part of the module listing for my RISC PC

The FileCore module (number 26) is used six times as explained. Also note the FontManager module, this takes care of all the plotting of Fonts. This has been recoded in each version of RISC OS.

All this is fine if you are an operating systems designer but is there scope for the enthusiast?

In a word YES. Modules are quite easy to design and can extend the facilities of RISC OS.In this set of articles I hope to show you how to design your own modules and give an insight into the workings of RISC OS.

Design Constraints

From the start I must point out that any programming that modifies the workings of the operating system is fraught with danger. I cannot take any responsibility for the readers typing errors, when trying out examples. I will test all the routines carefully on my computers (RISC OS 3.7 and RISC OS 3.1).

I am assuming the reader has a knowledge of ARM programming. (see my articles Beginners Guide to ARM programming in previous issues of Risc World). Make sure you back up all work on more than one destination (floppy and hard drive). Do not be surprised if your computer crashes when testing out your own routines.

Memory position and Usage

Modules loaded into RAM are loaded into an area of memory called the relocatable module area (RMA). RAM Modules must be coded so they are relocatable since RISC OS can at anytime move them. Any memory used for data is set up in the modules workspace. Each module can have blocks of memory set up when the module first starts (initialises). This workspace memory can reside anywhere in the RMA (not added on the end of the module).

When the module is killed off (finalises) the workspace must be released. This is currently done by RISC OS but it is best to do it yourself in case future releases do not.

A module can be initialised more than once, each initialisation produces a new instantiation, like the FileCore module above. Each instantiation can have its own workspace.Luckily RISC OS provides the user with SWI's to do these operations based on SWI"OS_Module".

What Modules can do.

A module can do the following.

  • Provide a set of * commands.
  • A set of SWI routines.
  • Intercept vectors and hence modify the workings of existing SWI's
  • Set up Wimp filtering and hence modify the workings of Wimp applications.
  • Intercept service calls and hence modify the workings of RISC OS at a low level.
  • Be a complete Wimp application, subject to certain restrictions.

It is up to the designer to decide which facilities are required and design the module accordingly. It is rare that a single module would use all the above, in fact most modules make use of a few, * commands and SWI commands being the most common.

Lets say a designer requires some SWI and *commands. The modules design would have these facilities active and the rest "switched off". RISC OS detects which facilities a module has by looking into the first 11 words of a module called the Module Header. The Module Header has the following structure:

     offset   Contents                Contains
     0000     offset to code          Application start code
     0004     offset to code          initialisation code
     0008     offset to code          finalisation code
     0012     offset to code          service call handling
     0016     offset to string        Module title string
     0020     offset to string        Module help string
     0024     offset to table         help and *commands table
     0028     number                  SWI start chunk number
     0032     offset to code          SWI handler code
     0036     offset to table         SWI decoding table
     0040     offset to code          SWI decoding code

If no SWI's are required then offsets 0028 to 0040 may be left out.

To switch off any of the facilities then a value zero is placed in the contents word. All modules must have an initialisation and finalisation codes, title and help strings.

All offsets to codes, strings, tables etc. must start on a word boundary and must point to a memory location within the RMA. Usually they will point to an offset within the module.

Loading and Deleting Modules

When a module is loaded using the *RMLoad command RISC OS loads it into the first available free memory block in the RMA. Then RISC OS looks into the module header, storing the title string, help string, SWI start chunk number etc. RISC OS then jumps to the initialisation code.

On returning from the initialisation code, RISC OS jumps to the Application start code and hence starts up the application (if this offset is greater than zero) otherwise the module is not called unless one of its *commands, SWIs are called, or any service or vector calls occurs.

Service calls are given to all the modules in order they were initialised. Each module can modify the service call (or ignore the call) then pass it on, or intercept the call. More on this in a later article. If any filters or vector claims have been set up in the initialisation then the module will be called in a similar method to service calls.

When the module is deleted by the command *RMKill RISC OS then 'forgets the offsets' and calls the finalisation code where the user must release any filters and vector claims, etc. then deletes the module from the RMA. RISC OS then tidies up the RMA to move any free blocks beyond the last loaded module. It does this by calling the finalisation codes, moving the modules then calling the initialisation codes. This prevents any memory fragmentation in the RMA.

How to Write Modules

No special software need be used to write modules since the BASIC ARM Assembler can be used. Obviously any other software can be used but I will stick to the Assembler within a BASIC program written using !Edit since everyones computer contains these.

As previously stated modules must be relocatable so the assembler must be set up using P% and O% for offset assembling thus.

     DIM mcode% 1024:          REM reserve memory for assembling code
     FOR pass% = 4 TO 7 STEP 3:REM two pass offset assembling
     P% = 0:                   REM offset zero memory offsets
                               REM are from start of module.
     O% = mcode%
                               REM Now setup the Module Header
     [ OPT pass%
     EQUD 0                    ;application startcode
     EQUD init%                ;initialisation
     EQUD final%               ;finalisation
     EQUD 0                    ;service start
     EQUD title%               ;Module title string
     EQUD help%                ;Module help string
     EQUD 0                    ;command table offset
     SYS "OS_File", 10, "filename", &FFA, ,mcode%, O%

This is the template for assembling all the examples in this series of articles. It is included on the CD with the title ModTemp. The title and help strings are setup by using the EQUS and EQUB directives and ALIGN thus:

     EQUS "Module Name" ;module name keep it less than 12 characters
     EQUB 0
     EQUS "More detailed title"+CHR$(9)+"x.xx ("+MID$(TIME$,5,11)+")"+CHR$(13)
     EQUS "Short description of Module action"
     EQUB 0

The x.xx is the version number e.g. 1.01. It is used within the RMEnsure command to test for an up to date module.

MID$(TIME$,5,11) gives the date the assembling was done in the form "21 Aug 2001". The CHR$(13) and CHR$(9) are used when RISC OS prints out this help message using *Help command so the printout looks tidy. Note the terminating zero byte after each string. The initialisation and finalisation code must start and end thus:

     STMFD R13!,{R7-R11,R14}
     LDMFD R13!,{R7-R11,PC}

The pushing and pulling of registers R7 to R11 may be left out if they are not used in the routine. Registers R0 to R6 can be used and corrupted. Registers R7 to R11 must not be corrupted.

RISC OS always enters a module with R13 pointing to a stack and R12 pointing to a private word which can be used to point to the modules workspace.

Well thats it for this part, next time I will extend the above ideas to produce a simple modules that will not do much but can be used as a basis for more useful modules in later parts.

Brian Pickard