RISC OS and the "HAL" ===================== RISC OS currently is tied to the IOMD20 and VIDC20 peripheral set, descendents of the original IOC, MEMC and VIDC devices designed in parallel with the original ARM. These devices provide a close fit with RISC OS, and their functionality is well suited to general purpose and embedded systems, but the continuing drive to reduce cost requires us to support other peripheral sets on off-the-shelf ARM system on chips. First targets for support are L7205/L7210 for Customer L and CL92xx (the new ARM920T based devices) for Customer A. Enclosed are a summary of their advantages and disadvantages over the ARM7500FE for our Information Appliance designs. L7205 CL92xx + Faster (50% or so) + Faster (400%+) + SDRAM support + SDRAM support + USB + USB + EIDE interface + Lots of GPIO - No hardware cursor - No floating point - Incompatible floating point - No video DACs - No video DACs - No PS/2 - No PS/2 - Bizarre MS-Windows video system To support these devices, and others in the future, a simple HAL is to be inserted underneath RISC OS. This will provide two functions. Firstly, it will be responsible for initial system bootstrap, much like a PC BIOS, and secondly it will provide 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. No attempt will be made (at this stage) to perform such tasks as separating the video drivers from the Kernel, for example. 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. So, initially, for example, the serial device API will just provide discovery, some capability flags and the base address of the UART register set. This will be sufficient for the vast majority of devices. If new hardware comes along later that isn't UART compatible, a new API can be defined. Simple hardware can continue to just report UART base addresses. The bulk of device driver implementation remains in RISC OS modules - the difference is that the HAL will allow many device drivers to no longer directly access 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 Kernel 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. The Kernel remains responsible for the ARM's MMU and all other aspects of the CPU core. The HAL requires no knowledge of details of ARM implementations, and thus any HAL implementation should work on any processor from the ARM610 to the ARM940T or XScale. OS independence =============== Notionally, the HAL implementation is OS independent. It makes no assumptions about the virtual memory map of the OS, and only uses the defined HAL->OS entries. The HAL should not call RISC OS SWIs. In practice, however, the HALs are unlikely to be used on anything other than RISC OS, and many HALs are likely to be written. This makes it sensible to place as much intelligence as possible within RISC OS itself, to prevent duplicated effort. Calling standards ================= RISC OS and the HAL are two separate entities, potentially linked separately. Thus some simple dynamic linking is required. This occurs via a hybrid of the RISC OS module header and Shared C Library stubs. Each RISC OS/HAL entry is given a unique (arbitrary) number, starting at 0. The offset to each entry is given in an entry table. Calls can be made manually through this table, or stubs could be created at run-time to allow high-level language calls. Every entry (up to the declared maximum) must exist. If not implemented, a failure response must be returned, or the call ignored, as appropriate. To permit high-level language use in the future, the procedure call standard in both directions is ATPCS, 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 (hence it is called with its static workspace base in sb). 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). Header formats ============== The OS is linked to run at a particular base address. At present, the address will be at MB + 64KB. This allows a HAL of up to 64K to be placed at the bottom of a ROM below the OS, and the whole thing to be section-mapped. However, if a different arrangement is used, the system will still work (albeit slightly less efficiently). The OS starts with a magic word - this aids probing and location of images. Following that is a defined header format: Word 0: Magic word ("OSIm" - &6D49534F) Word 1: Flags (0) Word 2: Image size Word 3: Offset from base to entry table Word 4: Number of entries available The HAL itself may have whatever header is required to start the system. For example on ARM7500 16->32 bit switch code is required, and on the 9500 parts a special ROM header and checksum must be present. Instead of a header, a pointer to the HAL descriptor is passed to the OS in the OS_Start call: Word 0: Flags bit 0 => uncachable workspace (32K) required bits 1-31 reserved Word 1: Offset from descriptor to start of HAL (will be <= 0) Word 2: HAL size Word 3: Offset from descriptor to entry table Word 4: Number of entries available Word 5: Static workspace required Each of the HAL and the OS must be contiguous within physical memory. RISC OS entry points from HAL init ================================== Entry 0: void RISCOS_InitARM(unsigned int flags) flags: reserved - sbz On entry: SVC mode MMU and caches off IRQs and FIQs disabled No RAM or stack used On exit: Instruction cache may be on Usage: This routine must be called once very early on in the HAL start-up, to accelerate the CPU for the rest of HAL initialisation. Typically, it will just enable the instruction cache (if possible on the ARM in use), and ensure that the processor is in 32-bit configuration and mode. Some architecture 4 (and later) ARMs have bits in the control register that affect the hardware layer - eg the iA and nF bits in the ARM920T. These are the HAL's responsibility - the OS will not touch them. Conversely, the HAL should not touch the cache, MMU and core configuration bits (currently bits 0-14). On architecture 3, the control register is write only - the OS will set bits 11-31 to zero. Likewise, such things as the StrongARM 110's register 15 (Test, Clock and Idle Control) are the HAL's responsibility. The OS does not know about the configuration of the system, so cannot program such registers. This entry may not be called after RISCOS_Start. Entry 1: void *RISCOS_AddRAM(unsigned int flags, void *start, void *end, uintptr_t sigbits, void *ref) flags bit 0: video memory (only first contiguous range will be used) bits 8-11: speed indicator (arbitrary, higher => faster) other bits reserved (SBZ) start start address of RAM (inclusive) (no alignment requirements) end end address of RAM (exclusive) (no alignment requirements, but must be >= start) sigbits significant address bit mask (1 => this bit of addr decoded, 0 => this bit ignored) ref reference handle (NULL for first call) Returns ref for next call On entry: SVC32 mode MMU and data cache off IRQs and FIQs disabled Other notes: This entry point must be the first call from the HAL to RISC OS following a hardware reset. It may be called as many times as necessary to give all enumerate RAM that is available for general purpose use. It should only be called to declare video memory if the video memory may be used as normal RAM when in small video modes. To permit software resets: The HAL must be non-destructive of any declared RAM outside the first 4K of the first block. The stack pointer should be initialised 4K into the first block, or in some non- declared RAM. Must present memory in a fixed order on any given system. Current limitations: The first block must be at least 256K and 16K aligned. (Yuck) Block coalescing only works well if RAM banks are added in ascending address order. RISC OS will use RAM at the start of the first block as initial workspace. Max usage is 16 bytes per block + 32 (currently 8 per block + 4). This limits the number of discontiguous blocks (although RISC OS will concatanate contiguous blocks where possible). This call must not be made after RISCOS_Start. Entry 2: void RISCOS_Start(unsigned int flags, int *riscos_header, int *hal_entry_table, void *ref) flags bit 0: power on reset bit 1: CMOS reset inhibited (eg protection link on Risc PC) bit 2: perform a CMOS reset (if bit 1 clear and bit 0 set - eg front panel button held down on an NC) On entry: SVC32 mode MMU and data cache off IRQs and FIQs disabled Usage: This routine must be called after all calls to RISCOS_AddRAM have been completed. It does not return. Future calls back to the HAL are via the HAL entry table, after the MMU has been enabled. Entry 3: void *RISCOS_MapInIO(unsigned int flags, void *phys, unsigned int size) flags: bit 2 => make memory bufferable phys: physical address to map in size: number of bytes of memory to map in Usage: This routine is used to map in IO memory for the HAL's usage. Normally it would only be called during HAL_Init(). Once mapped in the IO space cannot be released. It returns the resultant virtual address corresponding to phys, or 0 for failure. Failure can only occur if no RAM is available for page tables, or if the virtual address space is exhausted. void *RISCOS_AccessPhysicalAddress(unsigned int flags, uint64_t phys, void **oldp) flags: bit 2 => make memory bufferable other bits must be zero phys: physical address to access oldp: pointer to location to store old state (or NULL) On entry: Privileged mode MMU on FIQs on Re-entrant On exit: Returns logical address corresponding to phys Usage: Arranges for the physical address phys to be mapped in to logical memory. In fact, at least the whole megabyte containing "phys" is mapped in (ie if phys = &12345678, then &12300000 to &123FFFFF become available). The memory is supervisor access only, non-cacheable, non-bufferable by default, and will remain available until the next call to RISCOS_Release/AccessPhysicalAddress (although interrupt routines or subroutines may temporarily map in something else). When finished, the user should call RISCOS_ReleasePhysicalAddress. void RISCOS_ReleasePhysicalAddress(void *old) old: state returned from a previous call to RISCOS_AccessPhysicalAddress On entry: MMU on FIQs on Re-entrant Usage: Call with the a value output from a previous RISCOS_ReleasePhysicalAddress. Example: void *old; uint64_t addr_physical = (uint64_t) 0x80005000; uint64_t addr2_physical = (uint64_t) 0x90005000; uint32_t *addr_logical; uint32_t *addr2_logical; addr_logical = (unsigned int *) RISCOS_AccessPhysicalAddress(0, addr_physical, &old); addr_logical[0] = 3; addr_logical[1] = 5; addr2_logical = (unsigned int *) RISCOS_AccessPhysicalAddress(0, addr2_physical, NULL); *addr2_logical = 7; RISCOS_ReleasePhysicalAddress(old); HAL entries =========== void HAL_Start(int *riscos_header)