Porting Guide
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.
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.
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 . The links
refer to the on-line CVS tree (specifically to the sub-HALs used by
the PowerPC MBX target).
HAL type
Description
Functionality 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.
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.
Common HAL
File
Description
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.
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.
File
Description
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.
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.
File
Description
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.
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.
File
Description
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.
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.
Virtual Vectors (eCos/ROM Monitor Calling Interface)
Virtually all eCos platforms provide full debugging capabilities
via RedBoot. This enviroment 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.
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).
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.
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.
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.
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.
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.
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.
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:
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.
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).
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:
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.
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 .
DEBUG_PROCS
The communication procedure table used for debugger IO
(see ).
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 ).
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.
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:
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.
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.
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.
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).
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.
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.
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.
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.
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:
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:
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.
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.
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.
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))
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.
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.
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
Some CPU variants may require specific compiler
support. That support must be in place before you can undertake the
eCos variant port.
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.
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.
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.
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
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.
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.
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 for more details.
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.
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.
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.
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.
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.
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 for details.
Write the default exception VSR. This VSR is installed in the VSR
table for all synchronous exception vectors. See 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 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.
Write context.S. This file contains the context
switch code. See for details of
how these functions operate. This file may also contain the
implementation of hal_setjmp() and
hal_longjmp().
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 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.
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.
If GDB stubs are to be supported in RedBoot or eCos, then support must
be included for these. The most important of these are 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.
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.
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()"
}
}