mailto: blog -at- heyrick -dot- eu

Navi: Previous entry Display calendar Next entry
Switch to desktop version

FYI! Last read at 09:07 on 2024/11/23.

Writing Relocatable Modules in C

RISC OS has, essentially, two types of software.

The first is the application. This runs in User Mode, may be paged out (if a multitasking application), and is easy created using BASIC, C, Lua, etc.

The other type is the relocatable module. This (mostly) runs in Supervisor Mode (akin to kernel level), rarely interacts directly with the user, and is usually created in assembler or C.

The thing is, certain types of behaviour are not feasible for normal multitasking applications. Let's say we want to be like Windows and make a beep every time a USB device is connected. That's easy. We need to wait on the USB ServiceCall (&D2) for the attach message. This can be done in a regular application with the caveat that you unlink the service call handler prior to calling Wimp_Poll, and relink it afterwards.
Can you spot the flaw? That's right, you will only be able to see and respond to the Service Call whilst you are paged in.
You can't leave the thing set up across polls as the OS will attempt to call the Service Call handler at the address given. Which may be in a random part of some other application. Or it mightn't even be valid memory. At any rate, expect the machine to freeze if you do that.

There is an answer. A module. A piece of code that reacts to external events (via *Commands or SWIs or hooking into system events as described). It lives in the Relocatable Module Area (RMA) which is always mapped in to memory.

What we are going to do here is create a module. The module will be called USBeep and it will offer the following:

The module name, SWI, and *Command have not been registered. If you make a proper module that you plan to distribute to other people, you should register the names with RISC OS Open Ltd.

In this tutorial you should not copy-paste code. The stuff in bold typewriter text with light grey background is the contents of the file. The stuff in normal text (like this) are comments and annotations pertaining to what is going on.
Don't worry, there are downloads below. It's the 21st century, nobody expects you to type out stuff any more!

 

Setting up the environment

In the directory where you keep your projects, create a directory called USBeep.

Inside that directory, create three subdirectories: c, cmhg, and o.

 

CMHG

The most important part of creating a module is a program called CMHG, standing for C Module Header Generator. This creates all of the necessary veneer code to convert the system API into something acceptable to both APCS and C; plus it provides a way to make use of C code without trashing registers (etc) that are not supposed to be trashed.
Consider this the 'gateway' between a module written in C and the rest of the system.

This file is called CMHG.ModHeader.

; USBeep module definitions

; Module title/help
title-string: USBeep
help-string:  USBeep 0.01 © 2015 Rick Murray
There is nothing much to say about that. It is providing the expected module title and the string that will appear if you ask for help on the module. The help string also provides the version and some extra text that comes at the end of the line (the copyright symbol and stuff following).
Note that there is no date. CMHG will insert this when the header is built.

 

; Module start and stop code
initialisation-code: module_initialise
finalisation-code: module_finalise
This defines the function to use for module startup and shutdown. You must provide corresponding functions in your C code.

 

; SWI chunk and handler and list of SWIs [SWI chunk randomly chosen]
swi-chunk-base-number: 0x45600
swi-handler-code: module_swihandler
swi-decoding-table: USBeep,
  Status
This tells CMHG that your SWI chunk will be &45600, the function that you must provide for dealing with this is module_swihandler, and the SWI name prefix is USBeep and one SWI (suffix Status) is defined.

 

; Service call handler for call &D2 so we can notice USB devices
service-call-handler: module_servicecallhandler 0xD2
Here we are telling CMHG that we are responding to Service Call &D2 (so it can generate the appropriate fast-discard case for filtering out unwanted Service Calls (plus the extension block), and it will be handled by a function called module_servicecallhandler.

 

; Veneer handler for Callback
generic-veneers: module_veneer_callback
This defines a generic veneer suitable for passing to OS_AddCallBack (etc).

Something that needs to be explained here is that you must not provide the function module_veneer_callback. This is provided by CMHG itself. So, wait, how do you deal will callbacks then?
Here is how it works. CMHG provides the function module_veneer_callback which you give to SWIs as the address to come to upon callback (event, etc). This small function will set up an environment suitable for the C runtime, and it will then automatically call the function module_veneer_callback_handler. This is the function that you provide. That, with "_handler" suffixed.

 

; *Commands.
command-keyword-table: module_commands
  USBeep_Status(,
     min-args:0,
     max-args:0,
     invalid-syntax: "Syntax:\tUSBeep_Status\n",
     help-text: "Reports how many times this module has beeped.\n")
The commands are handled by a function called module_commands. We define one, USBeep_Status, which takes no parameters, and has some help text.

 

; End
That's all for this file.

Note: If you make modifications to the module source, don't forget to increment the version number here. This will cause the module header to be recreated which will have the pleasant side effect of embedding the correct build date.

 

The MakeFile

The MakeFile is what glues everything together by telling the DDE how to actually build everything. I will provide it here, but I am not going to describe what it does. My DDE is not the latest, so it doesn't have everything (incorrectly, IMHO) tied into the RISC OS build system. That said, nothing out of the ordinary is going on here so it should all "just work".

# Project: USBeep 

# Toolflags:
CCflags      = -c -depend !Depend -zM -I,C: -apcs 3/32bit -Wcd -throwback -fan 
Linkflags    = -m -o $@ 
CMHGflags    = -throwback -32bit
ObjAsmflags  = -APCS 3/32bit -desktop -Depend !Depend -ThrowBack -Stamp -IC:,
Squeezeflags = -f -o $@

# C files used (referred by object file, linked in ORDER)
c_files      = o.module

# Assembler files used
s_files      = 

# How to build the module
@.USBeep:  @.o.ModHeader $(c_files) $(s_files) C:o.stubs 
           link $(linkflags) @.o.ModHeader $(c_files) $(s_files) C:o.stubs 

# How to build the module header
@.o.ModHeader: @.cmhg.ModHeader
           cmhg $(cmhgflags) @.cmhg.ModHeader -o @.o.ModHeader

# A macro for building the C code
.c.o:;       cc $(ccflags) $< -o $@

# A macro for building the assembler code
.s.o:;       objasm $(objasmflags) -from $< -to $@

# Dynamic dependencies:

 

The header file?

There isn't one as this is essentially a short single-source module. If you are creating a module that does more than this, it is recommended to split up the functions according to what they do (for example, my MIDI module contains three sources: module, midi, usb - guess what they do!) and tie the lot together using header files.

 

The module

Okay. Now we get to the fun stuff. Yay! This file is called c.module.

/* USBeep module

   Version  : 0.01

   Date     : Monday, 23rd March 2015

   Copyright: © 2015 Richard Murray
              
*/
Normally I would include a line saying what the purpose of the particular source is, such as "Handles USB transactions" or whatever. I do not write a list of changes into the tops of files. I include a file called Versions in the project directory and all changes are described there by release.
These days, people would probably say to use CVS but the interesting thing is that there seems to be no easy way to extract the CVS version log to tie to directories short of cloning the entire repository and using CVS-aware software...so I'll stick to Versions files, thanks. You can drop them into the zip file and read 'em in a text editor.

 

// Include everything we need
#include "kernel.h"
#include "swis.h"
#include 
#include 
Include all the stuff that we need. This is all part of CLib so there's nothing unexpected here. Oh, and be aware that I use C++ style commenting extensively. That's okay, as the last compiler to not support that was Norcroft C v4 which doesn't work on modern systems.

 

// Some useful definitions
#define UNUSED(x)  (x = x)
#define TRUE  1
#define FALSE 0
The UNUSED macro is to stop the compiler throwing warnings. TRUE and FALSE are used to set the state of a flag.

 

// Register block for SWI calls
_kernel_swi_regs r;
Define a register block so we can call our own SWIs.

 

// Globals
int  connections      = 0; // count of times a USB device has been connected
int  callback_pending = 0; // non-zero if a callback is pending.
void *wsp = NULL;          // Workspace pointer
"Globals" is a bit silly in a one-source project, but you get the point...
Every time a USB device is connected, connections will be incremented.
RISC OS can get a bit stressy if you schedule multiple identical callbacks at the same time, so the callback_pending variable is used as a TRUE / FALSE flag to say whether or not one has already been scheduled. Still, you'd have to be pretty damn good to get in two device insertions between scheduling the callback and it actually happening.

 

// Error block definitions
_kernel_oserror err_nousb = { 0x23400, "This machine doesn't appear to have USB!"};
_kernel_oserror err_noswi = { 0x23401, "Unknown USBeep SWI."};
Again, I remind you, these numbers are completely made up and are not registered with ROOL. If you are making your own module, you should register your own stuff accordingly.

What would need to be registered for this module?

 

// Function prototype for CMHG-provided function
extern void module_veneer_callback(void);
This tells C about the veneer provided by CMHG.

 

Okay, here follows a safety trap that will remove a pending callback. Things can get unpleasant if the callback fires after the module has exit, so best not to let it happen.

// Safety trap
void exit_tidyup(void)
{
   if ( callback_pending )
   {
      r.r[0] = (int)module_veneer_callback;
      r.r[1] = (int)wsp;
      _kernel_swi(OS_RemoveTickerEvent, &r, &r);

      callback_pending = 0;
   }

   return;
}
Notice how we provide the CMHG veneer module_veneer_callback to the SWI, as well as our private copy of the workspace pointer.

 

Now for module startup:

// Module Initialisation
// =====================
int module_initialise(char *cmd_tail, int podule_base, void *pw)
{
   UNUSED(cmd_tail);
   UNUSED(podule_base);

   // Remember work pointer for callback stuff
   wsp = pw;
The parameters on entry are cmd_tail which points to any parameters following the module name (if any). This is so modules can be loaded and given options, though this behaviour is not usual. podule_base gives, I think, the base address of the podule that loaded this module. Probably not particularly relevant on newer systems that don't have podules! Finally pw is a pointer to our private word. We need to keep a copy of this.

 

The next job is to check that we have suitable USB support. Without USB functionality, our module is a bit, well, pointless.

   // Check we have USB support
   // The SWI "USBDriver_Version" should resolve to *something*.
   r.r[1] = (int)"USBDriver_Version";
   if ( _kernel_swi(OS_SWINumberFromString, &r, &r) != NULL )
      return (int)&err_nousb; // return pointer to error block

   // Check we have at least version 0.60 of USBDriver
   _kernel_swi(0x54A45 /* USBDriver_Version */, &r, &r);
   if (r.r[0] < 60)
      return (int)&err_nousb; // return pointer to error block

 

Ensure that the tidyup routine is called at exit.

   // Paranoia!
   atexit(exit_tidyup);
For more accomplished modules, you may want to claim blocks of memory (in the RMA) here, and so on. However, our job is done.
   // That's all for the startup

   return 0; // zero means all is good
}

 

Here is the module shutdown handler. I think fatal flags the difference between *RMReInit and *RMKill. I tend to treat all shutdowns as being fatal... The podule value is no longer relevant. And pw is, as always, our private word.
What we do is call the tidyup function. I think this technically means it will be called twice (once here, once by atexit()), but this is not a problem, the second time will have no effect as any pending callback will have been cleared.

// Module finalisation
// ===================
//
_kernel_oserror *module_finalise(int fatal, int podule, void *pw)
{
   UNUSED(fatal);
   UNUSED(podule);
   UNUSED(pw);

   // Ensure and pending callback is suppressed
   exit_tidyup();

   // That's all.

   return 0;
}

 

Here follows the SWI handler. swi is the SWI number MOD 63 (that means it starts at 0). The register block ir is the registers given to the command. Note the '*' prefix, you must use indirection here - this means the register is ir->r[x] and not the usual form (like ir.r[x]). You can update the register block and anything you modify will be changed prior to the SWI returning. Finally, pw is, as always, the private word.

There isn't much to say here. If the SWI is zero, it is USBeep_Status so we set R0 (updated on SWI exit) to the number of connections. If the SWI is something else, we throw an error.

// SWI handler
// ===========
//
_kernel_oserror *module_swihandler(int swi, _kernel_swi_regs *ir, void *pw)
{
   // SWI handler.

   UNUSED(pw);

   // The  number counts from zero.

   switch (swi)
   {
      case  0: // USBeep_Status
               // IN:  -
               // OUT: R0 = count of beeps made
               ir->r[0] = connections;
               break;

      default: // anything else - throw an error
               return (_kernel_oserror *)&err_noswi;
               break; // not necessary but karmically pleasing
   }

   return NULL;
}

 

Like the SWI handler, the command handler takes a variable command that counts from zero. The array arg_string and value argc provide a list of parameters to the command (if applicable) which works in the same way as the traditional argv/argc pair on entry to the usual main function.

In case you were wondering, there is no main() as this code is reactive, not interactive. It is possible to have a main() in a module, but that's way out of the scope of this tutorial!

The only complication here is looking at the number of USB connections in order to provide a suitable response. One thing that really annoys me is when I see software providing prompts like "You have 1 messages" because it is such a simple thing to deal with. It's a bit long winded here as the grammar changes. A correct 's' suffix? I've done it in assembler in a mere two instructions (a CMP followed by a conditional SWI to output an S). Sorry. Small things like that irritate me. I'm not perfect... ;-)

// *Command handler
// ================
//
_kernel_oserror *module_commands(char *arg_string, int argc, int command, void *pw)
{
   UNUSED(pw);
   UNUSED(argc);
   UNUSED(arg_string);

   // Commands count from zero.

   // *USBeep_Status
   if (command == 0)
   {
      if ( connections == 0 )
         printf("No USB devices have been connected.\n");
      else if ( connections == 1 )
         printf("There has been 1 USB connection.\n");
      else
         printf("There have been %d USB connections.\n", connections);
   }

   return NULL;
}

 

Here is the Service Call handler. We may be called in IRQ mode. RISC OS is threaded. It is really really unsafe to do much of anything in this state - especially you cannot use SWIs that are not re-entrant which precludes the use of OS_WriteC and other VDU/printing calls plus anything to do with FileCore (check the PRMs for other OS SWIs); so what happens here is we simply check to see if this matches up with the conditions we are looking for (Service Call &D2, reason code 0) and if it does the connections value will be incremented and if there is no callback pending, one will be requested. That's about all we can do here.

// ServiceCall handler
// ===================
//
void module_servicecallhandler(int service, _kernel_swi_regs *ir, void *pw)
{
   // Service Call handler
   //
   // NOTE SPECIFIC CONDITIONS OF ENTRY - it is NOT safe to do much of anything,
   // so if something happens that we need to respond to, do the bare minimum that
   // is actually necessary and then throw a callback and do the rest there.
   //
   // RISC OS IS THREADED: ABSOLUTELY DO NOT PRINT TO THE SCREEN OR DO FILE OPS.
   //
   // We hang on to this Service Call:
   //
   //   &D2 Service_USBDriver
   //       R0 = Reason (see below)
   //       R1 = &D2
   //       R2 ...
   //
   //       Reason = R0 = 0 = New device attached to the USB system.
   //                         R2 is a pointer to USB device descriptor block.
   //                     --> This is what we are looking for. <--
   //                R0 = 1 = List connected devices (see usb_findmidi() ).
   //                         R2 is a pointer to linked list of descriptor blocks.
   //                R0 = 2 = Unused
   //                R0 = 3 = USB system is (re)starting.
   //                R0 = 4 = USB system is dying.

   UNUSED(pw);

   if (service == 0xD2)
   {
      // USB driver
      if (ir->r[0] == 0)
      {
         // New device connected, time to do something!
         connections++; // we actually count connections, not beeps

         // The beep is a VDU operation, so we'll do this on a callback
         if ( !callback_pending )
         {
            r.r[0] = (int)module_veneer_callback;
            r.r[1] = (int)wsp;
            _kernel_swi(OS_AddCallBack, &r, &r);
            callback_pending = TRUE;
         }
      }
   }

   return;
}

 

And finally... the callback handler. RISC OS does not provide a guaranteed time for when the callback will happen. It will take place when RISC OS is no longer threaded - that means it would return to User Mode with the SVC stack empty or in some cases when the system is idling. We are called in SVC mode as we're a module. All we do is issue a beep the old-school (VDU7) way.

// Callback handler
// ================
//
_kernel_oserror *module_veneer_callback_handler(_kernel_swi_regs *ir, void *pw)
{
   // RISC OS is not threaded, so we can do stuff here.

   UNUSED(pw);
   UNUSED(ir);

   // Just make a beep
   _kernel_oswrch(7); // VDU 7

   callback_pending = FALSE;

   return NULL;
}

 

For extra credit: My original code here did the following and it made a beep and then stiffed the machine. Why did this happen?

   // Just make a beep
   __asm
   {
      SWI  256+7
   }

 

That's all folks!

Surprised to be here so soon? There is no real magic to writing a module in C. Just a bunch of quirks due to the C runtime using a totally different API and calling convention to the rest of RISC OS (which, pretty much, doesn't have a calling convention beyond "that which is needed by the SWIs"!).

I have demonstrated SWIs, commands, Service Calls, and using callbacks. I hope I have covered most of the important stuff in a module that does practically nothing. But, hey, it is supposed to be a tutorial to show how all this stuff fits together.

I hope this has been helpful, and if it has, please go to the comments below and let me know what you've done with this.

 

Questions?

You can ask below, or on the ROOL forums. I check there more than I check my own email...but then I'm a sad git and people don't email me that often. ;-)

 

Downloads!

 

 

Your comments:

No comments yet...

Add a comment (v0.11) [help?]
Your name:

 
Your email (optional):

 
Validation:
Please type 28061 backwards.

 
Your comment:

 

Navi: Previous entry Display calendar Next entry
Switch to desktop version

Search:

See the rest of HeyRick :-)