RISCOS.com

www.riscos.com Technical Support:
Acorn C/C++

 

How to write relocatable modules in C


Relocatable modules are the basic building blocks of RISC OS and the means by which RISC OS can be extended by a user. The archetypal use for RISC OS extensions is the provision of device drivers for devices attached to Archimedes hardware.

Relocatable modules also provide mechanisms which can be exploited to:

  • extend RISC OS's repertoire of built-in commands (* commands)
    (analogous to plugging additional ROMs into a BBC microcomputer of pre-Archimedes vintages)
  • provide services to applications (for example, as does the shared C library module)
  • implement 'terminate and stay resident' (TSR) applications.

The idea of TSR applications will be most familiar to PC users, whereas extending the * command set (via 'software ROM modules') will seem most familiar to those with a background in the BBC computer. A complete discussion of these topics is beyond the scope of this chapter.

For modules which provide services, the principal mechanism for accessing those services from user code is the SoftWare Interrupt (SWI). For example, the shared C library implements a handler for a single SWI which, when called from the library stubs linked with the application, returns the address of the C library module which in turn allows the library stubs to be initialised to point to the correct addresses within the library module. Thereafter, library services are accessed directly by procedure call, rather than by SWI call. All this illustrates is the rich variety of mechanism available to be exploited.

Getting started

To write a module in C you will need:

  • the CC and CMHG tools supplied with Acorn C/C++
  • the C Library stubs supplied with Acorn C/C++
  • a thorough understanding of RISC OS modules (read the Modules chapter of the RISC OS 3 Programmer's Reference Manual).

Constraints on modules written in C

A module written in C must use the shared C library module via the library stubs. Use of the stand-alone C library (ANSILib) is not a supported option.

All components of a module written in C must be compiled with the compiler SetUp menu option Module code enabled. This allows the module's static data to be separated from its code and multiply instantiated.

Overview of modules written in C

A module written in C includes the following:

  • a Module Header (described in the Modules chapter of the RISC OS 3 Programmer's Reference Manual), constructed using CMHG;
  • a set of entry and exit 'veneers', interfacing the module header to the C run-time environment (also constructed using CMHG);
  • the stubs of the shared C library;
  • code written by you to implement the module's functionality - for example: *command handlers, SWI handlers and service call handlers.

These parts must be linked together using the Link tool with the SetUp box Module option enabled.

The next section describes:

  • how to write a CMHG input file to make a module header and any necessary entry veneers
  • the interface definitions to which each component of your module must conform
  • how to write a CMHG input file to generate entry veneers for IRQ and event handlers written in C.

Functional components of modules written in C

The following components may be present in a module written in C (all are optional except for the title string and the help string which are obligatory):

  • Runnable application code (called start code in the module header description). This will be present if you tell CMHG that the module is runnable and include a main() function amongst your module code.
  • Initialisation code. 'System' initialisation code is always present, as the shared library must be initialised. Your initialisation function will be called after the system has been initialised if you declare its name to CMHG.
  • Finalisation code. The C library has to be closed down properly on module termination. Your own finalisation code will be called before the system has been closed down if you declare its name to CMHG.
  • Service call handler. This will be present if you declare the name of a handler function to CMHG. In addition, you can give a list of service call numbers which you wish to deal with and CMHG will generate fast code to ignore other calls without calling your handler.
  • A title string in the format described in the RISC OS 3 Programmer's Reference Manual. CMHG will insist that you give it a valid title string.
  • A help string in the format described in the RISC OS 3 Programmer's Reference Manual. Again, CMHG will insist that you give a valid help string.
  • Help and command keyword table. This section is optional and will be present only if you describe it to CMHG and declare the names of the command handlers to CMHG. Obviously, their implementations must be included in the linked module.
  • SWI chunk base number. Present only if declared to CMHG.
  • SWI handler code. Present if you declare the name of a handler function to CMHG.
  • SWI decoding table. Present only if described to CMHG.
  • SWI decoding code. Present only if you declare the name of your decoding function to CMHG.
  • IRQ handlers. Though not associated with the module header, CMHG will generate entry veneers for IRQ handlers. You can register these veneers with RISC OS using SWI OS_Claim, etc; you have to provide implementations of the handlers themselves. The names of the handler functions and of the entry veneers have to be given to CMHG.
  • An event handler. Though not associated with the module header, CMHG will generate entry veneers for an event handler. You can register these veneers with RISC OS using SWI OS_Claim, etc; you have to provide implementations of the handlers themselves. The names of the handler functions and of the entry veneers have to be given to CMHG.

Each component that you wish to use must be described in your input to CMHG. Use of most components also requires that you write some C code which must conform to the interface descriptions given in the sections below.

The C module header generator

The C Module Header Generator (CMHG) is a special-purpose assembler of module headers. It accepts as input a text file describing which module facilities you wish to use and generates as output a linkable object module (in ARM Object Format). For details of how to run the CMHG tool, see the chapter entitled CMHG earlier in this manual.

The format of input to CMHG

Input to CMHG is in free format and consists of a sequence of 'logical lines'. Each logical line starts with a keyword which is followed by some number of parameters and (sometimes) keywords. The precise form of each kind of logical input line is described in the following sections.

A logical line can be continued on the next line of input immediately after a comma (that it, if the next non-white-space character after a comma is a newline then the line is considered to be continued).

Lists of parameters can be separated by commas or spaces, but use of comma is required if the line is to be continued.

A comment begins with a ; and continues to the end of the current line. A comment is valid anywhere that trailing white space is valid (and, in particular, after a comma).

A keyword consists of a sequence of alphabetic characters and minus signs. Often, a keyword is the same as the description of the corresponding field of the module header (as described in the RISC OS 3 Programmer's Reference Manual) but with spaces replaced by minus signs. For example: initialisation-code; title-string; service-call-handler.

Keywords are always written entirely in lower case and are always immediately followed by a :. Character case is significant in all contexts: in keywords, in identifiers, and in strings.

Numbers used as parameters are unsigned. Three formats are recognised:

  • unsigned decimal
  • 0xhhh... (up to 8 hex digits)
  • &hhh... (up to 8 hex digits).

In the following sections, the parts headed CMHG description tell you what you have to describe to CMHG in order to use the facility described in that section; the parts headed C interface introduce a description of the interface to which the handler function you write must conform. You may omit any trailing arguments that you don't need from your handler implementations.

Runnable application code

CMHG description:

module-is-runnable:             ; No parameters.

C interface:

int main(int argc, char *argv[]);
/*
 * Entered in user-mode with argc and argv
 * set up as for any other application. Malloc 
 * obtains storage from application workspace.
 */

To be useful (ie re-runnable) as a 'terminate and stay resident' application, a runnable application must implement at least one * command handler (see below) for its command line, which, when invoked, enters the module (calls SWI OS_Module with the Enter reason code).

Initialisation code

CMHG description:

initialisation-code: user_init ; The name of your initialisation function.
                               ; Any valid C function name will do.

C interface:

_kernel_oserror *user_init(char *cmd_fail, int podule_base, void *pw);
/*
 * Return NULL if your initialisation succeeds; otherwise return a pointer to an 
 * error block. cmd_tail points to the string of arguments with which the 
 * module is invoked (may be "").
 * podule_base is 0 unless the code has been invoked from a podule.
 * pw is the 'r12' value established by module initialisation. You may assume
 * nothing about its value (in fact it points to some RMA space claimed and
 * used by the module veneers). All you may do is pass it back for your module
 * veneers via an intermediary such as SWI OS_Call Every (use _kernel_swi() to
 * issue the SWI call).
 */

Note that you can choose any valid C function name as the name of your initialisation code (CMHG insists on no more than 31 characters).

Finalisation code

CMHG description:

finalisation-code: user_final ; The name of your finalisation function.
                              ; Any valid C function name will do.

C interface:

extern _kernel_oserror *user_final(int fatal, int podule, void *pw);
/*
 * Return NULL if your finalisation succeeds. Otherwise return a pointer to an 
 * error block if your finalisation handler does not wish to die (e.g. toolbox
 * modules return a 'Task(s) active' error).
 * fatal, podule and pw are the values of R10, R11 and R12 (respectively)
 * on entry to the finalisation code.
 */

A call to library finalisation code is inserted automatically by CMHG; the C library finalisation code will call your finalisation handler immediately before closing down the library (on module finalisation).

Service call handler

CMHG description:

service-call-handler:  sc_handler <number> <number> ...

C interface:

void sc_handler(int service_number, _kernel_swi_regs *r, void *pw);
/*
 * Return values should be poked directly into r->r[n];
 * the right value/register to use depends on the service number
 * (see the relevant RISC OS Programmer's Reference Manual section for details).
 * pw is the private word (the 'r12' value.
 */

Service calls provide a generic mechanism. Some need to be handled quickly; others are not time critical. Because of this, you may give a list of service numbers in which you are interested and CMHG will generate code to ignore the rest quickly. The fast recognition code looks like:

        CMPS    r1, #FirstInterestingServiceNumber
        CMPNES  r1, #SecondInterestingServiceNumber
        ...
        CMPNES  r1, #NthInterestingServiceNumber
        MOVNES  pc, lr ; drop into service call entry veneer.

If you give no list of interesting service numbers then all service calls will be passed to your handler.

In order to construct a relocatable module which implements a RISC OS application (a TSR application) you must claim and deal with the Service_Memory service call. See the relevant section in the Programmer's Reference Manual for details of this service call.

The following is a suitable handler written in C for this service call:

#define Service_Memory 0x11
extern void FrontEnd_services(int service_number, _kernel_swi_regs *r, void *pw)
{
  IGNORE(pw);
  /* keep application workspace (r2 holds CAO pointer) */
  if (service_number == Service_Memory && r->r[2] == (int)Image__RO_Base)
  {
    r->r[1] = 0;  /* refuse to relinquish app. workspace */
  }
}

The above handler needs to compare the contents of r[2] with the address of the base of your module containing it. This is not a value directly available in C, so the following assembly language fragment can be used to gain access to the symbol Image$$RO$$Base, which is defined by Link when your module is linked together:

        IMPORT  |Image$$RO$$Base|
        EXPORT  Image__RO_Base

        AREA    Code_Description, DATA, REL
Image__RO_Base
        DCD     |Image$$RO$$Base|

        END

Title string

CMHG description:

title-string: title

title must consist entirely of printable, non-space ASCII characters.

Any underscores in the title are replaced by spaces. CMHG will fault any title longer than 31 characters and warn if the length of the title string is more than 16.

Help string

CMHG description:

help-string: help d.dd comment  ; help string and version number

The help string is restricted to 15 or fewer alphanumeric, ASCII characters and underscores. Longer strings are truncated (with a warning) to 15 characters then padded with a single space. Shorter titles are padded with one or two TAB characters so they will appear exactly 16 characters long.

The version number must consist of a digit, a dot, then 2 consecutive digits. Conventionally, the first digit denotes major releases; the second digit minor releases; and the third digit bug-fix or technical changes. If the version number is omitted, 0.00 is used.

CMHG automatically inserts the current date into the version string, as required by RISC OS convention.

A 'comment' of up to 34 characters can also be included after the version number. It will appear in the tail of the module's help string, after the date. A typical use is for annotating the help string in the following style:

SomeModule      0.91 (27 Jun 1989) Experimental version

CMHG refuses to generate a help string longer than 79 characters and warns if it has to truncate your input.

Help and command keyword table

CMHG description:

command-keyword-table: cmd_handler command-description+

(Here command-description+ denotes one or more command descriptions).

A command-description has the format:

star-command-name "("
  min-args:       unsigned-int ; default 0
  max-args:       unsigned-int ; default 0
  gstrans-map:    unsigned-int ; default 0
  fs-command:                  ; flag bits in
  status:                      ; the flag byte
  configure:                   ; of the cmd table
  help:                        ; info word.
  invalid-syntax: text
  help-text:      text
  ")"

Each sub-argument is optional. A comma after any item allows continuation on the next line.

A text item follows the conventions of ANSI C string constants: it is a sequence of implicitly concatenated string segments enclosed in " and ".

Segments may be separated by white space or newlines (no continuation comma is needed following a string segment).

Within a string segment \ introduces an escape character. All the single character ASCII escapes are implemented, but hexadecimal and octal escape codes are not implemented. A \ immediately preceding a newline allows the string segment to be continued on the following line (but does not include a newline in the string; if a newline is required, it must be explicitly included as \n).

min-args and max-args record the minimum and maximum number of arguments the command may accept; gstrans-map records, in the least significant 8 bits, which of the first 8 arguments should be subject to expansion by OS_GSTrans before calling the command handler.

The keywords fs-command, status, configure and help set bits in the command's information word which mark the command as being of one of those classes.

invalid-syntax and help-text messages are (should be) self-explanatory.

Example CMHG description:

command-keyword-table: cmd_handler
tm0(  min-args: 0, max-args: 255,
      help-text: "Syntax\ttm1 <filenames>\n"),
tm1(  min-args:1, max-args:1,
      help-text: "Syntax\ttm2" " <integer>"
      "\n")

This describes two * commands, *tm0 and *tm1, which are to be handled by the C function cmd_handler. The handler function will be called with 0 as its third argument if it is being called to handle the first command (tm0, above), 1 as its third argument if it is being called to handle the second command (tm1, above), etc. The programmer must keep the CMHG description in step with the implementation of cmd_handler.

C interface:

_kernel_oserror *cmd_handler(char *arg_string, int argc, int cmd_no, void *pw);
/*
 * If cmd_no identifies a *HELP entry, then cmd_handler must return
 * arg_string or NULL (if arg_string is returned, the NUL-terminated
 * buffer will be printed).
 * Return NULL if if the command has been successfully handled;
 * otherwise return a pointer to an error block describing the failure
 * (in this case, the veneer code will set the 'V' bit).
 * *STATUS and *CONFIGURE handlers will need to cast 'arg_string' to
 * (possibly unsigned) long and ignore argc. See the RISC OS Programmer's 
 * Reference Manual for details.
 * pw is the private word pointer ('r12') value passed into the entry veneer
 */

SWI chunk base number

CMHG description:

swi-chunk-base-number: number

You should use this entry if your module provides any SWI handlers. It denotes the base of a range of 64 values which may be passed to your SWI handler. SWI chunks are allocated by Acorn: read the documentation carefully to discover which chunks you may use safely. In some cases you may need to write to Acorn to get a chunk allocated uniquely to your product (though this should not be undertaken lightly and should only be done when all alternatives have been exhausted). See the chapter An introduction to SWIs in the RISC OS 3 PROGRAMMER'S REFERENCE MANUAL FOR MORE DETAILS.

SWI handler code

CMHG description:

swi-handler-code:  swi_handler  ; any valid C function name will do

C interface:

_kernel_oserror *swi_handler(int swi_no, _kernel_swi_regs *r, void *pw);
/*
 * Return: NULL if the SWI is handled successfully; otherwise return
 * a pointer to an error block which describes the error.
 * The veneer code sets the 'V' bit if the returned value is non-NULL.
 * The handler may update any of its input registers (r0-r9).
 * ps is the private word pointer ('r12') value passed into the
 * swi_handler entry veneer.
 */

If your module is to handle SWIs then it must include both swi-handler-code and swi-chunk-base.

Example CMHG description:

swi-chunk-base-number: 0x88000
swi-handler-code:      widget_swi

SWI decoding table

CMHG description:

swi-decoding-table: swi-base-nameswi-name*

This table, if present, is used by OS_SWINumberTo/FromString.

Example CMHG description:

swi-chunk-base-number: 0x88000
swi-handler-code:      widget_swi
swi-decoding-table:    Widget,
                       Init  Read  Write  Close

This would be appropriate for the following name/number pairs:

Widget_Init  0x88000
Widget_Read  0x88001
Widget_Write 0x88002
Widget_Close 0x88003

SWI decoding code

CMHG description:

swi-decoding-code: swi_decoder  ; any valid C function name will do

C interface:

void swi_decode(int r[4], void *pw);
/*
 * On entry, r[0] < 0 means a request to convert from text to a number.
 * In this case r[1] points to the string to convert (terminated by a
 * control character, NOT necessarily by NUL).
 * Set r[0] to the offset (0..63) of the SWI within the SWI chunk if
 * you recognise its name; set r[0] < 0 if you don't recognise the name.
 *
 * On entry, r[0] >= 0 means a request to convert from a SWI number to
 * a SWI string:
 *   r[0] is the offset (0..63) of th SWI within the SWI chunk.
 *   r[1] is a pointer to a buffer;
 *   r[2] is the offset within the buffer at which to place the text;
 *   r[3] points to the byte beyond the end of the buffer.
 * You should write th SWI name into the buffer at th position given
 * by r[2] then update r[2] by the length of the text written (excluding
 * any terminating NUL, if you add one).
 * 
 * pw is the private word pointer ('r12') passed into the swi_decode
 * entry veneer.
 */

If you omit a SWI decoding table then your SWI decoding code will be called instead. Of course, you don't have to provide either.

Turning interrupts on and off

The following (<kernel.h>) library functions support the control of the interrupt enable state:

int _irqs_disabled(void);
/*
 * Returns non-0 if IRQs are currently disabled.
 */  

void _irqs_off(void);
/*
 * Disable IRQs.
 */  

void _irqs_on(void);
/*
 * Enable IRQs.
 */

These functions suffice to allow saving, restoring and setting of the IRQ state. Ground rules for using these functions are beyond the scope of this document. However, general advice is to leave the IRQ state alone in SWI handlers which terminate quickly, but to enable it in long-running SWI handlers.

What a SWI handler does to the IRQ state is part of its interface contract with its clients: you, the implementor, control that interface contract.

IRQ handlers

CMHG description:

irq-handlers:  entry_name/handler_name ...

Any number of entry_name/handler_name pairs may be given. If you omit the / and the handler name, CMHG constructs a handler name by appending _handler to the entry name.

C interface:

extern int entry_name(_kernel_swi_regs *r, void *pw);
/*
 * This is name of the IRQ handler entry veneer compiled by CMHG.
 * Use this name as an argument to, for example, SWI OS_Claim, in
 * order to attach your handler to IrqV.
 */  

int handler_name(_kernel_swi_regs *r, void *pw);
/*
 * This is the handler function you must write to handle the IRQ for
 * which entry_name is the veneer function.
 *
 * Return 0 if you handled the interrupt.
 * Return non-0 if you did NOT handle the interrupt (because,
 * for example, it wasn't for your handler, but for some other
 * handler further down the stack of handlers).
 *
 * 'r' points to a vector of words containing the values of r0-r9 on
 * entry to the veneer. Pure IRQ handlers do not require these, though
 * event handlers and filing system entry points do. If r is updated,
 * the updated values will be loaded into r0-r9 on return from the
 * handler.
 *
 * pw is the private word pointer ('r12') value with which
 * the IRQ entry veneer is called.
 */

Handlers must be installed from some part of the module which runs in SVC mode (eg initialisation code, a SWI handler, etc). The name to use at installation time is the entry_name (not the name of the handler function). This is because C functions cannot be entered directly from IRQ mode, but have to be entered and exited via a veneer which switches to SVC mode. Running in SVC mode gives your handler maximum flexibility.

IRQ handlers can also be used as filing system entry points. A full discussion of these topics is beyond the scope of this Guide; refer to the RISC OS 3 PROGRAMMER'S REFERENCE MANUAL FOR DETAILS AND FOR INFORMATION ON HOW TO INSTALL AND REMOVE HANDLERS.

Event handler

CMHG description:

event-handler:  entry_name/handler_name event_no event_no ...

Only one entry_name/handler_name pair may be given.

C interface:

extern int entry_name(_kernel_swi_regs *r, void *pw);
/*
 * This is name of the event handler entry veneer compiled by CMHG.
 * Use this name as an argument to, for example, SWI OS_Claim, in
 * order to attach your handler to EventV.
 */  

int handler_name(_kernel_swi_regs *r, void *pw);
/*
 * This is the handler function you must write to handle the event for
 * which entry_name is the veneer function.
 *
 * Return 0 if you wish to claim the event.
 * Return non-0 if you do not wish to claim the event.
 *
 * 'r' points to a vector of words containing the values of r0-r9 on
 * entry to the veneer. If r is updated, the updated values will be
 * loaded into r0-r9 on return from the handler.
 *
 * pw is the private word pointer ('r12') value with which
 * the event entry veneer is called.
 */

The name to use at installation time is the entry_name (not the name of the handler function). Refer to the RISC OS 3 Programmer's Reference Manual for details and for information on how to install and remove event handlers. As an example, this is the skeleton of an event handler for key presses and mouse clicks:

/* the claim/free functions... */  

#define EventV 16
#define EnableEvent 14
#define DisableEvent 13
#define MouseClick 10
#define Keypress 11  

static void claim_release(int claim, void *pw)
{
  _kernel_swi_regs regs;
  regs.r[0] = EventV;
  regs.r[1] = (int) register_event;
  regs.r[2] = (int) pw;
  _kernel_swi(claim ? OS_Claim : OS_Release,&regs,&regs);
}  

static void add_remove(int add)
{
  _kernel_swi_regs regs;
  regs.r[0] = add ? EnableEvent:DisableEvent;
  regs.r[1] = MouseClick;           /* mouse */
  _kernel_swi(OS_Byte,&regs,&regs);           
  regs.r[1] = Keypress;             /* keyboard */
  _kernel_swi(OS_Byte,&regs,&regs);  
}  

static void claim_free_events(int claim,void *pw)
{
  if (claim) {
    claim_release(1,pw);
    add_remove(1);
  } else {
    add_remove(0);
    claim_release(0,pw);
  }
}  

/* init... */
extern _kernel_oserror *events_init(char *cmd_tail, int podule_base, void *pw)
{
  IGNORE(cmd_tail);
  IGNORE(podule_base);
  claim_free_events(1,pw);
  return NULL;
}  

/* finalise... */
extern _kernel_oserror *events_final (int fatal, int podule, void *pw)
{                                                
  IGNORE(fatal);
  IGNORE(podule);
  /* handle low level events */
  claim_free_events(0,pw);
  return NULL;
}  

/* the handler itself... */
extern int event_handler(_kernel_swi_regs *r,void *pw)
{                                                     
  IGNORE(pw);
  /* switch on the event code */
  switch (r->r[0]) {
  case MouseClick:
  case Keypress:
    break;
  default:
    break;
  }
  return 1;
}

Library initialisation code

CMHG description:

library-initialisation-code: xxxx

The code xxxx is called instead of _clib_initialisemodule. Because the C library has not been initialised at this point, and there is hence no C environment present, xxxx must be written in assembler. It should be a veneer around a call to _clib_initialisemodule.

© 3QD Developments Ltd 2013