The eCos Hardware Abstraction Layer (HAL)
To Contents | To previous page | To next page

## The eCos Hardware Abstraction Layer (HAL)

This is an initial specification of the eCos Hardware Abstraction Layer (HAL). The HAL abstracts the underlying hardware of a processor architecture and/or the platform to a level sufficient for the eCos kernel to be ported onto that platform.

Caveat This document is an informal description of the HAL capabilities and is not intended to be full documentation, although it may be used as a source for such. It also describes the HAL as it is currently implemented for the architectures targeted in this release. Further work (described in Future developments ), is needed to complete it.

### Architecture, implementation and platform

We have identified three levels at which the HAL must operate. The architecture HAL abstracts the basic CPU architecture and includes things like interrupt delivery, context switching, CPU startup etc. The platform HAL abstracts the properties of the current platform and includes things like platform startup, timer devices, I/O register access and interrupt controllers. The implementation HAL abstracts properties that lie between these two, such as architecture variants and on-chip devices. The boundaries between these three HAL levels are necessarily blurred.

In the current HAL structure, there are separate directory trees for the architectural and platform HALs. The implementation HAL is currently supported in one or other of these by means of conditional compilation depending on how generic a particular feature is expected to be. Thus processor variants are handled in the architectural HAL since they are likely to be generic to several implementations. On-chip devices are handled in the platform HAL, if they impact the kernel, or as proper device drivers (and are thus outside the HAL).

The one area where there is significant interaction between these HAL layers is in the interrupt delivery VSR. Here the VSR, which is in the architectural HAL, may need to interrogate an interrupt controller to dispatch the correct ISR. The interrupt controller may be defined by the platform or implementation HAL. This is normally only a few instructions so is currently handled by conditional compilation. If this proves to become unwieldy, a mechanism for including platform code in the architectural HAL may be needed.

### General principles

The HAL has been implemented according to the following general principles:

1. The HAL is implemented in C and assembler, although the eCos kernel is largely implemented in C++. This is to permit the HAL the widest possible applicability.

2. All interfaces to the HAL are implemented by CPP macros. This allows them to be implemented as inline C code, inline assembler or function calls to external C or assembler code. This allows the most efficient implementation to be selected without affecting the interface. It also allows them to be redefined if the platform HAL needs to replace or enhance a definition from the architecture HAL.

3. The HAL provides simple, portable mechanisms for dealing with the hardware of a wide range of architectures and platforms. It is always possible to bypass the HAL and program the hardware directly, but this may lead to a loss of portability.

### Architectural HAL files

### `hal/ARCH/arch/` v1_3_x `/include/basetype.h`

This file defines the properties of the base architecture that are used to compile the portable parts of the kernel. It is included automatically by `cyg/infra/cyg_type.h` . The following definitions may be included.

#### Byte order

`CYG_BYTEORDER`

This defines the byte order of the target and must be set to either `CYG_LSBFIRST` or `CYG_MSBFIRST` .

#### Label translation

`CYG_LABEL_NAME(name)`

This is a wrapper used in some C and C++ files which specify labels defined in assembly code or the linker script. It need only be defined if the default implementation in `cyg/kernel/ktypes.h` , which passes the name argument unaltered, is inadequate. The most usual alternative definition of this macro prepends an underscore to the label name. This depends on the labeling convention of the tool set.

#### Base types

    	cyg_halint8
    	cyg_halint16
    	cyg_halint32
    	cyg_halint64
    	cyg_halcount8
    	cyg_halcount16
    	cyg_halcount32
    	cyg_halcount64
    	cyg_halbool

These macros define the C base types that should be used to define variables of the given size. They only need to be defined if the default types specified in `cyg/infra/cyg_type.h` cannot be used. Note that these are only the base types, they will be composed with `signed` and `unsigned` to form full type specifications.

##### Atomic types

    	cyg_halatomic
    	CYG_ATOMIC

These types are guaranteed to be read or written in a single uninterruptible operation. It is architecture defined what size this type is, but it will be at least a byte.

### `hal/ARCH/arch/` v1_3_x `/include/hal_arch.h`

This file contains definitions that are related to the basic architecture of the CPU.

#### Register save format

    typedef struct HAL_SavedRegisters
    {
    /* architecture-dependent list of registers to be saved */
    } HAL_SavedRegisters;

This structure describes the layout of a saved machine state on the stack. Such states are saved during thread context switches, interrupts and exceptions. Different quantities of state may be saved during each of these, but usually a thread context state is a subset of the interrupt state which is itself a subset of an exception state. Where these states are significantly different, this structure should contain a union of the three states.

#### Thread context initialization

    	HAL_THREAD_INIT_CONTEXT( sp, arg, entry, id )

This macro initializes a thread's context so that it may be switched to by `HAL_THREAD_SWITCH_CONTEXT()` . The arguments are:

sp

A location containing the current value of the thread's stack pointer. This should be a variable or a structure field. The SP value will be read out of here and an adjusted value written back.

arg

A value that is passed as the first argument to the entry point function.

entry

The address of an entry point function. This will be called according the C calling conventions, and the value of arg will be passed as the first argument.

id

A thread id value. This is only used for debugging purposes, it is ORed into the initialization pattern for unused registers and may be used to help identify the thread from its register dump. The least significant 16 bits of this value should be zero to allow space for a register identifier.

#### Thread context switching

    HAL_THREAD_SWITCH_CONTEXT( from, to )

This macro implements the thread switch code. The arguments are:

from

A pointer to a location where the stack pointer of the current thread will be stored.

to

A pointer to a location from where the stack pointer of the next thread will be read.

The state of the current thread is saved onto its stack, using the current value of the stack pointer, and the address of the saved state placed in *from. The value in *to is then read and the state of the new thread is loaded from it.

Note that interrupts are not disabled during this process, any interrupts that occur will be delivered onto the stack to which the current value of the CPU stack pointer points. Hence the stack pointer should never be invalid, or loaded with a value that might cause the saved state to become corrupted by an interrupt.

#### Bit indexing

    HAL_LSBIT_INDEX( mask, index )
    HAL_MSBIT_INDEX( mask, index )

These macros place in index the bit index of the least(most) significant bit in mask. Some architectures have instruction level support for one or other of these operations. If no architectural support is available, then these macros may call C functions to do the job.

#### Idle thread activity

    HAL_IDLE_THREAD_ACTION( count )

It may be necessary under some circumstances for the HAL to execute code in the kernel idle thread's loop. An example might be to execute a processor halt instruction. This macro provides a portable way of doing this. The argument is a copy of the idle thread's loop counter, and may be used to trigger actions at longer intervals than every loop.

#### Reorder barrier

    HAL_REORDER_BARRIER()

When optimizing the compiler can reorder code. In some parts of multi-threaded systems, where the order of actions is vital, this can sometimes cause problems. This macro may be inserted into places where reordering should not happen and prevents code being migrated across it by the compiler optimizer. It should be placed between statements that must be executed in the order written in the code.

#### Breakpoint support

    HAL_BREAKPOINT( label )
    HAL_BREAKINST
    HAL_BREAKINST_SIZE

These macros provide support for breakpoints.

`HAL_BREAKPOINT()` executes a breakpoint instruction. The label is defined at the breakpoint instruction so that exception code can detect which breakpoint was executed.

HAL_BREAKINST contains the breakpoint instruction code as an integer value. HAL_BREAKINST_SIZE is the size of that breakpoint instruction in bytes. Together these may be used to place a breakpoint in any code.

#### GDB support

    HAL_THREAD_GET_SAVED_REGISTERS( sp, regs )
    HAL_GET_GDB_REGISTERS( regval, regs )
    HAL_SET_GDB_REGISTERS( regs, regval )

These macros provide support for interfacing GDB to the HAL.

`HAL_THREAD_GET_SAVED_REGISTERS()` extracts a pointer to a HAL_SavedRegisters structure from a stack pointer value. The stack pointer passed in should be the value saved by the thread context macros. The macro will assign a pointer to the HAL_SavedRegisters structure to the variable passed as the second argument.

`HAL_GET_GDB_REGISTERS()` translates a register state as saved by the HAL and into a register dump in the format expected by GDB. It takes a pointer to a HAL_SavedRegisters structure in the regs argument and a pointer to the memory to contain the GDB register dump in the regval argument.

`HAL_SET_GDB_REGISTERS()` translates a GDB format register dump into a the format expected by the HAL. It takes a pointer to the memory containing the GDB register dump in the regval argument and a pointer to a HAL_SavedRegisters structure in the regs argument.

#### Setjmp and longjmp support

    CYGARC_JMP_BUF_SIZE
    hal_jmp_buf[CYGARC_JMP_BUF_SIZE]
    hal_setjmp( hal_jmp_buf env )
    hal_longjmp( hal_jmp_buf env, int val )

These functions provide support for the C `setjmp()` and `longjmp()` functions. Refer to the C library for further information.

### `hal/ARCH/arch/v1_3_x/include/hal_intr.h`

This file contains definitions related to interrupt handling.

#### Vector numbers

    CYGNUM_HAL_VECTOR_XXX
    CYGNUM_HAL_VSR_MIN
    CYGNUM_HAL_VSR_MAX
    CYGNUM_HAL_ISR_MIN
    CYGNUM_HAL_ISR_MAX
    CYGNUM_HAL_EXCEPTION_MIN
    CYGNUM_HAL_EXCEPTION_MAX
    CYGNUM_HAL_ISR_COUNT
    CYGNUM_HAL_VSR_COUNT
    CYGNUM_HAL_EXCEPTION_COUNT

All possible interrupt and exception vectors should be specified here, together with maximum and minimum values for range checking.

There are two ranges of numbers, those for the vector service routines and those for the interrupt service routines. The relationship between these two ranges is undefined, and no equivalence should be assumed if vectors from the two ranges coincide.

The VSR vectors correspond to the set of exception vectors that can be delivered by the CPU architecture, many of these will be internal exception traps. The ISR vectors correspond to the set of external interrupts that can be delivered and are usually determined by extra decoding of an interrupt controller by the interrupt VSR.

Where a CPU supports synchronous exceptions, the range of such exceptions allowed are defined by CYGNUM_HAL_EXCEPTION_MIN and CYGNUM_HAL_EXCEPTION_MAX. The actual exception numbers will normally correspond to the VSR exception range. In future other exceptions generated by the system software (such as stack overflow) may be added.

CYGNUM_HAL_ISR_COUNT, CYGNUM_HAL_VSR_COUNT and CYGNUM_HAL_EXCEPTION_COUNT define the number of ISRs, VSRs and EXCEPTIONs respectively for the purposes of defining arrays etc. There might be a translation from the supplied vector numbers into array offsets. Hence CYGNUM_HAL_XXX_COUNT may not simply be CYGNUM_HAL_XXX_MAX - CYGNUM_HAL_XXX_MIN or CYGNUM_HAL_XXX_MAX +1.

#### Interrupt state control

    HAL_DISABLE_INTERRUPTS( old )
    HAL_RESTORE_INTERRUPTS( old )
    HAL_ENABLE_INTERRUPTS()
    HAL_QUERY_INTERRUPTS( state )

These macros provide control over the state of the CPUs interrupt mask mechanism. They should normally manipulate a CPU status register to enable and disable interrupt delivery. They should not access an interrupt controller.

`HAL_DISABLE_INTERRUPTS()` disables the delivery of interrupts and stores the original state of the interrupt mask in the variable passed in the old argument.

`HAL_RESTORE_INTERRUPTS()` restores the state of the interrupt mask to that recorded in old.

`HAL_ENABLE_INTERRUPTS()` simply enables interrupts regardless of the current state of the mask.

`HAL_QUERY_INTERRUPTS()` stores the state of the interrupt mask in the variable passed in the state argument.

It is at the HAL implementer's discretion exactly which interrupts are masked by this mechanism. Where a CPU has more than one interrupt type that may be masked separately (e.g. the ARM's IRQ and FIQ) only those that can raise DSRs need to be masked here. A separate architecture specific mechanism may then be used to control the other interrupt types.

#### ISR and VSR management

    HAL_INTERRUPT_ATTACH( vector, isr, data, object )
    HAL_INTERRUPT_DETACH( vector, isr )
    HAL_VSR_SET( vector, vsr, poldvsr )
    HAL_VSR_GET( vector, pvsr )

These macros manage the attachment of interrupt and vector service routines to interrupt and exception vectors respectively.

`HAL_INTERRUPT_ATTACH()` attaches the ISR, data pointer and object pointer to the given vector. When an interrupt occurs on this vector the ISR is called using the C calling convention and the vector number and data pointer are passed to it as the first and second arguments respectively.

`HAL_INTERRUPT_DETACH()` detaches the ISR from the vector.

`HAL_VSR_SET()` replaces the VSR attached to the vector with the replacement supplied in vsr. The old VSR is returned in the location pointed to by pvsr.

`HAL_VSR_GET()` assigns a copy of the VSR to the location pointed to by pvsr.

#### Interrupt controller management

    HAL_INTERRUPT_MASK( vector )
    HAL_INTERRUPT_UNMASK( vector )
    HAL_INTERRUPT_ACKNOWLEDGE( vector )
    HAL_INTERRUPT_CONFIGURE( vector, level, up )
    HAL_INTERRUPT_SET_LEVEL( vector, level )

These macros exert control over any prioritized interrupt controller that is present. If no priority controller exists, then these macros should be empty.

`HAL_INTERRUPT_MASK()` causes the interrupt associated with the given vector to be blocked.

`HAL_INTERRUPT_UNMASK()` causes the interrupt associated with the given vector to be unblocked.

`HAL_INTERRUPT_ACKNOWLEDGE()` acknowledges the current interrupt from the given vector. This is usually executed from the ISR for this vector when it is prepared to allow further interrupts. Most interrupt controllers need some form of acknowledge action before the next interrupt is allowed through. Executing this macro may cause another interrupt to be delivered. Whether this interrupts the current code depends on the state of the CPU interrupt mask.

`HAL_INTERRUPT_CONFIGURE()` provides control over how an interrupt signal is detected. The arguments are:

vector

The interrupt to be configured.

level

Set to `true` if the interrupt is detected by level, and `false` if it is edge triggered.

up

If the interrupt is set to level detect, then if this is `true` it is detected by a high signal level, and if `false` by a low signal level. If the interrupt is set to edge triggered, then if this is `true` it is triggered by a rising edge and if `false` by a falling edge.

`HAL_INTERRUPT_SET_LEVEL()` provides control over the hardware priority of the interrupt. The arguments are:

vector

The interrupt whose level is to be set.

level

The priority level to which the interrupt is to set. In some architectures the set interrupt level is also used as an interrupt enable/disable. Hence this function and `HAL_INTERRUPT_MASK()` and `HAL_INTERRUPT_UNMASK()` may interfere with each other.

#### Clock control

    HAL_CLOCK_INITIALIZE( period )
    HAL_CLOCK_RESET( vector, period )
    HAL_CLOCK_READ( pvalue )

These macros provide control over a clock or timer device that may be used by the kernel to provide time-out, delay and scheduling services. The clock is assumed to be implemented by some form of counter that is incremented or decremented by some external source and which raises an interrupt when it reaches zero.

`HAL_CLOCK_INITIALIZE()` initializes the clock device to interrupt at the given period. The period is essentially the value used to initialize the clock counter and must be calculated from the clock frequency and the desired interrupt rate.

`HAL_CLOCK_RESET()` re-initializes the clock to provoke the next interrupt. This macro is only really necessary when the clock device needs to be reset in some way after each interrupt.

`HAL_CLOCK_READ()` reads the current value of the clock counter and puts the value in the location pointed to by pvalue. The value stored will always be the number of clock ``ticks'' since the last interrupt, and hence ranges between zero and the initial period value.

### `hal/ARCH/arch/v1_3_x/include/hal_io.h`

This file contains definitions for supporting access to device control registers in an architecture neutral fashion.

#### Register address

    HAL_IO_REGISTER

This type is used to store the address of an I/O register. It will normally be a memory address, an integer port address or an offset into an I/O space. More complex architectures may need to code an address space plus offset pair into a single word, or may represent it as a structure.

Values of variables and constants of this type will usually be supplied by configuration mechanisms.

#### Register read

    HAL_READ_XXX( register, value )
    HAL_READ_XXX_VECTOR( register, buffer, count, stride )

These macros support the reading of I/O registers in various sizes. The XXX component of the name may be UINT8, UINT16, UINT32.

`HAL_READ_XXX()` reads the appropriately sized value from the register and stores it in the variable passed as the second argument.

`HAL_READ_XXX_VECTOR()` reads count values of the appropriate size into buffer. The stride controls how the pointer advances through the register space. A stride of zero will read the same register repeatedly, and a stride of one will read adjacent registers of the given size. Greater strides will step by larger amounts, to allow for sparsely mapped registers for example.

#### Register write

    HAL_WRITE_XXX( register, value )
    HAL_WRITE_XXX_VECTOR( register, buffer, count, stride )

These macros support the writing of I/O registers in various sizes. The `XXX` component of the name may be `UINT8` , `UINT16` , `UINT32` .

`HAL_WRITE_XXX()` writes the appropriately sized value from the variable passed as the second argument stored it in the register.

`HAL_WRITE_XXX_VECTOR()` writes count values of the appropriate size from buffer. The stride controls how the pointer advances through the register space. A stride of zero will write the same register repeatedly, and a stride of one will write adjacent registers of the given size. Greater strides will step by larger amounts, to allow for sparsely mapped registers for example.

### `hal/ARCH/arch/v1_3_x/include/hal_cache.h`

This file contains definitions for supporting control of the caches on the CPU.

There are versions of the macros defined here for both the Data and Instruction caches. these are distinguished by the use of either DCACHE or ICACHE in the macro names. In the following descriptions, XCACHE is also used to stand for either of these. Where there are issues specific to a particular cache, this will be explained in the text.

There might be restrictions on the use of some of the macros which it is the user's responsibility to comply with. Such restrictions are documented in the `hal_cache.h` file.

Note that destructive cache macros should be used with caution. Preceding a cache invalidation with a cache synchronization is not safe in itself since an interrupt may happen after the synchronization but before the invalidation. This might cause the state of dirty data lines created during the interrupt to be lost.

Depending on the architecture's capabilities, it may be possible to temporarily disable the cache while doing the synchronization and invalidation which solves the problem (no new data would be cached during an interrupt). Otherwise it is necessary to disable interrupts while manipulating the cache which may take a long time.

#### Cache dimensions

    HAL_XCACHE_SIZE
    HAL_XCACHE_LINE_SIZE
    HAL_XCACHE_WAYS
    HAL_XCACHE_SETS

These macros define the size and dimensions of the Instruction and Data caches.

HAL_XCACHE_SIZE

gives the total size of the cache in bytes.

HAL_XCACHE_LINE_SIZE

gives the cache line size in bytes.

HAL_XCACHE_WAYS

gives the number of ways in each set and defines its level of associativity. This would be 1 for a direct mapped cache.

HAL_XCACHE_SETS

gives the number of sets in the cache, and is derived from the previous values.

#### Global cache control

    HAL_XCACHE_ENABLE()
    HAL_XCACHE_DISABLE()
    HAL_XCACHE_INVALIDATE_ALL()
    HAL_XCACHE_SYNC()
    HAL_XCACHE_BURST_SIZE( size )
    HAL_DCACHE_WRITE_MODE( mode )
    HAL_XCACHE_LOCK( base, size )
    HAL_XCACHE_UNLOCK( base, size )
    HAL_XCACHE_UNLOCK_ALL()

These macros affect the state of the entire cache, or a large part of it.

HAL_XCACHE_ENABLE() and HAL_XCACHE_DISABLE()

enable and disable the cache.

HAL_XCACHE_INVALIDATE_ALL()

causes the entire contents of the cache to be invalidated. Depending on the hardware, this may require the cache to be disabled during the invalidation process. If so, the implementation must use HAL_XCACHE_IS_ENABLED to save and restore the previous state.

HAL_XCACHE_SYNC()

causes the contents of the cache to be brought into synchronization with the contents of memory. In some implementations this may be equivalent to HAL_XCACHE_INVALIDATE_ALL().

HAL_XCACHE_BURST_SIZE()

allows the size of cache to/from memory bursts to be controlled. This macro will only be defined if this functionality is available.

HAL_DCACHE_WRITE_MODE()

controls the way in which data cache lines are written back to memory. There will be definitions for the possible modes. Typical definitions are HAL_DCACHE_WRITEBACK_MODE and HAL_DCACHE_WRITETHRU_MODE. This macro will only be defined if this functionality is available.

HAL_XCACHE_LOCK()

causes data to be locked into the cache. The base and size arguments define the memory region that will be locked into the cache. It is architecture dependent whether more than one locked region is allowed at any one time, and whether this operation causes the cache to cease acting as a cache for addresses outside the region during the duration of the lock. This macro will only be defined if this functionality is available.

HAL_XCACHE_UNLOCK()

cancels the locking of the memory region given. This should normally correspond to a region supplied in a matching lock call. This macro will only be defined if this functionality is available.

HAL_XCACHE_UNLOCK_ALL()

cancels all existing locked memory regions. This may be required as part of the cache initialization on some architectures. This macro will only be defined if this functionality is available.

#### Cache line control

    HAL_DCACHE_ALLOCATE( base , size )
    HAL_DCACHE_FLUSH( base , size )
    HAL_XCACHE_INVALIDATE( base , size )
    HAL_DCACHE_STORE( base , size )
    HAL_DCACHE_READ_HINT( base , size )
    HAL_DCACHE_WRITE_HINT( base , size )
    HAL_DCACHE_ZERO( base , size )

All of these macros apply a cache operation to all cache lines that match the memory address region defined by the base and size arguments. These macros will only be defined if the described functionality is available. Also, it is not guaranteed that the cache function will only be applied to just the described regions, in some architectures it may be applied to the whole cache.

HAL_DCACHE_ALLOCATE()

allocates lines in the cache for the given region without reading their contents from memory, hence the contents of the lines is undefined. This is useful for preallocating lines which are to be completely overwritten, for example in a block copy operation.

HAL_DCACHE_FLUSH()

invalidates all cache lines in the region after writing any dirty lines to memory.

HAL_XCACHE_INVALIDATE()

invalidates all cache lines in the region. Any dirty lines are invalidated without being written to memory.

HAL_DCACHE_STORE()

writes all dirty lines in the region to memory, but does not invalidate any lines.

HAL_DCACHE_READ_HINT()

hints to the cache that the region is going to be read from in the near future. This may cause the region to be speculatively read into the cache.

HAL_DCACHE_WRITE_HINT()

hints to the cache that the region is going to be written to in the near future. This may have the identical behavior to HAL_DCACHE_READ_HINT().

HAL_DCACHE_ZERO()

allocates and zeroes lines in the cache for the given region without reading memory. This is useful if a large area of memory is to be cleared.

### `hal/ARCH/arch/v1_3_x/src/ARCH.ld`

This is the architecture specific linker script file. It defines the section types required for the architecture. During preprocessing, the memory layout specified for the chosen platform and startup type is included, defining region, alignment and location parameters for the sections.

### `hal/ARCH/arch/v1_3_x/src/vectors.S`

This file contains code to deal with exception and interrupt vectors. Since the reset entry point is usually implemented as one of these it also deals with system startup.

The exact implementation of this code is under the control of the HAL implementer. So long as it interacts correctly with the macros defined in `hal_intr.h` it may take any form. However, all current implementation follow the same pattern, and there should be a very good reason to break with this. The rest of this section describes how the standard HAL implementation operates.

This file usually contains the following sections of code:

- Startup and initialization code.

- Exception delivery.

- Default handling of synchronous exception.

- Default handling of interrupts.

#### HAL startup

Execution normally begins at the reset vector with the machine in a minimal startup state.

The following is a list of the jobs that need to be done in approximately the order in which they should be accomplished. Many of these will not be needed in some configurations.

- Initialize various CPU status registers. Most importantly, the CPU interrupt mask should be set to disable interrupts.

- Set up any CPU memory controller to access RAM, ROM and I/O devices correctly. Until this is done it may not be possible to access RAM.

- Enable the cache, if it is to be used. This may require enabling the CPU's memory management system since that is often the only way of controlling the cacheability of memory. If this is necessary, a direct one-to-one mapping between physical and virtual memory is most desirable.

- Set up the stack pointer, this allows subsequent initialization code to make procedure calls.

- Initialize any global pointer register needed for access to globally defined variables. This allows subsequent initialization code to access global variables.

- Perform any platform specific initialization. This is best accomplished by calling an initialization routine in `PLATFORM.S` (see hal/ARCH/PLATFORM/v1_3_x/src/PLATFORM.S ).

- If the system is starting from ROM, copy the ROM template of the `.data` section out to its correct position in RAM. (See hal/ARCH/arch/v1_3_x/src/ARCH.ld ).

- Zero the `BSS` section.

- Create a suitable C call stack frame.

- Call `cyg_hal_invoke_constructors()` to run any static constructors.

- Call `cyg_start()` . If `cyg_start()` returns, drop into an infinite loop.

#### Vectors and VSRs

The CPU delivers all exceptions whether synchronous or interrupts to a set of vectors. Depending on the architecture, these may be implemented in a number of different ways. Examples of existing mechanisms are:

PowerPC

Exceptions are vectored to locations 256 bytes apart starting at either zero or `0xFFF00000` . There are 16 such vectors defined by the architecture and extra vectors may be defined by specific implementations.

MIPS

All exceptions are vectored to a single address and software is responsible for reading the exception code from a CPU register to discover its true source.

MN10300

External interrupts are vectored to an address stored in one of seven interrupt vector registers. These only supply the lower 16 bits of the address, the upper 16 bits are fixed to `0x4000XXXX` . Hence the service routine is constrained to the 64k range starting at `0x40000000` .

Pentium

Exceptions are delivered via an Interrupt Descriptor Table (IDT) which is essentially an indirection table indexed by exception type. The IDT may be placed anywhere in memory. In PC hardware the interrupt controller can be programmed to deliver the external interrupts to a block of 16 vectors at any offset in the IDT.

680X0

Exceptions are delivered via an indirection table described by a CPU base register (for X > 0). External interrupts are either delivered via a set of level-specific vectors defined by the architecture, or a vector number may be supplied by the device in which case another entry in the table may be used.

The model adopted by the HAL is that VSRs should be easily replaceable with a pointer to an alternative routine. Of the above architectures, only the Pentium and 680X0 allow this directly in the hardware. In the other three, extra software is required. The code attached directly to the vector is a short trampoline that indirects by way of a HAL supplied VSR table to the true VSR. In the PowerPC and MN10300 the table offset is implicit in the vector routine called, for the MIPS the code reads the cause register and indirects through the appropriate table entry.

### Default exception handling

Most synchronous exception vectors will point to a default exception VSR which is responsible for handling all exceptions in a generic manner.

Since most exceptions handled by this VSR are errors (or breakpoints when a program is being debugged), its default behavior should be to save the entire machine state, disable interrupts, and invoke the debugger's entry point, passing it a pointer to the saved state.

If the debugger returns then the saved state is restored and the interrupted code resumed. Since the debugger may adjust the saved state while it runs a little care must be taken to restore the state correctly.

### Default interrupt handling

Most external interrupt vectors will point to a default interrupt VSR which decode the actual interrupt being delivered and invokes the appropriate ISR.

The default interrupt VSR has a number of responsibilities if it is going to interact with the Kernel cleanly and allow interrupts to cause thread preemption.

To support this VSR an ISR vector table is needed. For each valid vector three pointers need to be stored: the ISR, its data pointer and an interrupt object pointer needed by the kernel. It is implementation defined whether these are stored in a single table of triples, or in three separate tables.

The VSR should follow the following approximate plan:

- Save the CPU state. In non-debug configurations, it may be possible to get away with saving less than the entire machine state.

- Increment the kernel scheduler lock. This is a static member of the Cyg_Scheduler class. It may be necessary to look at a objdump or assembler listing of `sched.cxx` to discover its mangled label.

- (Optional) Switch to an interrupt stack if not already running on it. This allows nested interrupts to be delivered without needing every thread to have a stack large enough to take the maximum possible nesting. It is implementation defined how to detect whether this is a nested interrupt.

- (Optional) Re-enable interrupts to permit nesting.

- Decode the actual external interrupt being delivered from the interrupt controller. This will yield the ISR vector number.

- Using the ISR vector number as an index, retrieve the ISR pointer and its data pointer from the ISR vector table.

- Construct a C call stack frame.

- Call the ISR, passing the vector number and data pointer. The vector number and a pointer to the saved state should be preserved across this call, preferably by storing them in registers that are defined to be callee-saved by the calling conventions.

- If this is an un-nested interrupt and a separate interrupt stack is being used, switch back to the interrupted thread's own stack.

- (Optional) If interrupts were not enabled above, enable them here since the `interrupt_end()` function must be called with interrupts enabled.

- Use the saved ISR vector number to get the interrupt object pointer from the ISR vector table.

- Call `interrupt_end()` passing it the return value from the ISR, the interrupt object pointer and a pointer to the saved CPU state. This function is implemented by the Kernel and is responsible for finishing off the interrupt handling. Specifically, it may post a DSR depending on the ISR return value, and will decrement the scheduler lock. If the lock is zeroed by this then it may result in a thread context switch.

- When `interrupt_end()` returns, restore the machine state and resume execution of the interrupted thread. Depending on the architecture, it may be necessary to disable interrupts again for part of this.

The detailed order of these steps may vary slightly depending on the architecture, in particular where interrupts are enabled and disabled.

### `hal/ARCH/arch/v1_3_x/src/hal_misc.c`

This file contains any miscellaneous functions that are reference by the HAL. Typical functions that might go here are C implementations of the least- and most- significant bit index routines, constructor calling functions such as `cyg_hal_invoke_constructors()` and support routines for the exception and interrupt vector handling.

### `hal/ARCH/PLATFORM/v1_3_x/include/pkgconf/STARTUP.mlt`

For each startup type (STARTUP) the memory layout of the sections is defined. This information may be edited using the Configuration Tool only.

### `hal/ARCH/PLATFORM/v1_3_x/include/pkgconf/STARTUP.ldi`

For each startup type (STARTUP) the memory layout of the sections is exported by the Configuration Tool as a linker script fragment suitable for inclusion within the architecture-specific linker script file during preprocessing. The linker script fragment to be included is specified by the CYGHWR_MEMORY_LAYOUT_LDI macro in the `system.h` header file. The linker script fragments will be overwritten by the Configuration Tool and should only edited manually where the Configuration Tool is not in use.

### `hal/ARCH/PLATFORM/v1_3_x/include/hal_diag.h`

During early development it is useful to have the ability to output messages to some default destination. This may be a memory buffer, a simulator supported output channel, a ROM emulator virtual UART or a serial line. This file defines set of macros that provide simple, polled output for this purpose.

`HAL_DIAG_INIT()` performs any initialization required on the device being used to generate diagnostic output. This may include setting baud rate, and stop, parity and character bits.

`HAL_DIAG_WRITE_CHAR(c)` writes the character supplied to the diagnostic output device.

These macros may either implement the required functionality directly, or may call functions elsewhere in the HAL to do it. In the latter case these should be in the file hal/ARCH/PLATFORM/v1_3_x/src/hal_diag.c.

### `hal/ARCH/PLATFORM/v1_3_x/src/PLATFORM.S`

This is a platform specific assembly code file. Its main purpose is to contain any platform specific startup code called from `vectors.S` .

### `hal/ARCH/PLATFORM/v1_3_x/src/context.S`

If present, this is an assembly code file that contains the code to support thread contexts. The routines to switch between various contexts, as well as initialize a thread context may be present in this file.

### `hal/ARCH/PLATFORM/v1_3_x/src/hal_diag.c`

If present, this file contains the implementation of the HAL diagnostic support routines.

### Future developments

The HAL is not complete, and will evolve and increase over time. Among the intended developments are:

- Common macros for interpreting the contents of a saved machine context. These would allow portable code, such as debug stubs, to extract such values as the program counter and stack pointer from a state without having to interpret a HAL_SavedRegisters structure directly.

- Debugging support. Macros to set and clear hardware and software breakpoints. Access to other areas of machine state may also be supported.

- Floating point support. The saving and restoring of floating point state may need to added to the HAL for those architectures that support it. The exact mechanisms provided need to be defined.

- Static initialization support. The current HAL provides a dynamic interface to things like thread context initialization and ISR attachment. We also need to be able to define the system entirely statically so that it is ready to go on restart, without needing to run code. This will require extra macros to define these initializations. Such support may have a consequential effect on the current HAL specification.

- CPU state control. Many CPUs have both kernel and user states. Although it is not intended to run any code in user state for the foreseeable future, it is possible that this may happen eventually. If this is the case, then some minor changes may be needed to the current HAL API to accommodate this. These should mostly be extensions, but minor changes in semantics may also be required.

- Physical memory management. Many embedded systems have multiple memory areas with varying properties such as base address, size, speed, bus width, cacheability and persistence. An API is needed to support the discovery of this information about the machine's physical memory map.

- Memory management control. Some embedded processors have a memory management unit. In some cases this must be enabled to allow the cache to be controlled, particularly if different regions of memory must have different caching properties. For some purposes, in some systems, it will be useful to manipulate the MMU settings dynamically.

- Power management. Macros to access and control any power management mechanisms available on the CPU implementation. These would provide a substrate for a more general power management system that also involved device drivers and other hardware components.

- Generic serial line macros. Most serial line devices operate in the same way, the only real differences being exactly which bits in which registers perform the standard functions. It should be possible to develop a set of HAL macros that provide basic serial line services such as baud rate setting, enabling interrupts, polling for transmit or receive ready, transmitting and receiving data etc. Given these it should be possible to create a generic serial line device driver that will allow rapid bootstrapping on any new platform. It may be possible to extend this mechanism to other device types.

- Porting Guide. As the HAL develops it will become important to perform a port to a new architecture in the correct way.

### Kernel porting notes

This section briefly describes the issues involved in porting eCos to a new target platform and/or architecture.

### Porting overview

The effort required to port eCos to a new target varies. Adding support for a new platform/board may require almost no effort, while adding support for a new architecture is more demanding. Additionally, new device drivers may have to be written if there is no existing support for the target's devices.

Given that there are usually more target platforms using the same microprocessor or microcontroller, adding eCos support for a new target would often be a question of adding support for the new target platform. The architectures supported by eCos include the following: ARM7, MIPS (TX39), MN10300, PowerPC (MPC8xx), and SPARClite.

Adding a new architecture support is a bigger job and also requires tool support ( GCC, GDB and binutils) which is a big undertaking in itself.

#### Platform support

Adding support for a new platform requires (a subset of):

1. Adding eCos configuration information.

2. Memory layout description.

3. Memory controller initialization.

4. Interrupt controller handling.

5. Minimal serial device driver for GDB interaction and simple diagnostics output.

6. System timer initialization and control.

7. Wallclock driver.

A wallclock emulation based on the system timer is provided with the standard eCos distribution. For those hardware platforms where a battery backed-up clock device or other means of determining actual wallclock time exists, a wallclock driver may be implemented more fully.

If the architecture in question is a microcontroller (as opposed to a microprocessor), the job of porting may be as simple as adding configuration information and defining a new memory layout (items one and two). Currently eCos supports the following microcontrollers: MN10300, MPC8xx, and TX39.

#### Architectural support

Adding support for a new architecture requires:

1. Adding eCos configuration information.

2. Writing a HAL for the CPU core's register model, interrupt and exception model, cache model, and possibly simple handling for the MMU model.

3. For microcontrollers the HAL should also support the memory controller, interrupt controller and a possible on-MCP serial controller for GDB interaction and simple diagnostics output, system timer initialization and control, and a wallclock driver.

If there is already support for a member of the same architecture family, the porting job may just consist of adding extra feature support to the existing HAL. Or if the new target architecture only defines a subset of the architecture family, the HAL may need additional configuration control, allowing parts of the existing HAL code to be disabled.

### Adding configuration information

Architecture and platform configuration information resides in two top-level files `targets` and `packages` as well as in architecture and platform specific configuration files (`hal/<arch>/arch/current/include/pkgconf/hal_<arch>.h` and `hal/<arch>/<platform>/current/include/pkgconf/hal_<arch>_<platform>.h.` Furthermore, each platform must define memory layouts for each startup type.

##### targets

Architecture and platform information must be added to the `targets` file.

    target powerpc {
        alias               { PowerPC powerpc-eabi }
        command_prefix      powerpc-eabi
        packages            { CYGPKG_HAL_POWERPC }
        hal                 hal/powerpc/arch

        cflags {
            ARCHFLAGS       "-mcpu=860 -D_SOFT_FLOAT"
            ERRFLAGS        "-Wall -Wpointer-arith -Wstrict-prototypes -Winline -Wundef"
            CXXERRFLAGS     "-Woverloaded-virtual"
            LANGFLAGS       "-ffunction-sections -fdata-sections"
            DBGFLAGS        "-g -O2"
            CXXLANGFLAGS    "-fno-rtti -fno-exceptions -fvtable-gc -finit-priority"
            LDLANGFLAGS     "-Wl,--gc-sections -Wl,-static"
        }

        platform cogent {
            alias           { "Cogent board" }
            startup         { ram rom stubs }
            packages        {
                CYGPKG_HAL_POWERPC_COGENT
                CYGPKG_DEVICES_WALLCLOCK
                CYGPKG_DEVICES_WATCHDOG
            }
        }
    }

pkgconf uses the entries in `targets` to create a build tree. The --target option matches target name (`powerpc` ) or its aliases (`PowerPC` `powerpc-eabi` ), just as the --platform option matches platform name (`cogent` ) or its aliases (`Cogent board` ). The same is true for the --startup option which matches on the list of valid startup types (`ram` , `rom` and `stubs` ).

The `command_prefix` is the prefix on the cross compiler tools, usually the same target triplet used when configuring the tools (`powerpc-eabi` ).

`packages` lists the hardware-related packages that should be enabled if this target is selected. Typically this will just be the appropriate architectural HAL package provided for this architecture (`CYGPKG_HAL_POWERPC` ), while `hal` specifies the relative path of the source files.

`cflags` specifies the compiler and linker flags. The -finit-priority flag is required for proper initialization of eCos, while -ffunction-sections, -fdata-sections, and -Wl,--gc-sections are required to provide linker garbage collection which removes functions and initialized data that are not going to be used. The other `FLAGS` definitions can be set according to preference, taking care to ensure that `ARCHFLAGS` contains all necessary flags for the particular architecture.

The `platform` option is used to define a new target platform. There can be several of these for each architecture. The name and startup types are defined using `platform` , `alias` , and `startup` as described above. `packages` defines the set of packages supported by this particular platform. This set must include the platform HAL package (`CYGPKG_HAL_POWERPC_COGENT` ), but can name other packages (`CYGPKG_DEVICES_WALLCLOCK` and `CYGPKG_DEVICES_WATCHDOG` ) which will be enabled per default when selecting this architecture/platform configuration.

##### packages

The individual packages must be defined in the `packages` file.

    package CYGPKG_HAL_POWERPC {
            alias           { "PowerPC common HAL" hal_powerpc powerpc_hal powerpc_arch_hal }
            directory       hal/powerpc/arch
            include_dir     cyg/hal
            hardware
    }

    package CYGPKG_HAL_POWERPC_COGENT {
            alias           { "PowerPC Cogent board support" hal_powerpc_cogent powerpc_cogent_hal }
            directory       hal/powerpc/cogent
            include_dir     cyg/hal
            hardware
    }

These are the definitions of the two packages named in the `targets` file. The aliases can be used with the --disable- and --enable- options of pkgconf.

`directory` specifies the relative path of the source files, `include_dir` where header files provided by the package should be copied to in the install directory, and `hardware` specifies that these packages is normally associated with specific hardware and should only be enabled for the appropriate hardware.

#### Package-specific configuration

The package-specific configuration files provide presentation information used by the Configuration Tool, dependencies on other packages and of course additional fine-grained options that are architecture and/or target specific. See the two files `hal/powerpc/arch/current/include/pkgconf/hal_powerpc.h` and `hal/powerpc/cogent/current/include/pkgconf/hal_powerpc_cogent.h` for an example.

#### Memory layout information

For each target platform must be defined the memory layout used for any given startup type. This information resides in two files `mlt_<arch>_<platform>_<startup>.ldi` and `mlt_<arch>_<platform>_<startup>.mlt` in the directory `hal/<arch>/<platform>/current/include/pkgconf/` . The former is a linker script fragment, the latter a file describing the layout for the eCos Configuration Tool.

Redefining the memory layout can be done in the Configuration Tool, which will create the linker script (the `.ldi` file). It is also possible to do by hand, in which case only the linker script should be created; when no `.mlt` file exists, the Configuration Tool will not overwrite the default linker script.

### Platform porting

Platform porting basically consists of making a copy of an existing platform directory and changing the code to match the new platform. The header and source files in the platform directory and their contents are described in Architectural HAL files .

In particular the configuration information and memory layout need changing, as may the board initialization code and the minimal serial drivers used by `hal_diag.c` and `plf_stub.c` .

Another useful reference for porting to a new platform is the GNUPro documentation on `gdb` stubs, which can be found at

`http://www.cygnus.com/pubs/gnupro/3_GNUPro_Debugging_Tools/b_Debugging_with_GDB/gdbThe_GDB_remote_serial_protocol.html`

### Architectural porting

The easiest way to make a new architectural port of eCos is to make a copy of an existing HAL and change the code to suit the new CPU. This guide will use the PowerPC Cogent board as an example. Wherever powerpc, ppc, or cogent is mentioned in this guide or in the source files, you should replace the strings with appropriate architecture and platform names. There are also a few files that need renaming.

If there is simulator support for the new CPU it is possible to test big parts of the HAL and the rest of the eCos kernel before a port to a specific platform is attempted. This is an advantage as doing a platform port can cause problems of it own, making it difficult to determine whether the architectural or platform parts of the port in progress are to blame when something is not working properly.

When no simulator support exists, the starting point of a port is to produce a minimal GDB stub for the target platform, which will allow code to be downloaded, executed and/or debugged on the board. This guide is based on a situation where no simulator exists as it would be the most likely scenario.

#### Writing an eCos GDB stub

A GDB stub has both a architectural part (description of the CPUs registers, exception decoding, breakpoint and stepping model, etc.) and a platform part (board initialization and simple serial driver).

Writing a stub is a subset of the work required to a full architectural and platform port of the HAL (and thus eCos ). The below sections will be a rough list of minimal requirements for a stub; remaining elements of the files can be fleshed out when extending the port to include full eCos functionality. The files and their contents are described in Architectural HAL files .

### TIP

If the target board has an existing download stub (not necessarily GDB compliant), the GDB stub can be tested by changing it to run from RAM rather than ROM (using `ram` startup instead of `stubs` startup).

After downloading the stub and starting it, it should be possible to connect GDB to the target. Note that trying to download another application may cause the memory of the stub to be overwritten, so some consideration is required when defining the memory layout.

If the target board does not have an existing download stub and requires a new EPROM to be burned for each testing cycle, you may want to start with writing a minimal stub which can only be used for downloading data to the target board.

For this purpose you can skip the exception support code in `vectors.S` and hack `hal/common/current/src/stubrom/stubrom.c` to jump directly to the stub code without using a breakpoint.

### TIP

While working on improving the stub code or other parts of the HAL you can use the simple diagnostics output functions (by way of diag_printf) as a crude way of providing debugging feedback until you get full GDB stub functionality in place.

### TIP

A good way of debugging the stub itself is to enable remote debugging in GDB (`set remotedebug 1` ). This makes GDB display any communication between itself and the stub on the target. Consult the GDB file `remote.c` for details on the protocol.

##### Architecture files

`include/basetype.h`

Implement in full. Little effort.

`include/hal_arch.h`

The following macros are required for the stub: `HAL_SavedRegisters` , `HAL_BREAKPOINT` , `HAL_BREAKINST` , `HAL_BREAKINST_SIZE` , `HAL_GET_GDB_REGISTERS` , and `HAL_SET_GDB_REGISTERS` .

`include/hal_cache.h`

The macros in this file can be left as empty if caches are kept disabled. This is definitely the best way to start porting, avoiding cache problems entirely. The cache is not of much use until eCos can be used with applications anyway.

`include/hal_intr.h`

It is necessary to implement enough exception handling code to properly handle breakpoints.

As the porting job progresses, asynchronous break points (CYGDBG_HAL_DEBUG_GDB_BREAK_SUPPORT) may come in handy. These require a minimal interrupt system to be in place.

`include/hal_io.h`

Should be fully implemented. Usually zero effort.

`include/<arch>_regs.h`

Can be filled in piecemeal as the porting job progresses.

`include/<arch>_stub.h`

Redefine `NUMREGS` , `REGSIZE` , and `regnames` using the same register layout as GDB. The register definitions can be found in the `config/<arch>/tm-<arch>.h` file in the GDB sources. The definitions for the PowerPC were found in `config/rs6000/tm-rs6000.h` .

Discrepancies between what GDB expects and what is defined in the stub will show up when you use the `info reg` command in GDB (and know what the register contents on the target should be). Be careful to get the `REGSIZE` macro defined correctly.

`src/context.c`

Nothing here required by the stub.

`src/hal_misc.c`

The two functions `cyg_hal_invoke_constructors` and `cyg_hal_exception_handler` must be implemented. The former is the same on most architectures and the latter just needs to call `__handle_exception` .

`src/<arch>.ld`

The linker script must be properly defined.

`src/<arch>_stub.c`

This file must be fully implemented.

`__computeSignal` can be defined to just return SIGTRAP as a minimal implementation. Proper signal decoding may help debugging though.

Single-stepping can be implemented in one of two ways. Some architectures (such as the PowerPC) have hardware support to control single-stepping making it simple to implement. Other architectures require use of breakpoints to implement the functionality, which requires instruction decoding. Examples of the latter approach can be found in the ARM, MIPS, and MN10300 stubs. Implementing instruction decoding obviously requires more effort.

`src/vectors.S`

This is the core file of the architecture HAL. It is hard to define what the minimal implementation requirements are for stubs to work. It may be worth and/or necessary to do a full implementation of this file to start with, but here are some pointers anyway.

`_start` as defined for the PowerPC is about the minimum requirement, but you can ignore MMU and cache setup while working on the stub.

`__default_exception_vsr` and `restore_state` must preserve enough state to allow breakpoints without trashing CPU state for the application code. If you need asynchronous GDB breakpoints `__default_interrupt_vsr` must also be defined well enough to allow interrupts without trashing the CPU state of the interrupted application code.

Assorted tables also need to be defined, depending on how much of the exception and interrupt handlers is implemented.

##### Platform files

`include/hal_diag.h`

Shouldn't require any changes.

`include/plf_stub.h`

This file provides the interface to the platform stub functions for the `generic-stub.c` code.

The minimal stub (no asynchronous GDB breakpoints) only requires `HAL_STUB_PLATFORM_INIT_SERIAL` , `HAL_STUB_PLATFORM_GET_CHAR` , and `HAL_STUB_PLATFORM_PUT_CHAR` and the matching functions in `plf_stub.c` to be defined.

`src/<platform>.c`

This file defines `hal_hardware_init` which takes care of initializing the board. For the Cogent board this includes watchdog initialization and memory controller setup. Other boards may have different requirements.

`src/hal_diag.c`

This file defines three functions that provide simple diagnostics output; `hal_diag_init` , `hal_diag_write_char` , and `hal_diag_read_char` . Normally these would implement a very simple serial driver. They could also address an LCD or just some LEDs.

The simple serial driver for the Cogent board is implemented in a separate file, `cma_ser.c` , which is shared with the `plf_stub.c` file.

`src/plf_stub.c`

This file implements the serial driver needed by the GDB stub. The minimal stub only requires `init` , `putc` , and `getc` functions. A stub which supports asynchronous breakpoints also requires functions to handle serial interrupts. For example implementations see `cma_ser.c` or the `plf_stub.c` file for the MN10300 stdeval1 board.

#### Building the stub

1. Prepare a build directory, configuring eCos for `stubs` startup.

2. Disable all packages except eCos common HAL, infrastructure, <arch> common HAL, and <arch> <platform> board support.

3. Disable the HAL common options CYGFUN_HAL_COMMON_KERNEL_SUPPORT and CYGDBG_HAL_DEBUG_GDB_THREAD_SUPPORT.

Enable the HAL common option CYGDBG_HAL_DEBUG_GDB_INCLUDE_STUBS.

1. Build libtarget.

2. Change to the directory `hal/common/current/src/stubrom` and type `make` . This should result in an eCos GDB stub image file called `stubrom` . This can be converted to SRECord or binary format (using `objcopy` ) which can be used by EPROM burner or PROM emulator software.

#### Filling in the blanks

When a GDB stub has been written and is working, finishing the HAL port is pretty much a question of completing the header files and writing the functions that were not needed for the stub.

The eCos Hardware Abstraction Layer (HAL)
To Contents | To previous page | To next page