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:

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.

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:

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:

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

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