Chapter 7.  Porting Guide

7.1. Introduction

eCos has been designed to be fairly easy to port to new targets. A target is a specific platform (board) using a given architecture (CPU type). The porting is facilitated by the hierarchical layering of the eCos sources - all architecture and platform specific code is implemented in a HAL (hardware abstraction layer).

By porting the eCos HAL to a new target the core functionality of eCos (infra, kernel, uITRON, etc) will be able to run on the target. It may be necessary to add further platform specific code such as serial drivers, display drivers, ethernet drivers, etc. to get a fully capable system.

This document is intended as a help to the HAL porting process. Due to the nature of a porting job, it is impossible to give a complete description of what has to be done for each and every potential target. This should not be considered a clear-cut recipe - you will probably need to make some implementation decisions, tweak a few things, and just plain have to rely on common sense.

However, what is covered here should be a large part of the process. If you get stuck, you are advised to read the ecos-discuss archive where you may find discussions which apply to the problem at hand. You are also invited to ask questions on the ecos-discuss mailing list to help you resolve problems - but as is always the case with community lists, do not consider it an oracle for any and all questions. Use common sense - if you ask too many questions which could have been answered by reading the documentation, FAQ or source code, you are likely to be ignored.

This document will be continually improved by Red Hat engineers as time allows. Feedback and help with improving the document is sought, so if you have any comments at all, please do not hesitate to post them on ecos-discuss (please prefix the subject with [porting]).

At the moment this document is mostly an outline. There are many details to fill in before it becomes complete. Many places you'll just find a list of keywords / concepts that should be described (please post on ecos-discuss if there are areas you think are not covered).

All pages or sections where the caption ends in [TBD] contain little more than key words and/or random thoughts - there has been no work done as such on the content. The word FIXME may appear in the text to highlight places where information is missing.

7.2. HAL Structure

In order to write an eCos HAL it's a good idea to have at least a passing understanding of how the HAL interacts with the rest of the system.

7.2.1. HAL Classes

The eCos HAL consists of four HAL sub-classes. This table gives a brief description of each class and partly reiterates the description in Chapter 2, Architecture, Variant and Platform. The links refer to the on-line CVS tree (specifically to the sub-HALs used by the PowerPC MBX target).

HAL typeDescriptionFunctionality Overview

Common HAL

(hal/common)
Configuration options and functionality shared by all HALs. Generic debugging functionality, driver API, eCos/ROM monitor calling interface, and tests.

Architecture HAL

(hal/<architecture>/arch)
Functionality specific to the given architecture. Also default implementations of some functionality which can be overridden by variant or platform HALs. Architecture specific debugger functionality (handles single stepping, exception-to-signal conversion, etc.), exception/interrupt vector definitions and handlers, cache definition and control macros, context switching code, assembler functions for early system initialization, configuration options, and possibly tests.

Variant HAL

(hal/<architecture>/<variant>)
Some CPU architectures consist of a number variants, for example MIPS CPUs come in both 32 and 64 bit versions, and some variants have embedded features additional to the CPU core. Variant extensions to the architecture code (cache, exception/interrupt), configuration options, possibly drivers for variant on-core devices, and possibly tests.

Platform HAL

(hal/<architecture>/<platform>)
Contains functionality and configuration options specific to the platform. Early platform initialization code, platform memory layout specification, configuration options (processor speed, compiler options), diagnostic IO functions, debugger IO functions, platform specific extensions to architecture or variant code (off-core interrupt controller), and possibly tests.

Auxiliary HAL

(hal/<architecture>/<module>)
Some variants share common modules on the core. Motorola's PowerPC QUICC is an example of such a module. Module specific functionality (interrupt controller, simple device drivers), possibly tests.

7.2.2. File Descriptions

Listed below are the files found in various HALs, with a short description of what each file contains. When looking in existing HALs beware that they do not necessarily follow this naming scheme. If you are writing a new HAL, please try to follow it as closely as possible. Still, no two targets are the same, so sometimes it makes sense to use additional files.

7.2.2.1. Common HAL

FileDescription
include/dbg-thread-syscall.h Defines the thread debugging syscall function. This is used by the ROM monitor to access the thread debugging API in the RAM application. .
include/dbg-threads-api.h Defines the thread debugging API. .
include/drv_api.h Defines the driver API.
include/generic-stub.h Defines the generic stub features.
include/hal_if.h Defines the ROM/RAM calling interface API.
include/hal_misc.h Defines miscellaneous helper functions shared by all HALs.
include/hal_stub.h Defines eCos mappings of GDB stub features.
src/dbg-threads-syscall.c Thread debugging implementation.
src/drv_api.c Driver API implementation. Depending on configuration this provides either wrappers for the kernel API, or a minimal implementation of these features. This allows drivers to be written relying only on HAL features.
src/dummy.c Empty dummy file ensuring creation of libtarget.a.
src/generic-stub.c Generic GDB stub implementation. This provides the communication protocol used to communicate with GDB over a serial device or via the network.
src/hal_if.c ROM/RAM calling interface implementation. Provides wrappers from the calling interface API to the eCos features used for the implementation.
src/hal_misc.c Various helper functions shared by all platforms and architectures.
src/hal_stub.c Wrappers from eCos HAL features to the features required by the generic GDB stub.
src/stubrom/stubrom.c The file used to build eCos GDB stub images. Basically a cyg_start function with a hard coded breakpoint.
src/thread-packets.c More thread debugging related functions.
src/thread-pkts.h Defines more thread debugging related function.

7.2.2.2. Architecture HAL

Some architecture HALs may add extra files for architecture specific serial drivers, or for handling interrupts and exceptions if it makes sense.

Note that many of the definitions in these files are only conditionally defined - if the equivalent variant or platform headers provide the definitions, those override the generic architecture definitions.

FileDescription
include/arch.inc Various assembly macros used during system initialization.
include/basetype.h Endian, label, alignment, and type size definitions. These override common defaults in CYGPKG_INFRA.
include/hal_arch.h Saved register frame format, various thread, register and stack related macros.
include/hal_cache.h Cache definitions and cache control macros.
include/hal_intr.h Exception and interrupt definitions. Macros for configuring and controlling interrupts. eCos real-time clock control macros.
include/hal_io.h Macros for accessing IO devices.
include/<arch>_regs.h Architecture register definitions.
include/<arch>_stub.h Architecture stub definitions. In particular the register frame layout used by GDB. This may differ from the one used by eCos.
include/<arch>.inc Architecture convenience assembly macros.
src/<arch>.ld Linker macros.
src/context.S Functions handling context switching and setjmp/longjmp.
src/hal_misc.c Exception and interrupt handlers in C. Various other utility functions.
src/hal_mk_defs.c Used to export definitions from C header files to assembler header files.
src/hal_intr.c Any necessary interrupt handling functions.
src/<arch>stub.c Architecture stub code. Contains functions for translating eCos exceptions to UNIX signals and functions for single-stepping.
src/vectors.S Exception, interrupt and early initialization code.

7.2.2.3. Variant HAL

Some variant HALs may add extra files for variant specific serial drivers, or for handling interrupts/exceptions if it makes sense.

Note that these files may be mostly empty if the CPU variant can be controlled by the generic architecture macros. The definitions present are only conditionally defined - if the equivalent platform headers provide the definitions, those override the variant definitions.

FileDescription
include/var_arch.h Saved register frame format, various thread, register and stack related macros.
include/var_cache.h Cache related macros.
include/var_intr.h Interrupt related macros.
include/var_regs.h Extra register definitions for the CPU variant.
include/variant.inc Various assembly macros used during system initialization.
src/var_intr.c Interrupt functions if necessary.
src/var_misc.c hal_variant_init function and any necessary extra functions.
src/variant.S Interrupt handler table definition.
src/<arch>_<variant>.ld Linker macros.

7.2.2.4. Platform HAL

Extras files may be added for platform specific serial drivers. Extra files for handling interrupts and exceptions will be present if it makes sense.

FileDescription
include/hal_diag.h Defines functions used for HAL diagnostics output. This would normally be the ROM calling interface wrappers, but may also be the low-level IO functions themselves, saving a little overhead.
include/platform.inc Platform initialization code. This includes memory controller, vectors, and monitor initialization. Depending on the architecture, other things may need defining here as well: interrupt decoding, status register initialization value, etc.
include/plf_cache.h Platform specific cache handling.
include/plf_intr.h Platform specific interrupt handling.
include/plf_io.h PCI IO definitions and macros. May also be used to override generic HAL IO macros if the platform endianness differs from that of the CPU.
include/plf_stub.h Defines stub initializer and board reset details.
src/hal_diag.c May contain the low-level device drivers. But these may also reside in plf_stub.c
src/platform.S Memory controller setup macro, and if necessary interrupt springboard code.
src/plf_misc.c Platform initialization code.
src/plf_mk_defs.c Used to export definitions from C header files to assembler header files.
src/plf_stub.c Platform specific stub initialization and possibly the low-level device driver.

The platform HAL also contains files specifying the platform's memory layout. These files are located in include/pkgconf.

7.2.2.5. Auxiliary HAL

Auxiliary HALs contain whatever files are necessary to provide the required functionality. There are no predefined set of files required in an auxiliary HAL.

7.3. Virtual Vectors (eCos/ROM Monitor Calling Interface)

Virtually all eCos platforms provide full debugging capabilities via RedBoot. This environment contains not only debug stubs based on GDB, but also rich I/O support which can be exported to loaded programs. Such programs can take advantage of the I/O capabilities using a special ROM/RAM calling interface (also referred to as virtual vector table). eCos programs make use of the virtual vector mechanism implicitly. Non-eCos programs can access these functions using the support from the newlib library.

7.3.1. Virtual Vectors

What are virtual vectors, what do they do, and why are they needed?

"Virtual vectors" is the name of a table located at a static location in the target address space. This table contains 64 vectors that point to service functions or data.

The fact that the vectors are always placed at the same location in the address space means that both ROM and RAM startup configurations can access these and thus the services pointed to.

The primary goal is to allow services to be provided by ROM configurations (ROM monitors such as RedBoot in particular) with clients in RAM configurations being able to use these services.

Without the table of pointers this would be impossible since the ROM and RAM applications would be linked separately - in effect having separate name spaces - preventing direct references from one to the other.

This decoupling of service from client is needed by RedBoot, allowing among other things debugging of applications which do not contain debugging client code (stubs).

7.3.1.1. Initialization (or Mechanism vs. Policy)

Virtual vectors are a mechanism for decoupling services from clients in the address space.

The mechanism allows services to be implemented by a ROM monitor, a RAM application, to be switched out at run-time, to be disabled by installing pointers to dummy functions, etc.

The appropriate use of the mechanism is specified loosely by a policy. The general policy dictates that the vectors are initialized in whole by ROM monitors (built for ROM or RAM), or by stand-alone applications.

For configurations relying on a ROM monitor environment, the policy is to allow initialization on a service by service basis. The default is to initialize all services, except COMMS services since these are presumed to already be carrying a communication session to the debugger / console which was used for launching the application. This means that the bulk of the code gets tested in normal builds, and not just once in a blue moon when building new stubs or a ROM configuration.

The configuration options are written to comply with this policy by default, but can be overridden by the user if desired. Defaults are:

  • For application development: the ROM monitor provides debugging and diagnostic IO services, the RAM application relies on these by default.
  • For production systems: the application contains all the necessary services.

7.3.1.2. Pros and Cons of Virtual Vectors

There are pros and cons associated with the use of virtual vectors. We do believe that the pros generally outweigh the cons by a great margin, but there may be situations where the opposite is true.

The use of the services are implemented by way of macros, meaning that it is possible to circumvent the virtual vectors if desired. There is (as yet) no implementation for doing this, but it is possible.

Here is a list of pros and cons:

Pro: Allows debugging without including stubs
This is the primary reason for using virtual vectors. It allows the ROM monitor to provide most of the debugging infrastructure, requiring only the application to provide hooks for asynchronous debugger interrupts and for accessing kernel thread information.
Pro: Allows debugging to be initiated from arbitrary channel
While this is only true where the application does not actively override the debugging channel setup, it is a very nice feature during development. In particular it makes it possible to launch (and/or debug) applications via Ethernet even though the application configuration does not contain networking support.
Pro: Image smaller due to services being provided by ROM monitor
All service functions except HAL IO are included in the default configuration. But if these are all disabled the image for download will be a little smaller. Probably doesn't matter much for regular development, but it is a worthwhile saving for the 20000 daily tests run in the Red Hat eCos test farm.
Con: The vectors add a layer of indirection, increasing application size and reducing performance.

The size increase is a fraction of what is required to implement the services. So for RAM configurations there is a net saving, while for ROM configurations there is a small overhead.

The performance loss means little for most of the services (of which the most commonly used is diagnostic IO which happens via polled routines anyway).

Con: The layer of indirection is another point of failure.
The concern primarily being that of vectors being trashed by rogue writes from bad code, causing a complete loss of the service and possibly a crash. But this does not differ much from a rogue write to anywhere else in the address space which could cause the same amount of mayhem. But it is arguably an additional point of failure for the service in question.
Con: All the indirection stuff makes it harder to bring a HAL up

This is a valid concern. However, seeing as most of the code in question is shared between all HALs and should remain unchanged over time, the risk of it being broken when a new HAL is being worked on should be minimal.

When starting a new port, be sure to implement the HAL IO drivers according to the scheme used in other drivers, and there should be no problem.

However, it is still possible to circumvent the vectors if they are suspect of causing problems: simply change the HAL_DIAG_INIT and HAL_DIAG_WRITE_CHAR macros to use the raw IO functions.

7.3.1.3. Available services

The hal_if.h file in the common HAL defines the complete list of available services. A few worth mentioning in particular:

  • COMMS services. All HAL IO happens via the communication channels.
  • uS delay. Fine granularity (busy wait) delay function.
  • Reset. Allows a software initiated reset of the board.

7.3.2. The COMMS channels

As all HAL IO happens via the COMMS channels these deserve to be described in a little more detail. In particular the controls of where diagnostic output is routed and how it is treated to allow for display in debuggers.

7.3.2.1. Console and Debugging Channels

There are two COMMS channels - one for console IO and one for debugging IO. They can be individually configured to use any of the actual IO ports (serial or Ethernet) available on the platform.

The console channel is used for any IO initiated by calling the diag_*() functions. Note that these should only be used during development for debugging, assertion and possibly tracing messages. All proper IO should happen via proper devices. This means it should be possible to remove the HAL device drivers from production configurations where assertions are disabled.

The debugging channel is used for communication between the debugger and the stub which remotely controls the target for the debugger (the stub runs on the target). This usually happens via some protocol, encoding commands and replies in some suitable form.

Having two separate channels allows, e.g., for simple logging without conflicts with the debugger or interactive IO which some debuggers do not allow.

7.3.2.2. Mangling

As debuggers usually have a protocol using specialized commands when communicating with the stub on the target, sending out text as raw ASCII from the target on the same channel will either result in protocol errors (with loss of control over the target) or the text may just be ignored as junk by the debugger.

To get around this, some debuggers have a special command for text output. Mangling is the process of encoding diagnostic ASCII text output in the form specified by the debugger protocol.

When it is necessary to use mangling, i.e. when writing console output to the same port used for debugging, a mangler function is installed on the console channel which mangles the text and passes it on to the debugger channel.

7.3.2.3. Controlling the Console Channel

Console output configuration is either inherited from the ROM monitor launching the application, or it is specified by the application. This is controlled by the new option CYGSEM_HAL_VIRTUAL_VECTOR_INHERIT_CONSOLE which defaults to enabled when the configuration is set to use a ROM monitor.

If the user wants to specify the console configuration in the application image, there are two new options that are used for this.

Defaults are to direct diagnostic output via a mangler to the debugging channel (CYGDBG_HAL_DIAG_TO_DEBUG_CHAN enabled). The mangler type is controlled by the option CYGSEM_HAL_DIAG_MANGLER. At present there are only two mangler types:

GDB
This causes a mangler appropriate for debugging with GDB to be installed on the console channel.
None
This causes a NULL mangler to be installed on the console channel. It will redirect the IO to/from the debug channel without mangling of the data. This option differs from setting the console channel to the same IO port as the debugging channel in that it will keep redirecting data to the debugging channel even if that is changed to some other port.

Finally, by disabling CYGDBG_HAL_DIAG_TO_DEBUG_CHAN, the diagnostic output is directed in raw form to the specified console IO port.

In summary this results in the following common configuration scenarios for RAM startup configurations:

  • For regular debugging with diagnostic output appearing in the debugger, mangling is enabled and stubs disabled.

    Diagnostic output appears via the debugging channel as initiated by the ROM monitor, allowing for correct behavior whether the application was launched via serial or Ethernet, from the RedBoot command line or from a debugger.

  • For debugging with raw diagnostic output, mangling is disabled.

    Debugging session continues as initiated by the ROM monitor, whether the application was launched via serial or Ethernet. Diagnostic output is directed at the IO port configured in the application configuration.

    [Note]Note:

    There is one caveat to be aware of. If the application uses proper devices (be it serial or Ethernet) on the same ports as those used by the ROM monitor, the connections initiated by the ROM monitor will be terminated.

And for ROM startup configurations:

  • Production configuration with raw output and no debugging features (configured for RAM or ROM), mangling is disabled, no stubs are included.

    Diagnostic output appears (in unmangled form) on the specified IO port.

  • RedBoot configuration, includes debugging features and necessary mangling.

    Diagnostic and debugging output port is auto-selected by the first connection to any of the supported IO ports. Can change from interactive mode to debugging mode when a debugger is detected - when this happens a mangler will be installed as required.

  • GDB stubs configuration (obsoleted by RedBoot configuration), includes debugging features, mangling is hardwired to GDB protocol.

    Diagnostic and debugging output is hardwired to configured IO ports, mangling is hardwired.

7.3.2.4. Footnote: Design Reasoning for Control of Console Channel

The current code for controlling the console channel is a replacement for an older implementation which had some shortcomings which addressed by the new implementation.

This is what the old implementation did: on initialization it would check if the CDL configured console channel differed from the active debug channel - and if so, set the console channel, thereby disabling mangling.

The idea was that whatever channel was configured to be used for console (i.e., diagnostic output) in the application was what should be used. Also, it meant that if debug and console channels were normally the same, a changed console channel would imply a request for unmangled output.

But this prevented at least two things:

  • It was impossible to inherit the existing connection by which the application was launched (either by RedBoot commands via telnet, or by via a debugger).

    This was mostly a problem on targets supporting Ethernet access since the diagnostic output would not be returned via the Ethernet connection, but on the configured serial port.

    The problem also occurred on any targets with multiple serial ports where the ROM monitor was configured to use a different port than the CDL defaults.

  • Proper control of when to mangle or just write out raw ASCII text.

    Sometimes it's desirable to disable mangling, even if the channel specified is the same as that used for debugging. This usually happens if GDB is used to download the application, but direct interaction with the application on the same channel is desired (GDB protocol only allows output from the target, no input).

7.3.3. The calling Interface API

The calling interface API is defined by hal_if.h and hal_if.c in hal/common.

The API provides a set of services. Different platforms, or different versions of the ROM monitor for a single platform, may implement fewer or extra service. The table has room for growth, and any entries which are not supported map to a NOP-service (when called it returns 0 (false)).

A client of a service should either be selected by configuration, or have suitable fall back alternatives in case the feature is not implemented by the ROM monitor.

[Note]Note:

Checking for unimplemented service when this may be a data field/pointer instead of a function: suggest reserving the last entry in the table as the NOP-service pointer. Then clients can compare a service entry with this pointer to determine whether it's initialized or not.

The header file cyg/hal/hal_if.h defines the table layout and accessor macros (allowing primitive type checking and alternative implementations should it become necessary).

The source file hal_if.c defines the table initialization function. All HALs should call this during platform initialization - the table will get initialized according to configuration. Also defined here are wrapper functions which map between the calling interface API and the API of the used eCos functions.

7.3.3.1. Implemented Services

This is a brief description of the services, some of which are described in further detail below.

VERSION
Version of table. Serves as a way to check for how many features are available in the table. This is the index of the last service in the table.
KILL_VECTOR
[Presently unused by the stub code, but initialized] This vector defines a function to execute when the system receives a kill signal from the debugger. It is initialized with the reset function (see below), but the application (or eCos) can override it if necessary.
CONSOLE_PROCS
The communication procedure table used for console IO (see Section 7.3.4, “IO channels”.
DEBUG_PROCS
The communication procedure table used for debugger IO (see Section 7.3.4, “IO channels”).
FLUSH_DCACHE
Flushes the data cache for the specified region. Some implementations may flush the entire data cache.
FLUSH_ICACHE
Flushes (invalidates) the instruction cache for the specified region. Some implementations may flush the entire instruction cache.
SET_DEBUG_COMM
Change debugging communication channel.
SET_CONSOLE_COMM
Change console communication channel.
DBG_SYSCALL
Vector used to communication between debugger functions in ROM and in RAM. RAM eCos configurations may install a function pointer here which the ROM monitor uses to get thread information from the kernel running in RAM.
RESET
Resets the board on call. If it is not possible to reset the board from software, it will jump to the ROM entry point which will perform a "software" reset of the board.
CONSOLE_INTERRUPT_FLAG
Set if a debugger interrupt request was detected while processing console IO. Allows the actual breakpoint action to be handled after return to RAM, ensuring proper backtraces etc.
DELAY_US
Will delay the specified number of microseconds. The precision is platform dependent to some extend - a small value (<100us) is likely to cause bigger delays than requested.
FLASH_CFG_OP
For accessing configuration settings kept in flash memory.
INSTALL_BPT_FN
Installs a breakpoint at the specified address. This is used by the asynchronous breakpoint support (see ).

7.3.3.2. Compatibility

When a platform is changed to support the calling interface, applications will use it if so configured. That means that if an application is run on a platform with an older ROM monitor, the service is almost guaranteed to fail.

For this reason, applications should only use Console Comm for HAL diagnostics output if explicitly configured to do so (CYGSEM_HAL_VIRTUAL_VECTOR_DIAG).

As for asynchronous GDB interrupts, the service will always be used. This is likely to cause a crash under older ROM monitors, but this crash may be caught by the debugger. The old workaround still applies: if you need asynchronous breakpoints or thread debugging under older ROM monitors, you may have to include the debugging support when configuring eCos.

7.3.3.3. Implementation details

During the startup of a ROM monitor, the calling table will be initialized. This also happens if eCos is configured not to rely on a ROM monitor.

[Note]Note:

There is reserved space (256 bytes) for the vector table whether it gets used or not. This may be something that we want to change if we ever have to shave off every last byte for a given target.

If thread debugging features are enabled, the function for accessing the thread information gets registered in the table during startup of a RAM startup configuration.

Further implementation details are described where the service itself is described.

7.3.3.4. New Platform Ports

The hal_platform_init() function must call hal_if_init().

The HAL serial driver must, when called via cyg_hal_plf_comms_init() must initialize the communication channels.

The reset() function defined in hal_if.c will attempt to do a hardware reset, but if this fails it will fall back to simply jumping to the reset entry-point. On most platforms the startup initialization will go a long way to reset the target to a sane state (there will be exceptions, of course). For this reason, make sure to define HAL_STUB_PLATFORM_RESET_ENTRY in plf_stub.h.

All debugging features must be in place in order for the debugging services to be functional. See general platform porting notes.

7.3.3.5. New architecture ports

There are no specific requirements for a new architecture port in order to support the calling interface, but the basic debugging features must be in place. See general architecture porting notes.

7.3.4. IO channels

The calling interface provides procedure tables for all IO channels on the platform. These are used for console (diagnostic) and debugger IO, allowing a ROM monitor to provided all the needed IO routines. At the same time, this makes it easy to switch console/debugger channels at run-time (the old implementation had hardwired drivers for console and debugger IO, preventing these to change at run-time).

The hal_if provides wrappers which interface these services to the eCos infrastructure diagnostics routines. This is done in a way which ensures proper string mangling of the diagnostics output when required (e.g. O-packetization when using a GDB compatible ROM monitor).

7.3.4.1. Available Procedures

This is a brief description of the procedures

CH_DATA
Pointer to the controller IO base (or a pointer to a per-device structure if more data than the IO base is required). All the procedures below are called with this data item as the first argument.
WRITE
Writes the buffer to the device.
READ
Fills a buffer from the device.
PUTC
Write a character to the device.
GETC
Read a character from the device.
CONTROL

Device feature control. Second argument specifies function:

SETBAUD
Changes baud rate.
GETBAUD
Returns the current baud rate.
INSTALL_DBG_ISR
[Unused]
REMOVE_DBG_ISR
[Unused]
IRQ_DISABLE
Disable debugging receive interrupts on the device.
IRQ_ENABLE
Enable debugging receive interrupts on the device.
DBG_ISR_VECTOR
Returns the ISR vector used by the device for debugging receive interrupts.
SET_TIMEOUT
Set GETC timeout in milliseconds.
FLUSH_OUTPUT
Forces driver to flush data in its buffers. Note that this may not affect hardware buffers (e.g. FIFOs).
DBG_ISR
ISR used to handle receive interrupts from the device (see ).
GETC_TIMEOUT
Read a character from the device with timeout.

7.3.4.2. Usage

The standard eCos diagnostics IO functions use the channel procedure table when CYGSEM_HAL_VIRTUAL_VECTOR_DIAG is enabled. That means that when you use diag_printf (or the libc printf function) the stream goes through the selected console procedure table. If you use the virtual vector function SET_CONSOLE_COMM you can change the device which the diagnostics output goes to at run-time.

You can also use the table functions directly if desired (regardless of the CYGSEM_HAL_VIRTUAL_VECTOR_DIAG setting - assuming the ROM monitor provides the services). Here is a small example which changes the console to use channel 2, fetches the comm procs pointer and calls the write function from that table, then restores the console to the original channel:

#define T "Hello World!\n"

int
main(void)
{
    hal_virtual_comm_table_t* comm;
    int cur = CYGACC_CALL_IF_SET_CONSOLE_COMM(CYGNUM_CALL_IF_SET_COMM_ID_QUERY_CURRENT);

    CYGACC_CALL_IF_SET_CONSOLE_COMM(2);

    comm = CYGACC_CALL_IF_CONSOLE_PROCS();
    CYGACC_COMM_IF_WRITE(*comm, T, strlen(T));

    CYGACC_CALL_IF_SET_CONSOLE_COMM(cur);
}

Beware that if doing something like the above, you should only do it to a channel which does not have GDB at the other end: GDB ignores raw data, so you would not see the output.

7.3.4.3. Compatibility

The use of this service is controlled by the option CYGSEM_HAL_VIRTUAL_VECTOR_DIAG which is disabled per default on most older platforms (thus preserving backwards compatibility with older stubs). On newer ports, this option should always be set.

7.3.4.4. Implementation Details

There is an array of procedure tables (raw comm channels) for each IO device of the platform which get initialized by the ROM monitor, or optionally by a RAM startup configuration (allowing the RAM configuration to take full control of the target). In addition to this, there's a special table which is used to hold mangler procedures.

The vector table defines which of these channels are selected for console and debugging IO respectively: console entry can be empty, point to mangler channel, or point to a raw channel. The debugger entry should always point to a raw channel.

During normal console output (i.e., diagnostic output) the console table will be used to handle IO if defined. If not defined, the debug table will be used.

This means that debuggers (such as GDB) which require text streams to be mangled (O-packetized in the case of GDB), can rely on the ROM monitor install mangling IO routines in the special mangler table and select this for console output. The mangler will pass the mangled data on to the selected debugging channel.

If the eCos configuration specifies a different console channel from that used by the debugger, the console entry will point to the selected raw channel, thus overriding any mangler provided by the ROM monitor.

See hal_if_diag_* routines in hal_if.c for more details of the stream path of diagnostic output. See cyg_hal_gdb_diag_*() routines in hal_stub.c for the mangler used for GDB communication.

7.3.4.5. New Platform Ports

Define CDL options CYGNUM_HAL_VIRTUAL_VECTOR_COMM_CHANNELS, CYGNUM_HAL_VIRTUAL_VECTOR_DEBUG_CHANNEL, and CYGNUM_HAL_VIRTUAL_VECTOR_CONSOLE_CHANNEL.

If CYGSEM_HAL_VIRTUAL_VECTOR_DIAG is set, make sure the infra diag code uses the hal_if diag functions:

#define HAL_DIAG_INIT() hal_if_diag_init()
#define HAL_DIAG_WRITE_CHAR(_c_) hal_if_diag_write_char(_c_)
#define HAL_DIAG_READ_CHAR(_c_) hal_if_diag_read_char(&_c_)

In addition to the above functions, the platform HAL must also provide a function cyg_hal_plf_comms_init which initializes the drivers and the channel procedure tables.

Most of the other functionality in the table is more or less possible to copy unchanged from existing ports. Some care is necessary though to ensure the proper handling of interrupt vectors and timeouts for various devices handled by the same driver. See PowerPC/Cogent platform HAL for an example implementation.

[Note]Note:

When vector table console code is not used, the platform HAL must map the HAL_DIAG_INIT, HAL_DIAG_WRITE_CHAR and HAL_DIAG_READ_CHAR macros directly to the low-level IO functions, hardwired to use a compile-time configured channel.

[Note]Note:

On old ports the hardwired HAL_DIAG_INIT, HAL_DIAG_WRITE_CHAR and HAL_DIAG_READ_CHAR implementations will also contain code to O-packetize the output for GDB. This should not be adopted for new ports! On new ports the ROM monitor is guaranteed to provide the necessary mangling via the vector table. The hardwired configuration should be reserved for ROM startups where achieving minimal image size is crucial.

7.4. HAL Coding Conventions

To get changes and larger submissions included into the eCos source repository, we ask that you adhere to a set of coding conventions. The conventions are defined as an attempt to make a consistent tree. Consistency makes it easier for people to read, understand and maintain the code, which is important when many people work on the same project.

The below is only a brief, and probably incomplete, summary of the rules. Please look through files in the area where you are making changes to get a feel for any additional conventions. Also feel free to ask on the list if you have specific questions.

7.4.1. Implementation issues

There are a few implementation issues that should be kept in mind:

HALs
HALs must be written in C and assembly only. C++ must not be used. This is in part to keep the HALs simple since this is usually the first part of eCos a newcomer will see, and in part to maintain the existing de facto standard.
IO access
Use HAL IO access macros for code that might be reused on different platforms than the one you are writing it for.
MMU
If it is necessary to use the MMU (e.g., to prevent caching of IO areas), use a simple 1-1 mapping of memory if possible. On most platforms where using the MMU is necessary, it will be possible to achieve the 1-1 mapping using the MMU's provision for mapping large continuous areas (hardwired TLBs or BATs). This reduces the footprint (no MMU table) and avoids execution overhead (no MMU-related exceptions).
Assertions
The code should contain assertions to validate argument values, state information and any assumptions the code may be making. Assertions are not enabled in production builds, so liberally sprinkling assertions throughout the code is good.
Testing
The ability to test your code is very important. In general, do not add new code to the eCos runtime unless you also add a new test to exercise that code. The test also serves as an example of how to use the new code.

7.4.2. Source code details

Line length
Keep line length below 78 columns whenever possible.
Comments
Whenever possible, use // comments instead of /**/.
Indentation

Use spaces instead of TABs. Indentation level is 4. Braces start on the same line as the expression. See below for emacs mode details.

;;=================================================================
;; eCos C/C++ mode Setup.
;;
;; bsd mode: indent = 4
;; tail comments are at col 40.
;; uses spaces not tabs in C;

(defun ecos-c-mode ()
  "C mode with adjusted defaults for use with the eCos sources."
  (interactive)
  (c++-mode)
  (c-set-style "bsd")
  (setq comment-column 40)
  (setq indent-tabs-mode nil)
  (show-paren-mode 1)
  (setq c-basic-offset 4)

  (set-variable 'add-log-full-name "Your Name")
  (set-variable 'add-log-mailing-address "Your email address"))

(defun ecos-asm-mode ()
  "ASM mode with adjusted defaults for use with the eCos sources."
  (interactive)
  (setq comment-column 40)
  (setq indent-tabs-mode nil)
  (asm-mode)
  (setq c-basic-offset 4)

  (set-variable 'add-log-full-name "Your Name")
  (set-variable 'add-log-mailing-address "Your email address"))

(setq auto-mode-alist
  (append '(("/local/ecc/.*\\.C$"   . ecos-c-mode)
  ("/local/ecc/.*\\.cc$"  . ecos-c-mode)
  ("/local/ecc/.*\\.cpp$" . ecos-c-mode)
  ("/local/ecc/.*\\.inl$" . ecos-c-mode)
  ("/local/ecc/.*\\.c$"   . ecos-c-mode)
  ("/local/ecc/.*\\.h$"   . ecos-c-mode)
  ("/local/ecc/.*\\.S$"   . ecos-asm-mode)
  ("/local/ecc/.*\\.inc$" . ecos-asm-mode)
  ("/local/ecc/.*\\.cdl$" . tcl-mode)
) auto-mode-alist))

7.4.3. Nested Headers

In order to allow platforms to define all necessary details, while still maintaining the ability to share code between common platforms, all HAL headers are included in a nested fashion.

The architecture header (usually hal_XXX.h) includes the variant equivalent of the header (var_XXX.h) which in turn includes the platform equivalent of the header (plf_XXX.h).

All definitions that may need to be overridden by a platform are then only conditionally defined, depending on whether a lower layer has already made the definition:

hal_intr.h:     #include <var_intr.h>

                #ifndef MACRO_DEFINED
                # define MACRO ...
                # define MACRO_DEFINED
                #endif



var_intr.h:     #include <plf_intr.h>

                #ifndef MACRO_DEFINED
                # define MACRO ...
                # define MACRO_DEFINED
                #endif


plf_intr.h:

                # define MACRO ...
                # define MACRO_DEFINED

This means a platform can opt to rely on the variant or architecture implementation of a feature, or implement it itself.

7.5. Platform HAL Porting

This is the type of port that takes the least effort. It basically consists of describing the platform (board) for the HAL: memory layout, early platform initialization, interrupt controllers, and a simple serial device driver.

Doing a platform port requires a preexisting architecture and possibly a variant HAL port.

7.5.1. HAL Platform Porting Process

7.5.1.1. Brief overview

The easiest way to make a new platform HAL is simply to copy an existing platform HAL of the same architecture/variant and change all the files to match the new one. In case this is the first platform for the architecture/variant, a platform HAL from another architecture should be used as a template.

The best way to start a platform port is to concentrate on getting RedBoot to run. RedBoot is a simpler environment than full eCos, it does not use interrupts or threads, but covers most of the basic startup requirements.

RedBoot normally runs out of FLASH or ROM and provides program loading and debugging facilities. This allows further HAL development to happen using RAM startup configurations, which is desirable for the simple reason that downloading an image which you need to test is often many times faster than either updating a flash part, or indeed, erasing and reprogramming an EPROM.

There are two approaches to getting to this first goal:

  1. The board is equipped with a ROM monitor which allows "load and go" of ELF, binary, S-record or some other image type which can be created using objcopy. This allows you to develop RedBoot by downloading and running the code (saving time).

    When the stub is running it is a good idea to examine the various hardware registers to help you write the platform initialization code.

    Then you may have to fiddle a bit going through step two (getting it to run from ROM startup). If at all possible, preserve the original ROM monitor so you can revert to it if necessary.

  2. The board has no ROM monitor. You need to get the platform initialization and stub working by repeatedly making changes, updating flash or EPROM and testing the changes. If you are lucky, you have a JTAG or similar CPU debugger to help you. If not, you will probably learn to appreciate LEDs. This approach may also be needed during the initial phase of moving RedBoot from RAM startup to ROM, since it is very unlikely to work first time.

7.5.1.2. Step-by-step

Given that no two platforms are exactly the same, you may have to deviate from the below. Also, you should expect a fair amount of fiddling - things almost never go right the first time. See the hints section below for some suggestions that might help debugging.

The description below is based on the HAL layout used in the MIPS, PC and MN10300 HALs. Eventually all HALs should be converted to look like these - but in a transition period there will be other HALs which look substantially different. Please try to adhere to the following as much is possible without causing yourself too much grief integrating with a HAL which does not follow this layout.

7.5.1.2.1. Minimal requirements

These are the changes you must make before you attempt to build RedBoot. You are advised to read all the sources though.

  1. Copy an existing platform HAL from the same or another architecture. Rename the files as necessary to follow the standard: CDL and MLT related files should contain the <arch>_<variant>_<platform> triplet.
  2. Adjust CDL options. Primarily option naming, real-time clock/counter, and CYGHWR_MEMORY_LAYOUT variables, but also other options may need editing. Look through the architecture/variant CDL files to see if there are any requirements/features which where not used on the platform you copied. If so, add appropriate ones. See Section 7.5.2, “HAL Platform CDL” for more details.
  3. Add the necessary packages and target descriptions to the top-level ecos.db file. See Section 7.5.2.1, “eCos Database”. Initially, the target entry should only contain the HAL packages. Other hardware support packages will be added later.
  4. Adjust the MLT files in include/pkgconf to match the memory layout on the platform. For initial testing it should be enough to just hand edit .h and .ldi files, but eventually you should generate all files using the memory layout editor in the configuration tool. See Section 7.5.3, “Platform Memory Layout” for more details.
  5. Edit the misc/redboot_<STARTUP>.ecm for the startup type you have chosen to begin with. Rename any platform specific options and remove any that do not apply. In the cdl_configuration section, comment out any extra packages that are added, particularly packages such as CYGPKG_IO_FLASH and CYGPKG_IO_ETH_DRIVERS. These are not needed for initial porting and will be added back later.
  6. If the default IO macros are not correct, override them in plf_io.h. This may be necessary if the platform uses a different endianness from the default for the CPU.
  7. Leave out/comment out code that enables caches and/or MMU if possible. Execution speed will not be a concern until the port is feature complete.
  8. Implement a simple serial driver (polled mode only). Make sure the initialization function properly hooks the procedures up in the virtual vector IO channel tables. RedBoot will call the serial driver via these tables.

    By copying an existing platform HAL most of this code will be already done, and will only need the platform specific hardware access code to be written.

  9. Adjust/implement necessary platform initialization. This can be found in platform.inc and platform.S files (ARM: hal_platform_setup.h and <platform>_misc.c, PowerPC: <platform>.S). This step can be postponed if you are doing a RAM startup RedBoot first and the existing ROM monitor handles board initialization.
  10. Define HAL_STUB_PLATFORM_RESET (optionally empty) and HAL_STUB_PLATFORM_RESET_ENTRY so that RedBoot can reset-on-detach - this is very handy, often removing the need for physically resetting the board between downloads.

You should now be able to build RedBoot. For ROM startup:

% ecosconfig new <target_name> redboot
% ecosconfig import $(ECOS_REPOSITORY)/hal/<architecture>/<platform>/<version>/misc/redboot_ROM.ecm
% ecosconfig tree
% make

You may have to make further changes than suggested above to get the make command to succeed. But when it does, you should find a RedBoot image in install/bin. To program this image into flash or EPROM, you may need to convert to some other file type, and possibly adjust the start address. When you have the correct objcopy command to do this, add it to the CYGBLD_BUILD_GDB_STUBS custom build rule in the platform CDL file.

Having updated the flash/EPROM on the board, you should see output on the serial port looking like this when powering on the board:

RedBoot(tm) bootstrap and debug environment [ROMRAM]
Non-certified release, version UNKNOWN - built 15:42:24, Mar 14 2002

Platform: <PLATFORM> (<ARCHITECTURE> <VARIANT>)
Copyright (C) 2000, 2001, 2002, Free Software Foundation, Inc.

RAM: 0x00000000-0x01000000, 0x000293e8-0x00ed1000 available
FLASH: 0x24000000 - 0x26000000, 256 blocks of 0x00020000 bytes each.
RedBoot> 

If you do not see this output, you need to go through all your changes and figure out what's wrong. If there's a user programmable LED or LCD on the board it may help you figure out how far RedBoot gets before it hangs. Unfortunately there's no good way to describe what to do in this situation - other than that you have to play with the code and the board.

7.5.1.2.2. Adding features

Now you should have a basic RedBoot running on the board. This means you have a the correct board initialization and a working serial driver. It's time to flesh out the remaining HAL features.

  1. Reset. As mentioned above it is desirable to get the board to reset when GDB disconnects. When GDB disconnects it sends RedBoot a kill-packet, and RedBoot first calls HAL_STUB_PLATFORM_RESET(), attempting to perform a software-invoked reset. Most embedded CPUs/boards have a watchdog which is capable of triggering a reset. If your target does not have a watchdog, leave HAL_STUB_PLATFORM_RESET() empty and rely on the fallback approach.

    If HAL_STUB_PLATFORM_RESET() did not cause a reset, RedBoot will jump to HAL_STUB_PLATFORM_RESET_ENTRY - this should be the address where the CPU will start execution after a reset. Re-initializing the board and drivers will usually be good enough to make a hardware reset unnecessary.

    After the reset caused by the kill-packet, the target will be ready for GDB to connect again. During a days work, this will save you from pressing the reset button many times.

    Note that it is possible to disconnect from the board without causing it to reset by using the GDB command "detach".

  2. Single-stepping is necessary for both instruction-level debugging and for breakpoint support. Single-stepping support should already be in place as part of the architecture/variant HAL, but you want to give it a quick test since you will come to rely on it.
  3. Real-time clock interrupts drive the eCos scheduler clock. Many embedded CPUs have an on-core timer (e.g. SH) or decrementer (e.g. MIPS, PPC) that can be used, and in this case it will already be supported by the architecture/variant HAL. You only have to calculate and enter the proper CYGNUM_HAL_RTC_CONSTANTS definitions in the platform CDL file.

    On some targets it may be necessary to use a platform-specific timer source for driving the real-time clock. In this case you also have to enter the proper CDL definitions, but must also define suitable versions of the HAL_CLOCK_XXXX macros.

  4. Interrupt decoding usually differs between platforms because the number and type of devices on the board differ. In plf_intr.h (ARM: hal_platform_ints.h) you must either extend or replace the default vector definitions provided by the architecture or variant interrupt headers. You may also have to define HAL_INTERRUPT_XXXX control macros.
  5. Caching may also differ from architecture/variant definitions. This maybe just the cache sizes, but there can also be bigger differences for example if the platform supports 2nd level caches.

    When cache definitions are in place, enable the caches on startup. First verify that the system is stable for RAM startups, then build a new RedBoot and install it. This will test if caching, and in particular the cache sync/flush operations, also work for ROM startup.

  6. Asynchronous breakpoints allow you to stop application execution and enter the debugger. Asynchronous breakpoint details are described in .

You should now have a completed platform HAL port. Verify its stability and completeness by running all the eCos tests and fix any problems that show up (you have a working RedBoot now, remember! That means you can debug the code to see why it fails).

Given the many configuration options in eCos, there may be hidden bugs or missing features that do not show up even if you run all the tests successfully with a default configuration. A comprehensive test of the entire system will take many configuration permutations and many many thousands of tests executed.

7.5.1.3. Hints

  • JTAG or similar CPU debugging hardware can greatly reduce the time it takes to write a HAL port since you always have full visibility of what the CPU is doing.
  • LEDs can be your friends if you don't have a JTAG device. Especially in the start of the porting effort if you don't already have a working ROM monitor on the target. Then you have to get a basic RedBoot working while basically being blindfolded. The LED can make it little easier, as you'll be able to do limited tracking of program flow and behavior by switching the LED on and off. If the board has multiple LEDs you can show a number (using binary notation with the LEDs) and sprinkle code which sets different numbers throughout the code.
  • Debugging the interrupt processing is possible if you are careful with the way you program the very early interrupt entry handling. Write it so that as soon as possible in the interrupt path, taking a trap (exception) does not harm execution. See the SH vectors.S code for an example. Look for cyg_hal_default_interrupt_vsr and the label cyg_hal_default_interrupt_vsr_bp_safe, which marks the point after which traps/single-stepping is safe.

    Being able to display memory content, CPU registers, interrupt controller details at the time of an interrupt can save a lot of time.

  • Using assertions is a good idea. They can sometimes reveal subtle bugs or missing features long before you would otherwise have found them, let alone notice them.

    The default eCos configuration does not use assertions, so you have to enable them by switching on the option CYGPKG_INFRA_DEBUG in the infra package.

  • The idle loop can be used to help debug the system.

    Triggering clock from the idle loop is a neat trick for examining system behavior either before interrupts are fully working, or to speed up "the clock".

    Use the idle loop to monitor and/or print out variables or hardware registers.

  • hal_mk_defs is used in some of the HALs (ARM, SH) as a way to generate assembler symbol definitions from C header files without imposing an assembler/C syntax separation in the C++ header files.

7.5.2. HAL Platform CDL

The platform CDL both contains details necessary for the building of eCos, and platform-specific configuration options. For this reason the options differ between platforms, and the below is just a brief description of the most common options.

See Components Writers Guide for more details on CDL. Also have a quick look around in existing platform CDL files to get an idea of what is possible and how various configuration issues can be represented with CDL.

7.5.2.1. eCos Database

The eCos configuration system is made aware of a package by adding a package description in ecos.db. As an example we use the TX39/JMR3904 platform:

package CYGPKG_HAL_MIPS_TX39_JMR3904 {
    alias     { "Toshiba JMR-TX3904 board" hal_tx39_jmr3904 tx39_jmr3904_hal }
    directory hal/mips/jmr3904
    script    hal_mips_tx39_jmr3904.cdl
    hardware
    description "
        The JMR3904 HAL package should be used when targeting the
        actual hardware. The same package can also be used when
        running on the full simulator, since this provides an
        accurate simulation of the hardware including I/O devices.
        To use the simulator in this mode the command
        `target sim --board=jmr3904' should be used from inside gdb."
}

This contains the title and description presented in the Configuration Tool when the package is selected. It also specifies where in the tree the package files can be found (directory) and the name of the CDL file which contains the package details (script).

To be able to build and test a configuration for the new target, there also needs to be a target entry in the ecos.db file.

target jmr3904 {
    alias       { "Toshiba JMR-TX3904 board" jmr tx39 }
    packages    { CYGPKG_HAL_MIPS
                  CYGPKG_HAL_MIPS_TX39
                  CYGPKG_HAL_MIPS_TX39_JMR3904
    }
    description "
        The jmr3904 target provides the packages needed to run
        eCos on a Toshiba JMR-TX3904 board. This target can also
        be used when running in the full simulator, since the simulator provides an
        accurate simulation of the hardware including I/O devices.
        To use the simulator in this mode the command
        `target sim --board=jmr3904' should be used from inside gdb."
}

The important part here is the packages section which defines the various hardware specific packages that contribute to support for this target. In this case the MIPS architecture package, the TX39 variant package, and the JMR-TX3904 platform packages are selected. Other packages, for serial drivers, ethernet drivers and FLASH memory drivers may also appear here.

7.5.2.2. CDL File Layout

All the platform options are contained in a CDL package named CYGPKG_HAL_<architecture>_<variant>_<platform>. They all share more or less the same cdl_package details:

cdl_package CYGPKG_HAL_MIPS_TX39_JMR3904 {
    display       "JMR3904 evaluation board"
    parent        CYGPKG_HAL_MIPS
    requires      CYGPKG_HAL_MIPS_TX39
    define_header hal_mips_tx39_jmr3904.h
    include_dir   cyg/hal
    description   "
        The JMR3904 HAL package should be used when targeting the
        actual hardware. The same package can also be used when
        running on the full simulator, since this provides an
        accurate simulation of the hardware including I/O devices.
        To use the simulator in this mode the command
        `target sim --board=jmr3904' should be used from inside gdb."

    compile       platform.S plf_misc.c plf_stub.c

    define_proc {
        puts $::cdl_system_header "#define CYGBLD_HAL_TARGET_H   <pkgconf/hal_mips_tx39.h>"
        puts $::cdl_system_header "#define CYGBLD_HAL_PLATFORM_H <pkgconf/hal_mips_tx39_jmr3904.h>"
    }

    …
}

This specifies that the platform package should be parented under the MIPS packages, requires the TX39 variant HAL and all configuration settings should be saved in cyg/hal/hal_mips_tx39_jmt3904.h.

The compile line specifies which files should be built when this package is enabled, and the define_proc defines some macros that are used to access the variant or architecture (the _TARGET_ name is a bit of a misnomer) and platform configuration options.

7.5.2.3. Startup Type

eCos uses an option to select between a set of valid startup configurations. These are normally RAM, ROM and possibly ROMRAM. This setting is used to select which linker map to use (i.e., where to link eCos and the application in the memory space), and how the startup code should behave.

cdl_component CYG_HAL_STARTUP {
    display       "Startup type"
    flavor        data
    legal_values  {"RAM" "ROM"}
    default_value {"RAM"}
    no_define
    define -file system.h CYG_HAL_STARTUP
    description   "
        When targeting the JMR3904 board it is possible to build
        the system for either RAM bootstrap, ROM bootstrap, or STUB
        bootstrap. RAM bootstrap generally requires that the board
        is equipped with ROMs containing a suitable ROM monitor or
        equivalent software that allows GDB to download the eCos
        application on to the board. The ROM bootstrap typically
        requires that the eCos application be blown into EPROMs or
        equivalent technology."
}

The no_define and define pair is used to make the setting of this option appear in the file system.h instead of the default specified in the header.

7.5.2.4. Build options

A set of options under the components CYGBLD_GLOBAL_OPTIONS and CYGHWR_MEMORY_LAYOUT specify how eCos should be built: what tools and compiler options should be used, and which linker fragments should be used.

cdl_component CYGBLD_GLOBAL_OPTIONS {
    display "Global build options"
    flavor  none
    parent  CYGPKG_NONE
    description   "
        Global build options including control over
        compiler flags, linker flags and choice of toolchain."


    cdl_option CYGBLD_GLOBAL_COMMAND_PREFIX {
        display "Global command prefix"
        flavor  data
        no_define
        default_value { "mips-tx39-elf" }
        description "
            This option specifies the command prefix used when
            invoking the build tools."
    }

    cdl_option CYGBLD_GLOBAL_CFLAGS {
        display "Global compiler flags"
        flavor  data
        no_define
        default_value { "-Wall -Wpointer-arith -Wstrict-prototypes -Winline -Wundef -Woverloaded-virtual " .
            "-g -O2 -ffunction-sections -fdata-sections -fno-rtti -fno-exceptions" }
        description   "
            This option controls the global compiler flags which
            are used to compile all packages by
            default. Individual packages may define
            options which override these global flags."
    }

    cdl_option CYGBLD_GLOBAL_LDFLAGS {
        display "Global linker flags"
        flavor  data
        no_define
        default_value { "-g -nostdlib -Wl,--gc-sections -Wl,-static" }
        description   "
            This option controls the global linker flags. Individual
            packages may define options which override these global flags."
        }
    }

    cdl_component CYGHWR_MEMORY_LAYOUT {
        display "Memory layout"
        flavor data
        no_define
        calculated { CYG_HAL_STARTUP == "RAM" ? "mips_tx39_jmr3904_ram" : \
                                                "mips_tx39_jmr3904_rom" }

        cdl_option CYGHWR_MEMORY_LAYOUT_LDI {
        display "Memory layout linker script fragment"
        flavor data
        no_define
        define -file system.h CYGHWR_MEMORY_LAYOUT_LDI
        calculated { CYG_HAL_STARTUP == "RAM" ? "<pkgconf/mlt_mips_tx39_jmr3904_ram.ldi>" : \
                                                "<pkgconf/mlt_mips_tx39_jmr3904_rom.ldi>" }
    }

    cdl_option CYGHWR_MEMORY_LAYOUT_H {
        display "Memory layout header file"
        flavor data
        no_define
        define -file system.h CYGHWR_MEMORY_LAYOUT_H
        calculated { CYG_HAL_STARTUP == "RAM" ? "<pkgconf/mlt_mips_tx39_jmr3904_ram.h>" : \
                                                "<pkgconf/mlt_mips_tx39_jmr3904_rom.h>" }
    }
}

7.5.2.5. Common Target Options

All platforms also specify real-time clock details:

# Real-time clock/counter specifics
cdl_component CYGNUM_HAL_RTC_CONSTANTS {
    display       "Real-time clock constants."
    flavor        none

    cdl_option CYGNUM_HAL_RTC_NUMERATOR {
        display       "Real-time clock numerator"
        flavor        data
        calculated    1000000000
    }
    cdl_option CYGNUM_HAL_RTC_DENOMINATOR {
        display       "Real-time clock denominator"
        flavor        data
        calculated    100
    }
    # Isn't a nice way to handle freq requirement!
    cdl_option CYGNUM_HAL_RTC_PERIOD {
        display       "Real-time clock period"
        flavor        data
        legal_values  { 15360 20736 }
        calculated     { CYGHWR_HAL_MIPS_CPU_FREQ == 50 ? 15360 : \
        CYGHWR_HAL_MIPS_CPU_FREQ == 66 ? 20736 : 0 }
    }
}

The NUMERATOR divided by the DENOMINATOR gives the number of nanoseconds per tick. The PERIOD is the divider to be programmed into a hardware timer that is driven from an appropriate hardware clock, such that the timer overflows once per tick (normally generating a CPU interrupt to mark the end of a tick). The tick default rate is typically 100Hz.

Platforms that make use of the virtual vector ROM calling interface (see Section 7.3, “Virtual Vectors (eCos/ROM Monitor Calling Interface)”) will also specify details necessary to define configuration channels (these options are from the SH/EDK7707 HAL) :

cdl_option CYGNUM_HAL_VIRTUAL_VECTOR_COMM_CHANNELS {
    display      "Number of communication channels on the board"
    flavor       data
    calculated   1
}

cdl_option CYGNUM_HAL_VIRTUAL_VECTOR_DEBUG_CHANNEL {
    display          "Debug serial port"
    flavor data
    legal_values     0 to CYGNUM_HAL_VIRTUAL_VECTOR_COMM_CHANNELS-1
    default_value    0
    description      "
        The EDK/7708 board has only one serial port. This option
        chooses which port will be used to connect to a host
        running GDB."
}

cdl_option CYGNUM_HAL_VIRTUAL_VECTOR_CONSOLE_CHANNEL {
    display          "Diagnostic serial port"
    flavor data
    legal_values     0 to CYGNUM_HAL_VIRTUAL_VECTOR_COMM_CHANNELS-1
    default_value    0
    description      "
        The EDK/7708 board has only one serial port.  This option
        chooses which port will be used for diagnostic output."
}

The platform usually also specify an option controlling the ability to co-exist with a ROM monitor:

cdl_option CYGSEM_HAL_USE_ROM_MONITOR {
    display       "Work with a ROM monitor"
    flavor        booldata
    legal_values  { "Generic" "CygMon" "GDB_stubs" }
    default_value { CYG_HAL_STARTUP == "RAM" ? "CygMon" : 0 }
    parent        CYGPKG_HAL_ROM_MONITOR
    requires      { CYG_HAL_STARTUP == "RAM" }
    description   "
        Support can be enabled for three different varieties of ROM monitor.
        This support changes various eCos semantics such as the encoding
        of diagnostic output, or the overriding of hardware interrupt
        vectors.
        Firstly there is \"Generic\" support which prevents the HAL
        from overriding the hardware vectors that it does not use, to
        instead allow an installed ROM monitor to handle them. This is
        the most basic support which is likely to be common to most
        implementations of ROM monitor.
        \"CygMon\" provides support for the Cygnus ROM Monitor.
        And finally, \"GDB_stubs\" provides support when GDB stubs are
        included in the ROM monitor or boot ROM."
}

Or the ability to be configured as a ROM monitor:

cdl_option CYGSEM_HAL_ROM_MONITOR {
    display       "Behave as a ROM monitor"
    flavor        bool
    default_value 0
    parent        CYGPKG_HAL_ROM_MONITOR
    requires      { CYG_HAL_STARTUP == "ROM" }
    description   "
        Enable this option if this program is to be used as a ROM monitor,
        i.e. applications will be loaded into RAM on the board, and this
        ROM monitor may process exceptions or interrupts generated from the
        application. This enables features such as utilizing a separate
        interrupt stack when exceptions are generated."
}

The latter option is accompanied by a special build rule that extends the generic ROM monitor build rule in the common HAL:

cdl_option CYGBLD_BUILD_GDB_STUBS {
    display "Build GDB stub ROM image"
    default_value 0
    requires { CYG_HAL_STARTUP == "ROM" }
    requires CYGSEM_HAL_ROM_MONITOR
    requires CYGBLD_BUILD_COMMON_GDB_STUBS
    requires CYGDBG_HAL_DEBUG_GDB_INCLUDE_STUBS
    requires ! CYGDBG_HAL_DEBUG_GDB_BREAK_SUPPORT
    requires ! CYGDBG_HAL_DEBUG_GDB_THREAD_SUPPORT
    requires ! CYGDBG_HAL_COMMON_INTERRUPTS_SAVE_MINIMUM_CONTEXT
    requires ! CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
    no_define
    description "
        This option enables the building of the GDB stubs for the
        board. The common HAL controls takes care of most of the
        build process, but the final conversion from ELF image to
        binary data is handled by the platform CDL, allowing
        relocation of the data if necessary."

    make -priority 320 {
        <PREFIX>/bin/gdb_module.bin : <PREFIX>/bin/gdb_module.img
        $(OBJCOPY) -O binary $< $@
    }
}

Most platforms support RedBoot, and some options are needed to configure for RedBoot.

    cdl_component CYGPKG_REDBOOT_HAL_OPTIONS {
    display       "Redboot HAL options"
    flavor        none
    no_define
    parent        CYGPKG_REDBOOT
    active_if     CYGPKG_REDBOOT
    description   "
        This option lists the target's requirements for a valid Redboot
        configuration."

    cdl_option CYGBLD_BUILD_REDBOOT_BIN {
        display       "Build Redboot ROM binary image"
        active_if     CYGBLD_BUILD_REDBOOT
        default_value 1
        no_define
        description "This option enables the conversion of the Redboot ELF
            image to a binary image suitable for ROM programming."

        make -priority 325 {
            <PREFIX>/bin/redboot.bin : <PREFIX>/bin/redboot.elf
            $(OBJCOPY) --strip-debug $< $(@:.bin=.img)
            $(OBJCOPY) -O srec $< $(@:.bin=.srec)
            $(OBJCOPY) -O binary $< $@
        }
    }
}

The important part here is the make command in the CYGBLD_BUILD_REDBOOT_BIN option which emits makefile commands to translate the .elf file generated by the link phase into both a binary file and an S-Record file. If a different format is required by a PROM programmer or ROM monitor, then different output formats would need to be generated here.

7.5.3. Platform Memory Layout

The platform memory layout is defined using the Memory Configuration Window in the Configuration Tool.

[Note]Note

If you do not have access to a Windows machine, you can hand edit the .h and .ldi files to match the properties of your platform. If you want to contribute your port back to the eCos community, ask someone on the list to make proper memory map files for you.

7.5.3.1. Layout Files

The memory configuration details are saved in three files:

.mlt
This is the Configuration Tool save-file. It is only used by the Configuration Tool.
.ldi
This is the linker script fragment. It defines the memory and location of sections by way of macros defined in the architecture or variant linker script.
.h
This file describes some of the memory region details as C macros, allowing eCos or the application adapt the memory layout of a specific configuration.

These three files are generated for each startup-type, since the memory details usually differ.

7.5.3.2. Reserved Regions

Some areas of the memory space are reserved for specific purposes, making room for exception vectors and various tables. RAM startup configurations also need to reserve some space at the bottom of the memory map for the ROM monitor.

These reserved areas are named with the prefix "reserved_" which is handled specially by the Configuration Tool: instead of referring to a linker macro, the start of the area is labeled and a gap left in the memory map.

7.5.4. Platform Serial Device Support

The first step is to set up the CDL definitions. The configuration options that need to be set are the following:

CYGNUM_HAL_VIRTUAL_VECTOR_COMM_CHANNELS
The number of channels, usually 0, 1 or 2.
CYGNUM_HAL_VIRTUAL_VECTOR_DEBUG_CHANNEL
The channel to use for GDB.
CYGNUM_HAL_VIRTUAL_VECTOR_DEBUG_CHANNEL_BAUD
Initial baud rate for debug channel.
CYGNUM_HAL_VIRTUAL_VECTOR_CONSOLE_CHANNEL
The channel to use for the console.
CYGNUM_HAL_VIRTUAL_VECTOR_CONSOLE_CHANNEL_BAUD
The initial baud rate for the console channel.
CYGNUM_HAL_VIRTUAL_VECTOR_CONSOLE_CHANNEL_DEFAULT
The default console channel.

The code in hal_diag.c need to be converted to support the new serial device. If this the same as a device already supported, copy that.

The following functions and types need to be rewritten to support a new serial device.

struct channel_data_t;
Structure containing base address, timeout and ISR vector number for each serial device supported. Extra fields my be added if necessary for the device. For example some devices have write-only control registers, so keeping a shadow of the last value written here can be useful.
xxxx_ser_channels[];
Array of channel_data_t, initialized with parameters of each channel. The index into this array is the channel number used in the CDL options above and is used by the virtual vector mechanism to refer to each channel.
void cyg_hal_plf_serial_init_channel(void *__ch_data)
Initialize the serial device. The parameter is actually a pointer to a channel_data_t and should be cast back to this type before use. This function should use the CDL definition for the baud rate for the channel it is initializing.
void cyg_hal_plf_serial_putc(void * __ch_data, char *c)
Send a character to the serial device. This function should poll for the device being ready to send and then write the character. Since this is intended to be a diagnostic/debug channel, it is often also a good idea to poll for end of transmission too. This ensures that as much data gets out of the system as possible.
bool cyg_hal_plf_serial_getc_nonblock(void* __ch_data, cyg_uint8* ch)
This function tests the device and if a character is available, places it in *ch and returns TRUE. If no character is available, then the function returns FALSE immediately.
int cyg_hal_plf_serial_control(void *__ch_data, __comm_control_cmd_t __func, ...)
This is an IOCTL-like function for controlling various aspects of the serial device. The only part in which you may need to do some work initially is in the __COMMCTL_IRQ_ENABLE and __COMMCTL_IRQ_DISABLE cases to enable/disable interrupts.
int cyg_hal_plf_serial_isr(void *__ch_data, int* __ctrlc, CYG_ADDRWORD __vector, CYG_ADDRWORD __data)

This interrupt handler, called from the spurious interrupt vector, is specifically for dealing with Ctrl-C interrupts from GDB. When called this function should do the following:

  1. Check for an incoming character. The code here is very similar to that in cyg_hal_plf_serial_getc_nonblock().
  2. Read the character and call cyg_hal_is_break().
  3. If result is true, set *__ctrlc to 1.
  4. Return CYG_ISR_HANDLED.
void cyg_hal_plf_serial_init()
Initialize each of the serial channels. First call cyg_hal_plf_serial_init_channel() for each channel. Then call the CYGACC_COMM_IF_* macros for each channel. This latter set of calls are identical for all channels, so the best way to do this is to copy and edit an existing example.

7.6. Variant HAL Porting

A variant port can be a fairly limited job, but can also require quite a lot of work. A variant HAL describes how a specific CPU variant differs from the generic CPU architecture. The variant HAL can re-define cache, MMU, interrupt, and other features which override the default implementation provided by the architecture HAL.

Doing a variant port requires a preexisting architecture HAL port. It is also likely that a platform port will have to be done at the same time if it is to be tested.

7.6.1. HAL Variant Porting Process

The easiest way to make a new variant HAL is simply to copy an existing variant HAL and change all the files to match the new variant. If this is the first variant for an architecture, it may be hard to decide which parts should be put in the variant - knowledge of other variants of the architecture is required.

Looking at existing variant HALs (e.g., MIPS tx39, tx49) may be a help - usually things such as caching, interrupt and exception handling differ between variants. Initialization code, and code for handling various core components (FPU, DSP, MMU, etc.) may also differ or be missing altogether on some variants. Linker scripts may also require specific variant versions.

[Note]Note

Some CPU variants may require specific compiler support. That support must be in place before you can undertake the eCos variant port.

7.6.2. HAL Variant CDL

The CDL in a variant HAL tends to depend on the exact functionality supported by the variant. If it implements some of the devices described in the platform HAL, then the CDL for those will be here rather than there (for example the real-time clock).

There may also be CDL to select options in the architecture HAL to configure it to a particular architectural variant.

Each variant needs an entry in the ecos.db file. This is the one for the SH3:

package CYGPKG_HAL_SH_SH3 {
    alias         { "SH3 architecture" hal_sh_sh3 }
    directory     hal/sh/sh3
    script        hal_sh_sh3.cdl
    hardware
    description   "
    The SH3 (SuperH 3) variant HAL package provides generic
    support for SH3 variant CPUs."
}

As you can see, it is very similar to the platform entry.

The variant CDL file will contain a package entry named for the architecture and variant, matching the package name in the ecos.db file. Here is the initial part of the MIPS VR4300 CDL file:

cdl_package CYGPKG_HAL_MIPS_VR4300 {
    display       "VR4300 variant"
    parent        CYGPKG_HAL_MIPS
    implements    CYGINT_HAL_MIPS_VARIANT
    hardware
    include_dir   cyg/hal
    define_header hal_mips_vr4300.h
    description   "
        The VR4300 variant HAL package provides generic support
        for this processor architecture. It is also necessary to
        select a specific target platform HAL package."
}

This defines the package, placing it under the MIPS architecture package in the hierarchy. The implements line indicates that this is a MIPS variant. The architecture package uses this to check that exactly one variant is configured in.

The variant defines some options that cause the architecture HAL to configure itself to support this variant.

    cdl_option CYGHWR_HAL_MIPS_64BIT {
    display    "Variant 64 bit architecture support"
    calculated 1
}

cdl_option CYGHWR_HAL_MIPS_FPU {
    display    "Variant FPU support"
    calculated 1
}

cdl_option CYGHWR_HAL_MIPS_FPU_64BIT {
    display    "Variant 64 bit FPU support"
    calculated 1
}

These tell the architecture that this is a 64 bit MIPS architecture, that it has a floating point unit, and that we are going to use it in 64 bit mode rather than 32 bit mode.

The CDL file finishes off with some build options.

    define_proc {
        puts $::cdl_header "#include <pkgconf/hal_mips.h>"
    }

    compile       var_misc.c

    make {
        <PREFIX>/lib/target.ld: <PACKAGE>/src/mips_vr4300.ld
        $(CC) -E -P -Wp,-MD,target.tmp -DEXTRAS=1 -xc $(INCLUDE_PATH) $(CFLAGS) -o $@ $<
        @echo $@ ": \\" > $(notdir $@).deps
        @tail +2 target.tmp >> $(notdir $@).deps
        @echo >> $(notdir $@).deps
        @rm target.tmp
    }

    cdl_option CYGBLD_LINKER_SCRIPT {
        display "Linker script"
        flavor data
        no_define
        calculated  { "src/mips_vr4300.ld" }
    }

}

The define_proc causes the architecture configuration file to be included into the configuration file for the variant. The compile causes the single source file for this variant, var_misc.c to be compiled. The make command emits makefile rules to combine the linker script with the .ldi file to generate target.ld. Finally, in the MIPS HALs, the main linker script is defined in the variant, rather than the architecture, so CYGBLD_LINKER_SCRIPT is defined here.

7.6.3. Cache Support

The main area where the variant is likely to be involved is in cache support. Often the only thing that distinguishes one CPU variant from another is the size of its caches.

In architectures such as the MIPS and PowerPC where cache instructions are part of the ISA, most of the actual cache operations are implemented in the architecture HAL. In this case the variant HAL only needs to define the cache dimensions. The following are the cache dimensions defined in the MIPS VR4300 variant var_cache.h.

// Data cache
#define HAL_DCACHE_SIZE                 (8*1024)        // Size of data cache in bytes
#define HAL_DCACHE_LINE_SIZE            16              // Size of a data cache line
#define HAL_DCACHE_WAYS                 1               // Associativity of the cache

// Instruction cache
#define HAL_ICACHE_SIZE                 (16*1024)       // Size of cache in bytes
#define HAL_ICACHE_LINE_SIZE            32              // Size of a cache line
#define HAL_ICACHE_WAYS                 1               // Associativity of the cache

#define HAL_DCACHE_SETS (HAL_DCACHE_SIZE/(HAL_DCACHE_LINE_SIZE*HAL_DCACHE_WAYS))
#define HAL_ICACHE_SETS (HAL_ICACHE_SIZE/(HAL_ICACHE_LINE_SIZE*HAL_ICACHE_WAYS))

Additional cache macros, or overrides for the defaults, may also appear in here. While some architectures have instructions for managing cache lines, overall enable/disable operations may be handled via variant specific registers. If so then var_cache.h should also define the HAL_XCACHE_ENABLE() and HAL_XCACHE_DISABLE() macros.

If there are any generic features that the variant does not support (cache locking is a typical example) then var_cache.h may need to disable definitions of certain operations. It is architecture dependent exactly how this is done.

7.7. Architecture HAL Porting

A new architecture HAL is the most complex HAL to write, and it the least easily described. Hence this section is presently nothing more than a place holder for the future.

7.7.1. HAL Architecture Porting Process

The easiest way to make a new architecture HAL is simply to copy an existing architecture HAL of an, if possible, closely matching architecture and change all the files to match the new architecture. The MIPS architecture HAL should be used if possible, as it has the appropriate layout and coding conventions. Other HALs may deviate from that norm in various ways.

[Note]Note

eCos is written for GCC. It requires C and C++ compiler support as well as a few compiler features introduced during eCos development - so compilers older than eCos may not provide these features. Note that there is no C++ support for any 8 or 16 bit CPUs. Before you can undertake an eCos port, you need the required compiler support.

The following gives a rough outline of the steps needed to create a new architecture HAL. The exact order and set of steps needed will vary greatly from architecture to architecture, so a lot of flexibility is required. And of course, if the architecture HAL is to be tested, it is necessary to do variant and platform ports for the initial target simultaneously.

  1. Make a new directory for the new architecture under the hal directory in the source repository. Make an arch directory under this and populate this with the standard set of package directories.
  2. Copy the CDL file from an example HAL changing its name to match the new HAL. Edit the file, changing option names as appropriate. Delete any options that are specific to the original HAL, and and any new options that are necessary for the new architecture. This is likely to be a continuing process during the development of the HAL. See Section 7.7.2, “CDL Requirements” for more details.
  3. Copy the hal_arch.h file from an example HAL. Within this file you need to change or define the following:

    • Define the HAL_SavedRegisters structure. This may need to reflect the save order of any group register save/restore instructions, the interrupt and exception save and restore formats, and the procedure calling conventions. It may also need to cater for optional FPUs and other functional units. It can be quite difficult to develop a layout that copes with all requirements.
    • Define the bit manipulation routines, HAL_LSBIT_INDEX() and HAL_MSBIT_INDEX(). If the architecture contains instructions to perform these, or related, operations, then these should be defined as inline assembler fragments. Otherwise make them calls to functions.
    • Define HAL_THREAD_INIT_CONTEXT(). This initializes a restorable CPU context onto a stack pointer so that a later call to HAL_THREAD_LOAD_CONTEXT() or HAL_THREAD_SWITCH_CONTEXT() will execute it correctly. This macro needs to take account of the same optional features of the architecture as the definition of HAL_SavedRegisters.
    • Define HAL_THREAD_LOAD_CONTEXT() and HAL_THREAD_SWITCH_CONTEXT(). These should just be calls to functions in context.S.
    • Define HAL_REORDER_BARRIER(). This prevents code being moved by the compiler and is necessary in some order-sensitive code. This macro is actually defined identically in all architecture, so it can just be copied.
    • Define breakpoint support. The macro HAL_BREAKPOINT(label) needs to be an inline assembly fragment that invokes a breakpoint. The breakpoint instruction should be labeled with the label argument. HAL_BREAKINST and HAL_BREAKINST_SIZE define the breakpoint instruction for debugging purposes.
    • Optionally provide a macro HAL_HWDEBUG_BREAKPOINT. This is used by the common HAL's gdb file I/O support to get the attention of gdb when using hardware debug technology such as jtag or BDM. The macro may involve a dedicated breakpoint instruction or a processor exception or trap of some sort. Only one instance of this macro will ever be invoked. It should define either one or two labels. _gdb_hwdebug_break should correspond to the address that will be reported to gdb. If that address is the same as the breakpoint instruction or trap, or if the instruction has side effects like pushing exception data onto the stack, then the macro should also define a label _gdb_hwdebug_continue. When the application is resumed gdb will transfer control to that label if defined, allowing any necessary clean-up operations to be performed.
    • Define GDB support. GDB views the registers of the target as a linear array, with each register having a well defined offset. This array may differ from the ordering defined in HAL_SavedRegisters. The macros HAL_GET_GDB_REGISTERS() and HAL_SET_GDB_REGISTERS() translate between the GDB array and the HAL_SavedRegisters structure. The HAL_THREAD_GET_SAVED_REGISTERS() translates a stack pointer saved by the context switch macros into a pointer to a HAL_SavedRegisters structure. Usually this is a one-to-one translation, but this macro allows it to differ if necessary.
    • Define long jump support. The type hal_jmp_buf and the functions hal_setjmp() and hal_longjmp() provide the underlying implementation of the C library setjmp() and longjmp().
    • Define idle thread action. Generally the macro HAL_IDLE_THREAD_ACTION() is defined to call a function in hal_misc.c.
    • Define stack sizes. The macros CYGNUM_HAL_STACK_SIZE_MINIMUM and CYGNUM_HAL_STACK_SIZE_TYPICAL should be defined to the minimum size for any thread stack and a reasonable default for most threads respectively. It is usually best to construct these out of component sizes for the CPU save state and procedure call stack usage. These definitions should not use anything other than numerical values since they can be used from assembly code in some HALs.
    • Define memory access macros. These macros provide translation between cached and uncached and physical memory spaces. They usually consist of masking out bits of the supplied address and ORing in alternative address bits.
    • Define global pointer save/restore macros. These really only need defining if the calling conventions of the architecture require a global pointer (as does the MIPS architecture), they may be empty otherwise. If it is necessary to define these, then take a look at the MIPS implementation for an example.
  4. Copy hal_intr.h from an example HAL. Within this file you should change or define the following:

    • Define the exception vectors. These should be detailed in the architecture specification. Essentially for each exception entry point defined by the architecture there should be an entry in the VSR table. The offsets of these VSR table entries should be defined here by CYGNUM_HAL_VECTOR_* definitions. The size of the VSR table also needs to be defined here.
    • Map any hardware exceptions to standard names. There is a group of exception vector name of the form CYGNUM_HAL_EXCEPTION_* that define a wide variety of possible exceptions that many architectures raise. Generic code detects whether the architecture can raise a given exception by testing whether a given CYGNUM_HAL_EXCEPTION_* definition is present. If it is present then its value is the vector that raises that exception. This does not need to be a one-to-one correspondence, and several CYGNUM_HAL_EXCEPTION_* definitions may have the same value.

      Interrupt vectors are usually defined in the variant or platform HALs. The interrupt number space may either be continuous with the VSR number space, where they share a vector table (as in the i386) or may be a separate space where a separate decode stage is used (as in MIPS or PowerPC).

    • Declare any static data used by the HAL to handle interrupts and exceptions. This is usually three vectors for interrupts: hal_interrupt_handlers[], hal_interrupt_data[] and hal_interrupt_objects[], which are sized according to the interrupt vector definitions. In addition a definition for the VSR table, hal_vsr_table[] should be made. These vectors are normally defined in either vectors.S or hal_misc.c.
    • Define interrupt enable/disable macros. These are normally inline assembly fragments to execute the instructions, or manipulate the CPU register, that contains the CPU interrupt enable bit.
    • A feature that many HALs support is the ability to execute DSRs on the interrupt stack. This is not an essential feature, and is better left unimplemented in the initial porting effort. If this is required, then the macro HAL_INTERRUPT_STACK_CALL_PENDING_DSRS() should be defined to call a function in vectors.S.
    • Define the interrupt and VSR attachment macros. If the same arrays as for other HALs have been used for VSR and interrupt vectors, then these macro can be copied across unchanged.
  5. A number of other header files also need to be filled in:

    • basetype.h. This file defines the basic types used by eCos, together with the endianness and some other characteristics. This file only really needs to contain definitions if the architecture differs significantly from the defaults defined in cyg_type.h
    • hal_io.h. This file contains macros for accessing device IO registers. If the architecture uses memory mapped IO, then these can be copied unchanged from an existing HAL such as MIPS. If the architecture uses special IO instructions, then these macros must be defined as inline assembler fragments. See the I386 HAL for an example. PCI bus access macros are usually defined in the variant or platform HALs.

      This file may also provide further macro definitions, if relevant for the underlying hardware:

      HAL_MEMORY_BARRIER()
      This causes any memory writes pending within the CPU to be flushed to memory before continuing. Frequently there is a specific instruction, such as sync on MIPS, to cause write buffers to be flushed. This macro is generally not relevant to be called if you also have a writeback data cache, as that needs separate treatment. However this macro is relevant for systems with no data cache, a writethrough data cache, or in code running with the data cache disabled. For the latter reason this macro should be implemented if the facility exists, irrespective of the cache properties.
      HAL_IO_BARRIER()
      This causes any I/O writes pending within the CPU to be flushed to the I/O space before continuing. Frequently there is a specific instruction, such as eieio on PowerPC, to cause such pending writes to be guaranteed to be committed. On systems with no separate I/O space, such that all device access is instead memory-mapped, then this function may be defined to be the same as HAL_MEMORY_BARRIER().
    • hal_cache.h. This file contains cache access macros. If the architecture defines cache instructions, or control registers, then the access macros should be defined here. Otherwise they must be defined in the variant or platform HAL. Usually the cache dimensions (total size, line size, ways etc.) are defined in the variant HAL.
    • arch.inc and <architecture>.inc. These files are assembler headers used by vectors.S and context.S. <architecture>.inc is a general purpose header that should contain things like register aliases, ABI definitions and macros useful to general assembly code. If there are no such definitions, then this file need not be provided. arch.inc contains macros for performing various eCos related operations such as initializing the CPU, caches, FPU etc. The definitions here may often be configured or overridden by definitions in the variant or platform HALs. See the MIPS HAL for an example of this.
  6. Write vectors.S. This is the most important file in the HAL. It contains the CPU initialization code, exception and interrupt handlers. While other HALs should be consulted for structures and techniques, there is very little here that can be copied over without major edits.

    The main pieces of code that need to be defined here are:

    • Reset vector. This usually need to be positioned at the start of the ROM or FLASH, so should be in a linker section of its own. It can then be placed correctly by the linker script. Normally this code is little more than a jump to the label _start.
    • Exception vectors. These are the trampoline routines connected to the hardware exception entry points that vector through the VSR table. In many architectures these are adjacent to the reset vector, and should occupy the same linker section. If the architecture allow the vectors to be moved then it may be necessary for these trampolines to be position independent so they can be relocated at runtime.

      The trampolines should do the minimum necessary to transfer control from the hardware vector to the VSR pointed to by the matching table entry. Exactly how this is done depends on the architecture. Usually the trampoline needs to get some working registers by either saving them to CPU special registers (e.g. PowerPC SPRs), using reserved general registers (MIPS K0 and K1), using only memory based operations (IA32), or just jumping directly (ARM). The VSR table index to be used is either implicit in the entry point taken (PowerPC, IA32, ARM), or must be determined from a CPU register (MIPS).

    • Write kernel startup code. This is the location the reset vector jumps to, and can be in the main text section of the executable, rather than a special section. The code here should first initialize the CPU and other hardware subsystems. The best approach is to use a set of macro calls that are defined either in arch.inc or overridden in the variant or platform HALs. Other jobs that this code should do are: initialize stack pointer; copy the data section from ROM to RAM if necessary; zero the BSS; call variant and platform initializers; call cyg_hal_invoke_constructors(); call initialize_stub() if necessary. Finally it should call cyg_start(). See Section 5.1, “HAL Startup” for details.
    • Write the default exception VSR. This VSR is installed in the VSR table for all synchronous exception vectors. See Section 5.3, “Default Synchronous Exception Handling” for details of what this VSR does.
    • Write the default interrupt VSR. This is installed in all VSR table entries that correspond to external interrupts. See Section 5.3, “Default Synchronous Exception Handling” for details of what this VSR does.
    • Write hal_interrupt_stack_call_pending_dsrs(). If this function is defined in hal_arch.h then it should appear here. The purpose of this function is to call DSRs on the interrupt stack rather than the current thread's stack. This is not an essential feature, and may be left until later. However it interacts with the stack switching that goes on in the interrupt VSR, so it may make sense to write these pieces of code at the same time to ensure consistency.

      When this function is implemented it should do the following:

      • Take a copy of the current SP and then switch to the interrupt stack.
      • Save the old SP, together with the CPU status register (or whatever register contains the interrupt enable status) and any other registers that may be corrupted by a function call (such as any link register) to locations in the interrupt stack.
      • Enable interrupts.
      • Call cyg_interrupt_call_pending_DSRs(). This is a kernel functions that actually calls any pending DSRs.
      • Retrieve saved registers from the interrupt stack and switch back to the current thread stack.
      • Merge the interrupt enable state recorded in the save CPU status register with the current value of the status register to restore the previous enable state. If the status register does not contain any other persistent state then this can be a simple restore of the register. However if the register contains other state bits that might have been changed by a DSR, then care must be taken not to disturb these.
    • Define any data items needed. Typically vectors.S may contain definitions for the VSR table, the interrupt tables and the interrupt stack. Sometimes these are only default definitions that may be overridden by the variant or platform HALs.
  7. Write context.S. This file contains the context switch code. See Section 4.2.3, “Thread Context Switching” for details of how these functions operate. This file may also contain the implementation of hal_setjmp() and hal_longjmp().
  8. Write hal_misc.c. This file contains any C data and functions needed by the HAL. These might include:

    • hal_interrupt_*[]. In some HALs, if these arrays are not defined in vectors.S then they must be defined here.
    • cyg_hal_exception_handler(). This function is called from the exception VSR. It usually does extra decoding of the exception and invokes any special handlers for things like FPU traps, bus errors or memory exceptions. If there is nothing special to be done for an exception, then it either calls into the GDB stubs, by calling __handle_exception(), or invokes the kernel by calling cyg_hal_deliver_exception().
    • hal_arch_default_isr(). The hal_interrupt_handlers[] array is usually initialized with pointers to hal_default_isr(), which is defined in the common HAL. This function handles things like Ctrl-C processing, but if that is not relevant, then it will call hal_arch_default_isr(). Normally this function should just return zero.
    • cyg_hal_invoke_constructors(). This calls the constructors for all static objects before the program starts. eCos relies on these being called in the correct order for it to function correctly. The exact way in which constructors are handled may differ between architectures, although most use a simple table of function pointers between labels __CTOR_LIST__ and __CTOR_END__ which must called in order from the top down. Generally, this function can be copied directly from an existing architecture HAL.
    • Bit indexing functions. If the macros HAL_LSBIT_INDEX() and HAL_MSBIT_INDEX() are defined as function calls, then the functions should appear here. The main reason for doing this is that the architecture does not have support for bit indexing and these functions must provide the functionality by conventional means. While the trivial implementation is a simple for loop, it is expensive and non-deterministic. Better, constant time, implementations can be found in several HALs (MIPS for example).
    • hal_delay_us(). If the macro HAL_DELAY_US() is defined in hal_intr.h then it should be defined to call this function. While most of the time this function is called with very small values, occasionally (particularly in some ethernet drivers) it is called with values of several seconds. Hence the function should take care to avoid overflow in any calculations.
    • hal_idle_thread_action(). This function is called from the idle thread via the HAL_IDLE_THREAD_ACTION() macro, if so defined. While normally this function does nothing, during development this is often a good place to report various important system parameters on LCDs, LED or other displays. This function can also monitor system state and report any anomalies. If the architecture supports a halt instruction then this is a good place to put an inline assembly fragment to execute it. It is also a good place to handle any power saving activity.
  9. Create the <architecture>.ld file. While this file may need to be moved to the variant HAL in the future, it should initially be defined here, and only moved if necessary.

    This file defines a set of macros that are used by the platform .ldi files to generate linker scripts. Most GCC toolchains are very similar so the correct approach is to copy the file from an existing architecture and edit it. The main things that will need editing are the OUTPUT_FORMAT() directive and maybe the creation or allocation of extra sections to various macros. Running the target linker with just the --verbose argument will cause it to output its default linker script. This can be compared with the .ld file and appropriate edits made.

  10. If GDB stubs are to be supported in RedBoot or eCos, then support must be included for these. The most important of these are include/<architecture>-stub.h and src/<architecture>-stub.c. In all existing architecture HALs these files, and any support files they need, have been derived from files supplied in libgloss, as part of the GDB toolchain package. If this is a totally new architecture, this may not have been done, and they must be created from scratch.

    include/<architecture>-stub.h contains definitions that are used by the GDB stubs to describe the size, type, number and names of CPU registers. This information is usually found in the GDB support files for the architecture. It also contains prototypes for the functions exported by src/<architecture>-stub.c; however, since this is common to all architectures, it can be copied from some other HAL.

    src/<architecture>-stub.c implements the functions exported by the header. Most of this is fairly straight forward: the implementation in existing HALs should show exactly what needs to be done. The only complex part is the support for single-stepping. This is used a lot by GDB, so it cannot be avoided. If the architecture has support for a trace or single-step trap then that can be used for this purpose. If it does not then this must be simulated by planting a breakpoint in the next instruction. This can be quite involved since it requires some analysis of the current instruction plus the state of the CPU to determine where execution is going to go next.

7.7.2. CDL Requirements

The CDL needed for any particular architecture HAL depends to a large extent on the needs of that architecture. This includes issues such as support for different variants, use of FPUs, MMUs and caches. The exact split between the architecture, variant and platform HALs for various features is also somewhat fluid.

To give a rough idea about how the CDL for an architecture is structured, we will take as an example the I386 CDL.

This first section introduces the CDL package and placed it under the main HAL package. Include files from this package will be put in the include/cyg/hal directory, and definitions from this file will be placed in include/pkgconf/hal_i386.h. The compile line specifies the files in the src directory that are to be compiled as part of this package.

cdl_package CYGPKG_HAL_I386 {
    display       "i386 architecture"
    parent        CYGPKG_HAL
    hardware
    include_dir   cyg/hal
    define_header hal_i386.h
    description   "
        The i386 architecture HAL package provides generic
        support for this processor architecture. It is also
        necessary to select a specific target platform HAL
        package."

    compile       hal_misc.c context.S i386_stub.c hal_syscall.c

Next we need to generate some files using non-standard make rules. The first is vectors.S, which is not put into the library, but linked explicitly with all applications. The second is the generation of the target.ld file from i386.ld and the startup-selected .ldi file. Both of these are essentially boilerplate code that can be copied and edited.

make {
    <PREFIX>/lib/vectors.o : <PACKAGE>/src/vectors.S
    $(CC) -Wp,-MD,vectors.tmp $(INCLUDE_PATH) $(CFLAGS) -c -o $@ $<
    @echo $@ ": \\" > $(notdir $@).deps
    @tail +2 vectors.tmp >> $(notdir $@).deps
    @echo >> $(notdir $@).deps
    @rm vectors.tmp
}

make {
    <PREFIX>/lib/target.ld: <PACKAGE>/src/i386.ld
    $(CC) -E -P -Wp,-MD,target.tmp -DEXTRAS=1 -xc $(INCLUDE_PATH) $(CFLAGS) -o $@ $<
    @echo $@ ": \\" > $(notdir $@).deps
    @tail +2 target.tmp >> $(notdir $@).deps
    @echo >> $(notdir $@).deps
    @rm target.tmp
}

The i386 is currently the only architecture that supports SMP. The following CDL simply enabled the HAL SMP support if required. Generally this will get enabled as a result of a requires statement in the kernel. The requires statement here turns off lazy FPU switching in the FPU support code, since it is inconsistent with SMP operation.

cdl_component CYGPKG_HAL_SMP_SUPPORT {
    display       "SMP support"
    default_value 0
    requires { CYGHWR_HAL_I386_FPU_SWITCH_LAZY == 0 }

    cdl_option CYGPKG_HAL_SMP_CPU_MAX {
        display       "Max number of CPUs supported"
        flavor        data
        default_value 2
    }
}

The i386 HAL has optional FPU support, which is enabled by default. It can be disabled to improve system performance. There are two FPU support options: either to save and restore the FPU state on every context switch, or to only switch the FPU state when necessary.

cdl_component CYGHWR_HAL_I386_FPU {
    display       "Enable I386 FPU support"
    default_value 1
    description   "This component enables support for the
        I386 floating point unit."

    cdl_option CYGHWR_HAL_I386_FPU_SWITCH_LAZY {
        display       "Use lazy FPU state switching"
        flavor        bool
        default_value 1

        description "
            This option enables lazy FPU state switching.
            The default behaviour for eCos is to save and
            restore FPU state on every thread switch, interrupt
            and exception. While simple and deterministic, this
            approach can be expensive if the FPU is not used by
            all threads. The alternative, enabled by this option,
            is to use hardware features that allow the FPU state
            of a thread to be left in the FPU after it has been
            descheduled, and to allow the state to be switched to
            a new thread only if it actually uses the FPU. Where
            only one or two threads use the FPU this can avoid a
            lot of unnecessary state switching."
    }
}

The i386 HAL also has support for different classes of CPU. In particular, Pentium class CPUs have extra functional units, and some variants of GDB expect more registers to be reported. These options enable these features. Generally these are enabled by requires statements in variant or platform packages, or in .ecm files.

cdl_component CYGHWR_HAL_I386_PENTIUM {
    display       "Enable Pentium class CPU features"
    default_value 0
    description   "This component enables support for various
        features of Pentium class CPUs."

    cdl_option CYGHWR_HAL_I386_PENTIUM_SSE {
        display       "Save/Restore SSE registers on context switch"
        flavor        bool
        default_value 0

        description "
            This option enables SSE state switching. The default
            behaviour for eCos is to ignore the SSE registers.
            Enabling this option adds SSE state information to
            every thread context."
    }

    cdl_option CYGHWR_HAL_I386_PENTIUM_GDB_REGS {
        display       "Support extra Pentium registers in GDB stub"
        flavor        bool
        default_value 0

        description "
        This option enables support for extra Pentium registers
        in the GDB stub. These are registers such as CR0-CR4, and
        all MSRs. Not all GDBs support these registers, so the
        default behaviour for eCos is to not include them in the
        GDB stub support code."
    }
}

In the i386 HALs, the linker script is provided by the architecture HAL. In other HALs, for example MIPS, it is provided in the variant HAL. The following option provides the name of the linker script to other elements in the configuration system.

    cdl_option CYGBLD_LINKER_SCRIPT {
        display "Linker script"
        flavor data
        no_define
        calculated  { "src/i386.ld" }
    }

Finally, this interface indicates whether the platform supplied an implementation of the hal_i386_mem_real_region_top() function. If it does then it will contain a line of the form: implements CYGINT_HAL_I386_MEM_REAL_REGION_TOP. This allows packages such as RedBoot to detect the presence of this function so that they may call it.

    cdl_interface CYGINT_HAL_I386_MEM_REAL_REGION_TOP {
        display  "Implementations of hal_i386_mem_real_region_top()"
    }

}