Hardware Abstraction Layer
Document version 0.10, December 2002
A simple HAL has been inserted underneath RISC OS. This provides two
functions. Firstly, it is responsible for initial system bootstrap, much like
a PC BIOS, and secondly it provides simple APIs to allow hardware access.
The HAL APIs are a thin veneer on top of the hardware. They are designed to
act as replacements for all the hardware knowledge and manipulation performed
by the RISC OS Kernel, together with some APIs that will allow RISC OS driver
modules to become more hardware independent.
One tricky design decision is the amount of abstraction to aim for. Too
little, and the system is not flexible enough; too much and HAL design is
needlessly complicated for simple hardware. The present design tries to err
on the side of too little abstraction. Extra, more abstract APIs can always
be added later.
The bulk of device driver implementation remains in RISC OS modules - the
difference is that the HAL will allow many device drivers to avoid direct
access to hardware. For example, PS2Driver can now use HAL calls to send and
receive bytes through the PS/2 ports, and thus is no longer tied to IOMD's
PS/2 hardware. Similarly, interrupt masking and unmasking, as performed by
any device vector claimant, is now a HAL call. Note that HAL calls are
normally performed via a Kernel SWI - alternatively the Kernel can return
the address of specific HAL routines. There is nothing to stop specific
drivers talking to hardware directly, as long as they accept that this will
tie them to specific devices.
This dividing line between the HAL and RISC OS driver modules is crucial. If
the HAL does everything, then we have achieved nothing - we have just as
much hardware dependent code - it's just in a different place. It is
important to place the dividing line as close to the hardware as possible,
to make it easy to design a HAL and to prevent large amounts of code
duplication between HALs for different platforms.
RISC OS and the HAL are two separate entities, potentially linked separately.
The HAL is defined with a set of callable routines for the OS/HAL interface.
Each HAL entry is given a unique (arbitrary) number, starting at 0.
Every entry (up to the declared maximum) must exist. If not implemented, a
failure response must be returned, or the call ignored, as appropriate. Note
that the OS interface for the HAL should not be confused with standard OS
calls (SWIs) already defined for use in the OS itself.
To permit high-level language use in the future, the procedure call standard
in both directions is the ARM-Thumb Procedure Call Standard (ATPCS) as
defined by ARM, with no use of floating point, no stack limit checking,
no frame pointers, and no Thumb interworking. HAL code is expected to
be ROPI and RWPI (i.e. all its read-only segments and read-write segments
are position-independent). Hence the HAL is called with its static workspace
base (sb) in r9. The OS kernel is neither ROPI nor RWPI (except for the
pre-MMU calls, which are ROPI).
The HAL will always be called in a privileged mode - if called in an
interrupt mode, the corresponding interrupts will be disabled. The HAL
should not change mode. HAL code should work in both 26-bit and 32-bit modes
(but should assume 32-bit configuration).
Routines can be conveniently specified in C language syntax. Typically they
will be written in assembler. In detail, the ATPCS register usage for HAL
calls is as follows:
| ATPCS | | ARM | | Use | | At exit
| | a1 | | r0 | | argument 1/return value | | undefined or return value
| | a2 | | r1 | | argument 2/return value | | undefined or return value
| | a3 | | r2 | | argument 3/return value | | undefined or return value
| | a4 | | r3 | | argument 4/return value | | undefined or return value
| | v1 | | r4 | | var 1 | | preserved
| | v2 | | r5 | | var 2 | | preserved
| | v3 | | r6 | | var 3 | | preserved
| | v4 | | r7 | | var 4 | | preserved
| | v5 | | r8 | | var 5 | | preserved
| | sb | | r9 | | static workspace base | | preserved
| | v7 | | r10 | | var 7 | | preserved
| | v8 | | r11 | | var 8 | | preserved
| | ip | | r12 | | scratch | | undefined
| | sp | | r13 | | stack pointer | | preserved
| | lr | | r14 | | return link | | undefined
|
The static workspace base points to the HAL workspace.
Note that HAL calls must be assumed to corrupt all of r0-r3,r12,r14. A
function return value may be in r0, or (less commonly) multiple return
words in two or more of r0-r3.
If there are more than 4 arguments to a HAL call, arguments 5 onwards must
be pushed onto the stack before the call, and discarded after return.
(The order of arguments is with argument 5 at top of stack, i.e. first
to be pulled.)
When using assembler, the register usage may seem somewhat restricted, and
cumbersome for more than 4 arguments. However, it is typically a reasonable
balance for function calls (as a PCS would aim to be), and does not preclude
implementation in C for example. Old code may require register preserving
overhead to insert HAL calls easily, but for most calls this is
insignificant, compared to hardware access costs.
| On entry: | | R0-R7 parameters
| | | R8 = reason code (bits 0-7) and flags (bits 8-31)
| | | R9 = hardware call number
| | On exit: | | depends on flags
|
This SWI provides access to the hardware layer. Conceptually, it is
similar to accessing the hardware registers directly in earlier versions
of RISC OS - wherever possible OS routines should be used in preference.
This call is primarily designed for the use of device drivers - for example
the PS2Driver module makes PS2 hardware calls using this interface.
Making hardware calls to devices normally managed by the Kernel is liable to
cause the same problems as poking the hardware. However, making hardware
calls is of course preferable to actually accessing the hardware directly.
Use this interface with caution.
| On entry: | | R0-R7 parameters for hardware routine
| | | R8 = 0
| | | R9 = hardware call number
| | On exit: | | R0-R3 updated by call
| | | R4-R9 preserved.
|
This SWI calls a HAL routine. HAL routines internally are ATPCS, so R0-R3 are
passed in as a1-a4, and R4-R7 are pushed on to the stack. The a1-a4 on exit
from the routine are passed back in R0-R3.
If the HAL routine is not available, an error is returned. Actual HAL
routines do not return RISC OS errors - any possible failure will be
indicated in a call-specific manner.
| On entry: | | R8 = 1
| | | R9 = hardware call number
| | On exit: | | R0 = routine address
| | | R1 = static base value for routine
|
This call looks up the address of a HAL routine. If it does not exist, an
error is returned. Otherwise, the address of the routine is returned in R0.
Calls made to the routine should be in a privileged mode, with R9 (sb) set to
the static base value returned by this SWI. Refer to the HAL documentation
for more details of calling conditions.
This is a summary of some of the HAL routines. They should be used (via
the mechanisms described above) in places where programmers would have
previously read and written IOC/IOMD directly. OS calls should always
be used in preference to HAL calls, if available.
The HAL provides the ability to identify, prioritise and mask IRQs and FIQs.
RISC OS supplies the ARM's processor vectors, and on an IRQ calls the HAL to
request the identity of the highest priority interrupt.
IRQ and FIQ device numbers are arbitrary and independent, varying from system
to system. They should be arranged to allow quick mappings to and from
hardware registers, and should ideally be packed, starting at 0.
Note that some interrupt lines (eg for Acorn Expansion Cards and PCI
Expansion Cards) may be shared. Such lines should not be disabled when
a device driver is removed - see the PRM for details.
int HAL_IRQEnable(int device) [ entry no 1 ]
Unmasks the specified IRQ source. Returns non-zero if the IRQ source
was previously enabled.
int HAL_IRQDisable(int device) [ entry no 2 ]
Masks the specified IRQ source. Returns non-zero if the IRQ source
was previously enabled.
void HAL_IRQClear(int device) [ entry no 3 ]
Clears the specified IRQ source, if it is latched in the interrupt
controller, rather than the device. For certain low-level devices,
such as the timer, the interrupt must be cleared with this call,
however the hardware is implemented.
int HAL_IRQSource(void) [ entry no 4 ]
Returns the number of the highest priority asserted IRQ, or -1 if
no IRQ is asserted.
int HAL_IRQStatus(int device) [ entry no 5 ]
Returns non-zero if the specified device is requesting an interrupt
(independent of mask status).
int HAL_FIQEnable(int device) [ entry no 6 ]
Unmasks the specified FIQ source. Returns non-zero if the FIQ source
was previously enabled.
int HAL_FIQDisable(int device) [ entry no 7 ]
Masks the specified FIQ source. Returns non-zero if the FIQ source
was previously enabled.
void HAL_FIQDisableAll(void) [ entry no 8 ]
Masks all FIQ sources.
void HAL_FIQClear(int device) [ entry no 9 ]
Clears the specified FIQ source, if it is latched in the interrupt
controller, rather than the device.
int HAL_FIQSource(void) [ entry no 10 ]
Returns the number of the highest priority asserted FIQ, or -1 if
no FIQ is asserted.
int HAL_FIQStatus(int device) [ entry no 11 ]
Returns non-zero if the specified device is requesting an interrupt
(independent of mask status).
The HAL must supply at least one timer capable of generating periodic
interrupts. Each timer should generate a separate logical interrupt, and the
interrupt must be latched. The timers must either be variable rate (period is
a multiple of a basic granularity), or be fixed rate (period = granularity).
Optionally, the timer should be capable of reporting the time until the
next interrupt, in units of the granularity.
int HAL_Timers(void) [ entry no 12 ]
Returns number of timers. Timers are numbered from 0 upwards. Timer 0
must exist. RISC OS uses timer 0 for its centisecond clock - other timers
(if available) may be used by other applications, as in IOC/IOMD
versions of the OS.
int HAL_TimerDevice(int timer) [ entry no 13 ]
Returns IRQ device number of timer n. The timer will generate an interrupt
on this device when it wraps. The interrupt should be cleared with
HAL_IRQClear.
unsigned int HAL_TimerGranularity(int timer) [ entry no 14 ]
Returns basic granularity of timer n in ticks per second.
unsigned int HAL_TimerMaxPeriod(int timer) [ entry no 15 ]
Returns maximum period of the timer, in units of Granularity. Will be 1
for a fixed rate timer.
void HAL_TimerSetPeriod(int timer, unsigned int period) [ entry no 16 ]
Sets period of timer n. If period > 0, the timer will generate interrupts
every (period / granularity) seconds. If period = 0, the timer may be
stopped. This may not be possible on some hardware, so the corresponding
interrupt should be masked in addition to calling this function with
period 0. If period > maxperiod, behaviour is undefined.
unsigned int HAL_TimerPeriod(int timer) [ entry no 17 ]
Reads period of timer n. This should be the actual period in use by the
hardware, so if for example period 0 was requested and impossible, the
actual current period should be reported.
unsigned int HAL_TimerReadCountdown(int timer) [ entry no 18 ]
Returns the time until the next interrupt in units of granularity, rounded
down. If not available, 0 is returned.
The HAL must supply a counter that varies rapidly, appropriate for use for
sub-millisecond timing. On many systems, this counter will form part of
timer 0 - as such it is not required to operate when timer 0 is not running.
On other systems, the periodic timers may have no readable latch, and a
separate unit will be required.
The counter should count down from (period-1) to 0 continuously.
unsigned int HAL_CounterRate(void) [ entry no 19 ]
Returns the rate of the counter in ticks per second. Typically will
equal HAL_TimerGranularity(0).
unsigned int HAL_CounterPeriod(void) [ entry no 20 ]
Returns the period of the counter, in ticks. Typically will equal
HAL_TimerPeriod(0).
unsigned int HAL_CounterRead(void) [ entry no 21 ]
Reads the current counter value. Typically will equal
HAL_TimerReadCountdown(0).
void HAL_CounterDelay(unsigned int microseconds) [ entry no 22 ]
Delay for at least the specified number of microseconds.
 |
| © 2006 IYONIX Ltd |
32-bit RISC OS |
|