Last time we looked at interrupts, and how they can be triggered in hardware. 
Now we're going to deal with their software side - since without software,
hardware is useless.


Interrupt control


We explained last time how to build an interrupt control circuit, which can
be used to enable or disable interrupts on a particular podule.  When
interrupts are used, it's also necessary to enable interrupts for the podule
bus as a whole to ensure that the ARM will notice an interrupting podule. 
Since the means of doing this varies between machines (in particular on
Iyonix) we must use the RISC OS 5 Hardware Abstraction Layer (HAL).


The Hardware Abstraction Layer


Before anyone starts to panic and complain that they don't have RISC OS 5,
the HAL26 module provides the same interface for RISC OS 3 and 4 machines. 
So by loading this module it's possible to write code that will work on all
podule-supporting machines from RISC OS 3.1 upwards.  HAL26 can be found at
http://www.markettos.org.uk/riscos/podules/

The HAL provides means to access particular low-level hardware functions in
the machine, without knowing precisely which chip is implementing them.  This
allows the designers of a new machine to provide the same functionality by
using a different chip and just rewriting the HAL - as far as any software
above the HAL is concerned, the functions are unchanged.

Before we dive into how to use the HAL, let's explore the functions the HAL
provides to give a better idea of what it does.  For the sake of space I'll
only mention the interrupt-related HAL functions here, although others are
listed in the documentation if you are interested.


HAL functions


Call 1: int HAL_IRQEnable(int device)
Enables the IRQ source number 'device' (see later for what the device number
means).  Non-zero is returned if it was previously enabled.  After this call
the ARM will receive interrupts from this device if they are triggered.

Call 2: int HAL_IRQDisable(int device)
Disables the IRQ source number 'device', returning non-zero if the source was
previously enabled.  After this call, interrupts on this device will be
ignored by the ARM.

Call 3: void HAL_IRQClear(int device)
If the interrupt controller has latched an IRQ for 'device', we must clear it
with this call when we have dealt with it.  This is mainly of concern to the
system's timer interrupts who require clearing in this way - podule
interrupts cannot be cleared in this manner.

Call 4: int HAL_IRQSource(void)
If IRQ(s) are being asserted, return the number of the highest priority
asserted IRQ.  If not, return -1 to say no IRQs are asserted.

Call 5: int HAL_IRQStatus(int device)
Check whether the specified device is requesting an interrupt, and return
non-zero if so.  This is returned even if the IRQ is disabled by
HAL_IRQDisable.

Call 6: int HAL_FIQEnable(int device)
As HAL_IRQEnable but controlling FIQs (note the device number for FIQs has a
different meaning).

Call 7: int HAL_FIQDisable(int device)
As HAL_IRQDisable but for FIQs.

Call 8: void HAL_FIQDisableAll(void)
Disable all FIQ sources together.

Call 9: void HAL_FIQClear(int device)
Clear the specified FIQ, in the same manner as HAL_IRQClear.

Call 10: int HAL_FIQSource(void)
As HAL_IRQSource but for FIQs.

Call 11: int HAL_FIQStatus(int device)
As HAL_IRQStatus but for FIQs.


How to call the HAL


Now you're probably wondering how to call these C functions, especially if
you don't or can't program in C.  Never fear, they can easily be called from
BASIC or assembler instead.

If you are using C however, OSLib provides a means to call them directly,
albeit with slightly modified names - see hal.h in OSLib for more details.

From other languages, the SWI OS_Hardware can be used to call them, or
determine a pointer to these functions.  HAL26 provides OS_Hardware, but
doesn't provide the SWI name, so to be compatible across all RISC OS machines
we must call it using its number, &7A.  Typically we would use from BASIC:

OS_Hardware = &7A
SYS OS_Hardware,   <parameters here>

(notice no quotes in the SYS command).

The simplest way to call a HAL function is via OS_Hardware reason code 0.  
The interface is as follows:

SWI OS_Hardware 0 (SWI &7A)
On entry: R0-R7 parameters for hardware routine
          R8 = 0
          R9 = hardware call number
On exit:  R0-R3 updated by call
          R4-R9 preserved

So for example, to read the IRQ status of device 13, we would call
HAL_IRQStatus (call number 5) as follows:

REM perform:  status = HAL_IRQStatus(13);
OS_Hardware = &7A
SYS OS_Hardware,13,,,,,,,,0,5 TO status%
IF status% PRINT "Device 13 is interrupting"

The parameters supplied in R0-R7 are those specified by the C function
outlined above.  The first parameter to the C function should go in R0, the
second in R1, and so on up to the eighth in R7.  The routine can return up to
4 results, which are placed in R0 to R3.  Note that R0-R3 will be corrupted
even if results are not returned in them.

This is all very well, but often when dealing with a hardware device we want
to perform an action as quickly as possible, and would rather not have the
overhead of calling a SWI.  OS_Hardware 1 provides for this eventuality by
giving us the address of a specified HAL routine:

SWI OS_Hardware 1 (SWI &7A)
On entry: R8 = 1
          R9 = hardware call number
On exit:  R0 = routine address
          R1 = static base value for routine

The static base for the routine returned in R1 must be supplied in R9 when
the routine is called.  The routine must be called in a privileged mode,
meaning supervisor or interrupt mode - and so cannot be called directly from
BASIC (use OS_Hardware 0 is this case).

HAL routines follow the ARM/Thumb Procedure Call Standard (ATPCS), meaning
that there are certain conventions for the use of registers:

r0-r3 (also known as a1-a4) contain the first four parameters to supply to
the routine.  Any further parameters are supplied on the stack.

r4-r8 and r10-r11 (or v1-v5, v7-v8) are preserved across calls to this
function.

r9 (sb) holds the static base, as above, which is preserved across calls.

r12 (ip) is a scratch register, corrupted across calls.
r13 (sp) is the stack pointer, which must be valid (preserved).
r14 (lr) is the link register, which must hold a valid return address.

Typically we would store the routine address and static base somewhere
safe, then when we wish to call this routine load the static base into R9,
set up a suitable return address in R14 and LDR PC to the routine address.


IRQ devices


Now we know how to control interrupts, we need to know what we are
controlling.  Pre-Risc PC machines contain 16 IRQ devices, whose numbers
correspond to the device numbers supplied to HAL functions.  They can be seen
in table 1. Device 13 is the interrupt triggered by the /PIRQ pin on the
podule bus, and for building podules is the only one to concern us.  Risc PCs
and ARM7500 machines have similar device numbers with some additions, but the
majority of devices (including the podule device) retain the same number. 
Iyonix has a somewhat different device mapping, but the podule interrupt
remains at device 13.



Interrupt service routines


Now we know how to enable our interrupt, we'd like to be able to run some
code when it occurs.  This requires the use of OS_ClaimDeviceVector.

SWI OS_ClaimDeviceVector (&4B)
On entry: R0 = device number
          R1 = address of device driver routine
          R2 = value to pass in R12 to driver routine
          R3 = address of interrupt status (if podule interrupt ie R0 = 8 or 13)
          R4 = interrupt status mask (if podule interrupt)
On exit:  R0-R4 preserved

This arranges to call the routine pointed to by R1 when the interrupt occurs. 
The values in R3 and R4 correspond to the interrupt status register and
status mask for our podule as discussed last time - except that here the
address of the interrupt status register for our podule must be absolute,
taking into account the slot it is in and adding on the base for whichever
speed of access we require.

After calling OS_ClaimDeviceVector, we must enable our interrupt so the
hardware can trigger it.  This is done using the HAL as discussed above.

When we've finished with our interrupt service routine we must remove it from
the vector.  This is done with SWI OS_ReleaseDeviceVector (&4C), which is
called with the same parameters as OS_ClaimDeviceVector - they must be the
same as the previous call to it to correctly remove the service routine. 
Once removed, it is important *not* to disable the interrupt since other
users might also need it.  The OS keeps track of users, and will disable it
if everyone has finished with it.

The routine itself will be called in interrupt mode.  This means:

  It must be situated in an area of memory that is always mapped in -
commonly the module area.  If you try to put it in an application's workspace
it is possible when multitasking that another application is swapped into the
same place in memory - meaning arbitrary data will be called, probably
resulting in a crash.

  It needs to run quickly, otherwise data such as keystrokes or network
packets might be missed as no other IRQs can be dealt with whilst in
interrupt mode.

  Be careful when calling SWIs - only call those marked as reentrant.  If not
you might end up being in the middle of executing a SWI, when your interrupt
happens and your routine calls the same SWI again.  Once returned from
interrupt mode, the second call to the SWI will have trampled all over the
data the first call was working with, probably causing a crash in many
interesting ways.  Also make sure you preserve R14_svc over calls to SWIs
(this may mean changing the processor into SVC mode to store R14, then
dropping it back into IRQ mode).

Return with the instruction MOV pc,r14.  You may corrupt registers R0-R3 and
R12.

To round up all these explanations, on the monthly disc or at
http://www.markettos.org.uk/riscos/podules/ you can find an example of a
module which just counts the number of interrupts it gets - this should
demonstrate how to use many of these features.

That's it for now.  Next time we'll now at the Risc PC extensions - it should be EASI!
