ARM code for beginners

Part 7 — Reacting to system events




    This month, I`ve moved onto a more complex example of a module, called NoiseBox.  Its job is to watch for system events happening such as clicks on pop-up menus, or the desktop shutting down, and to play sound files to accompany these events.

    The module watches for these events in two ways; read these two explanations, then have a look at the information box to see which events NoiseBox traps.

Filtering

    RISC OS provides a mechanism for trapping the Wimp_Poll SWI, by use of the FilterManager module.  This module provides four SWIs to register and deregister pieces of 'filter' code, which can intercept the SWI before it is called, or before it returns to the caller.  To find out what events are happening on the desktop, we install a 'post-filter' on every occurrence of the Wimp_Poll SWI, so that when the WIMP returns to any task in the desktop with an event, our filter code gets to look at it first.  Each filter can be installed with a mask on it, so that the filter code only gets called when particular Wimp_Poll result codes are being returned (in our case, we are only interested in 9, Menu clicks and 17-19, WIMP messages).

Service calls

    Outside of the desktop, RISC OS provides modules with service calls.  Each module can have a service call entry point, which comes after the initialisation and finalisation code in the header.  Certain events under RISC OS generate these service calls, and each has a number.  For instance, the screen mode changing is service call &46.  When any service call is being issued, each module`s service call handler is entered with R1 containing the service number.  A module can 'claim' a service call by setting R1 to zero on exit, which means that the service call will not get passed to any further modules.  Some service calls are only there 'for your information' so that modules can prepare themselves for something (e.g. a system reset); others expect to be claimed, like the desktop welcome banner.  In this example, if the service call is claimed, the OS expects the module to draw its own welcome banner.  If the call isn`t claimed, the OS draws its own.
 
 
Events trapped by NoiseBox

It should be noted that the names given here are a contrivance for the benefit NoiseBox; sometimes, by co-incidence, they are also the 'official' names of particular service calls or WIMP messages.

DesktopStart (service call &7C)

This service call is issued by the OS when it is about to draw its welcome banner.

ShutDown (service call &80)

This service call marks a successful shutdown of the machine, when the 'Restart' button is waiting on the screen.

WimpErrorBox (service call &400C0)

The WIMP issues this service call when it is about to display an error box; this only happens under RISC OS 3.5 or later, but Andrew Clover`s 3DErrorWindow module will add this service call to computers running 3.1.

ScreenBlank / ScreenRestore (&7A / &7B)

These are issued when the OS blanks or restores the screen.

WimpSave (Wimp_Poll returns with R0=17-R8, R1+16 points to 1)

This is a WIMP message, which can be trapped with by our filter.  With WIMP messages, Wimp_Poll returns 17, 18 or 19 in R0 to show a WIMP message, and the address pointed to by R1+16 should contain the message number.  The DataSave message is 1, and implies that the user has dragged a save box somewhere.

ModeChange (service call &46)

Issued before a mode change

WimpInitialise (WIMP message &400C2)

The TaskInitialise message is sent round by the Task Manager when a new task calls Wimp_Initialise.

WimpCloseDown (WIMP message &400C3)

The opposite.

WimpMenuSelect (WIMP event 9)

The WIMP returns this event to a task (this isn`t a message; see the Wimp_Poll documentation in StrongHelp if you‘re not sure) when the user has selected an item from a pop-up menu.
 

Module workspace

    If your module is going to need to reserve lots of memory, it becomes impractical to store great chunks of zeros inside the module image to keep data in.  What the OS does for each module is to allocated it one word of private workspace in RAM when it initialises, and sets this to zero.  It expects modules requiring workspace to claim their workspace and store their pointer to it in this private word; a pointer to this word is passed to all module entry points in R12.  Hence a lot of entry points in our module start with LDR R12,[R12], which loads the 'real' workspace pointer out of our private word.  The workspace pointer is also passed to the FilterManager when setting up our filters, so that any routines which aren‘t part of the module header can still be entered with the correct private word.

    OS_Module is a general purpose SWI for manipulating modules, but its use here is to claim and release chunks of RAM from the module area.  To claim, we pass the reason code 6 in R0 (claim), followed by the amount of memory we want in R3.  The call returns a memory pointer in R2.  Then to release this, we call it with R0=7 and R2 pointing to the memory to release.  Thus the initialisation code and finalisation code are used to claim and release our workspace; in fact, releasing is done automatically by the OS if you leave your workspace pointer in R12, so we don‘t need to release the memory explicitly.

    Our workspace is going to be used to store the filenames of the sound samples associated with each event.  It has a simple structure; each event has a maximum data length associated with it (11 characters), and the sound filename for each event can be found by adding (event_number * event_length) to the workspace pointer.

Callbacks

    When an event occurs, signalled by a service call or WIMP message, RISC OS can be involved in an icky, processor-intensive task such as accessing hardware.  What *not* to do in these situations is to call a (relatively) slow SWI which could affect critical operations.  The thing to do instead is to use a callback˜ this is a routine which RISC OS is told to call when the ARM chip is back in user mode with interrupt requests enabled, implying that it is not doing anything critical.  Hence when an event occurs, we tell RISC OS to /call back/ our sound sample playing code.  Typically this will be a fraction of a fraction of a second later and the user won‘t notice, but if you try to call the PlaySample_Open SWI (which would open a file from disc) while RISC OS is trying to access hardware, it would probably cause crashes.
 
 
Two SWIs used this month

The PlaySample SWIs are documented inside the !NoiseBox.Docs folder on the cover disc.  OS_ReadArgs is a complicated SWI; the StrongHelp manual gives a good description of its use.

SWI Filter_RegisterPostFilter (DeRegisterPostFilter)

R0 > filter title
R1 > filter code
R2 = value to pass to filter code in R12
R3 = task handle to apply filter to (or zero for all)
R4 = event mask (one bit per WIMP event)
Registers (or removes) a filter to be called after WIMP_Poll has been called by a task, but before the SWI returns to the caller.  Similarly, PreFilter code is called before the SWI is executed by the WIMP.

SWI OS_AddCallBack (RemoveCallBack)

R0 > callback code
R1 = value to pass in R12 to callback code
This tells RISC OS to call a routine when it is not engaged in some critical activity.  OS_RemoveCallBack works with the same parameters to remove a callback routine.
 

Macro assembly

    An oddity you`ll notice about the source code is the inclusions of functions which are masquerading as instructions.  These are examples of macros: small pieces of code which are expanded when the code is assembled, like a shorthand.

    FNinsert_binary is used to insert the help text of the *NoiseAssign command into the module; this allows us to edit the help text as a separate file in Zap, and have it included in our code without using messy EQUS statements.  It uses OS_File to load the file in at O% (the assembly address in memory), and then advances both P% and O% by the file length, so that further instructions are assembled after this code.  FNfill_space leaves a gap in our assembly, to store some scratch space.  FNadrl is a potentially more useful macro which assembles a long ADR instruction.  ADR is limited because it uses immediate constants (see article 3 for the exact limitations involved), so this macro assembles two instructions to give a longer reach to the ADR command.  It shouldn`t be used unless assembling a normal ADR directive fails.

    It`s also worth noting how things like the amount of workspace the module is assembled to claim depends on two variables at the top of the listing.  Judicious use of instructions like MOV R0,#(var1*var2) along with macros can make code significantly easier to modify and re-assemble; so you don`t have to change reams of lines in a similar fashion.  Don`t forget, though, that these constants will be fixed at assembly time and won‘t alter once you‘ve saved the code out.

    More ARM coding frolics next month.
 

SampConv

The PlaySample module (which plays sound samples directly from disc) used this month is part of a larger program called !SampConv, by Rick Hudson.  It‘s available on the Internet from ftp://micros.hensa.ac.uk/micros/arch/riscos/d/d044/ or from the DataFile PD library.