Name
HAL Port — Implementation Details
Description
This documentation explains how the eCos HAL specification has been mapped onto the ARM hardware and should be read in conjunction with the relevant Architecture Reference Manual and the Technical Reference Manual for the revision of the ARM architecture being used. It should be noted that the architectural HAL is usually complemented by a variant HAL and a platform HAL, and those may affect or redefine some parts of the implementation.
Exports
The architectural HAL provides header
files cyg/hal/hal_arch.h
,
cyg/hal/hal_intr.h
,
cyg/hal/hal_io.h
and cyg/hal/hal_mmu.h
. These
header files export the functionality provided by all the ARM HALs for
a given target, automatically including headers from the lower-level
HALs as appropriate. For example the platform HAL may provide a
header cyg/hal/plf_io.h
containing additional I/O functionality, but that header will be
automatically included
by cyg/hal/hal_io.h
so there
is no need to include it directly.
Additionally, the architecture HAL provides
the cyg/hal/basetype.h
header,
which defines the basic properties of the architecture, including
endianness, data type sizes and alignment constraints.
Startup
The architectural HAL provides a default implementation of the low-level startup code which will be appropriate in nearly all scenarios. For a ROM startup this includes copying initialized data from flash to RAM. For all startup types it will involve zeroing BSS regions and setting up the general C environment. It will also set up the initial exception priorities, switches the CPU into the correct execution mode, enables the debug monitor and enables error exception handling.
In addition to the setup it does itself, the initialization code calls
out to the variant and platform HALs to perform their own
initialization via the hal_hardware_init()
function.
The architectural HAL also initializes the VSR and virtual vector
tables, sets up HAL diagnostics, and invokes C++ static constructors,
prior to calling the first application entry
point cyg_start
. This code resides
in src/vectors.S
.
Interrupts and Exceptions
The eCos interrupt and exception architecture is built around a table
of pointers to Vector Service Routines that translate hardware
exceptions and interrupts into the function calls expected by
eCos. The ARM vector table provides exactly this functionality, so it
is used directly as the eCos VSR
table. The HAL_VSR_GET
and HAL_VSR_SET
macros therefore manipulate the
vector table
directly. The hal_intr.h
header provides definitions for all the standard ARM exception
vectors.
The vector table is constructed at runtime. For ROM, ROMRAM and SRAM startup all entries are initialized. For RAM startup only the interrupt vectors are (re-)initialized to point to the VSR in the loaded code, the exception vectors are left pointing to the VSRs of the loading software, usually RedBoot or GDB stubs.
When an exception occurs it is delivered via the relevant handler
provided in vectors.S
. The handler will save the
CPU state and call exception_handler
in hal_misc.c
, which passes the exception on to
either the kernel or the GDB stub handler. If it returns then the CPU
state is restored and the code continued.
When an interrupt occurs it is delivered to a shared
VSR, hal_default_irq_vsr
, which saves some state
and calls hal_IRQ_handler
.
The architectural HAL provides default implementations of
HAL_DISABLE_INTERRUPTS
,
HAL_RESTORE_INTERRUPTS
,
HAL_ENABLE_INTERRUPTS
and
HAL_QUERY_INTERRUPTS
. These involve manipulation
of the status register I
flag. Similarly there are
default implementations of the interrupt controller
macros HAL_INTERRUPT_MASK
,
HAL_INTERRUPT_UNMASK
,
HAL_INTERRUPT_ACKNOWLEDGE
and HAL_INTERRUPT_CONFIGURE
macros.
HAL_INTERRUPT_SET_LEVEL
manipulates the relevant
interrupt priority registers. The valid range of interrupts supported
depends on the number of interrupt priority bits supported by the CPU
variant.
Stacks and Stack Sizes
cyg/hal/hal_arch.h
defines
values for minimal and recommended thread stack
sizes, CYGNUM_HAL_STACK_SIZE_MINIMUM
and CYGNUM_HAL_STACK_SIZE_TYPICAL
. These values
depend on a number of configuration options.
A number of system stacks are provided, and their properties controlled in
this package's configuration. By default, the ARM HAL will use a separate
stack for calling interrupt handlers. This separate interrupt stack means that
the worst case overhead of interrupt handling does not need to be considered
when determining each thread's maximum stack usage, which reduces overall
stack overhead. The size of this interrupt stack is controlled by the common
HAL's configuration
(CYGNUM_HAL_COMMON_INTERRUPTS_STACK_SIZE
) or can be
disabled entirely by turning off
CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK
.
System startup code will also run on the interrupt stack, if enabled, as it is
usually sufficiently large for this. Optionally, a separate startup
stack can be enabled in this HAL by disabling
CYGIMP_HAL_ARM_INT_STACK_IS_STARTUP_STACK
, in which case
when control is passed to the application by
the cyg_start()
or cyg_user_start()
entry points, this startup stack will then be used. Alternatively, if the
interrupt stack has been disabled entirely then a startup stack must be
present, and will be used for all initialisation. Its size can be set
with CYGNUM_HAL_ARM_STARTUP_STACK_SIZE
. Note that global
C++ object constructors defined by either the system, or in application code,
will have their constructors run on the interrupt stack. Using the C library
startup package's "Invoke default static constructors" option
(CYGSEM_LIBC_INVOKE_DEFAULT_STATIC_CONSTRUCTORS
) would
instead ensure the user application constructors are called in the context of
main(), which can be more appropriate.
If including GDB stubs in the application, then a separate GDB stub stack is
required in order to guarantee that application problems with stack use will
not prevent the GDB stub being able to debug the application. Again the size
is controlled via this package's CDL
(CYGNUM_HAL_ARM_GDB_STACK_SIZE
)
Separate small stacks are also created to do the initial handling of Abort Prefetch, Abort Data, Undefined Instruction exceptions, as well as IRQ and FIQ interrupts. Assuming the default eCos VSRs are in place for these exceptions/interrupts, these small stacks are only used very temporarily until the context is switched to supervisor (SVC) mode.
At that point, in the case of the first three exceptions, if GDB stubs are included, the stack then used will be the GDB stack mentioned above. Alternatively, in the case of the first three exceptions without GDB stubs, the stack used will be that of the supervisor mode (SVC) context at the time of the exception. This is usually the running thread, but can also be a DSR running on the interrupt stack.
In the case of the IRQ and FIQ interrupts, these small stacks are only used by the default eCos VSRs temporarily until the stack is switched to the interrupt stack (or if that is disabled, the stack of the interrupted thread).
The above describes the situation when using the normal eCos VSRs for handling the Abort Data, Abort Prefetch, Undefined Instruction, IRQ and FIQ exceptions/interrupts. However if the user overrides the eCos VSRs with their own VSRs, then it may be necessary to change the stack sizes for these contexts depending on the stack use by those new VSRs. Therefore each of the stack sizes corresponding to these exception/interrupt contexts can be changed in the ARM HAL package configuration.
Thread Contexts and setjmp/longjmp
cyg/hal/hal_arch.h
defines a
thread context data structure, the context-related macros, and
the setjmp
/longjmp
support. The implementations can be found
in src/context.S
.
Bit Indexing
The architectural HAL provides implementations in the source
file hal_misc.c
that are referenced by
the HAL_LSBIT_INDEX
and HAL_MSBIT_INDEX
macros.
Idle Thread Processing
Normally the variant HAL provides
the HAL_IDLE_THREAD_ACTION
implementation. It
usually implements code that can be used to put the CPU into a low
power mode ready to respond quickly to the next interrupt.
Clock Support
The architectural HAL provides default implementations of the various system clock macros such as HAL_CLOCK_INITIALIZE. The variant or platform HAL are responsible for providing the necessary implementation routines.
HAL I/O
The ARM architecture does not have a separate I/O bus. Instead all hardware is assumed to be memory-mapped. Further it is assumed that all peripherals on the memory bus will switch endianness with the processor and that there is no need for any byte swapping. Hence the various HAL macros for performing I/O simply involve pointers to volatile memory.
The variant and platform files included by the
cyg/hal/hal_io.h
header will
typically also provide details of some or all of the peripherals, for
example register offsets and the meaning of various bits in those
registers.
Cache Handling
The architecture HAL does not provide direct support for dealing with
caches, since there is no common mechanism for doing this. The cache
support is the responsibility of the variant HAL to, which will supply
the cyg/hal/hal_cache.h
header.
Linker Scripts
The architectural HAL will generate the linker script for eCos
applications. This involves the architectural
file src/arm.ld
and a .ldi
memory layout file, typically provided by the platform HAL. It is
the .ldi
file which places code and data in the
appropriate places for the startup type, but most of the hard work is
done via macros in the arm.ld
file.
Diagnostic Support
The architectural HAL implements diagnostic support for DCC output if
available, or for discarding all output. However, by default, the
diagnostics output is left to the variant or platform HAL, depending
on whether suitable peripherals are available on-chip or
off-chip. The CYGHWR_HAL_ARM_DIAGNOSTICS_INTERFACE
can be configured to direct the diagnostic output support used to the
appropriate destination.
SMP Support
The ARM architectural HAL provides SMP support for Cortex-A class
processors. If the configuration option
CYGPKG_HAL_SMP_SUPPORT
is enabled then the
hal_smp.h
header defines the standard SMP macros
described in the HAL
documentation. The architectural HAL only provides the SMP
components that are common to all CPUs. It is the responsibility of
variant and platform HALs to complete SMP support.
The variant HAL needs to supply a number of services for SMP. Access
to the interrupt controller needs to be multi-core safe. The design of
the standard ARM GIC provides this by default, but other controllers
may need a spinlock. MMU and cache support are linked since any memory
containing a spinlock must be cached and marked shareable. The variant
HAL should also contain cyg_hal_cpu_start()
,
which is used to start up the secondary CPUs and
cyg_hal_smp_start()
, which is the initial entry
point for secondary CPUs. It must also supply
cyg_hal_cpu_message()
, and associated ISR and
DSR, which are used to pass scheduling messages between CPUs.
The platform HAL (which may comprise more than one layer of hardware
specific HALs) is responsible for the memory map and
initialization. Initialization will usually involve starting clocks,
setting up pin multiplexing and configuring the CPU state. Most of
this is common to single and multi-core configurations, although there
will be some SMP specific settings. This HAL will also need to supply
the PLATFORM_SETUP_CPU
macro to initialize the
secondary CPUs.
Debug Support
The architectural HAL provides basic support for gdb stubs using the
debug monitor exceptions. Breakpoints are implemented using a
fixed-size list of breakpoints, as per the configuration
option CYGNUM_HAL_BREAKPOINT_LIST_SIZE
. When a JTAG
device is connected to a ARM device, it will steal breakpoints and
other exceptions from the running code. Therefore debugging from
RedBoot or the GDB stubs can only be done after detaching any JTAG
debugger and power-cycling the board.
HAL_DELAY_US() Macro
The variant or platform HAL is responsible for providing an
implementation of the HAL_DELAY_US
macro. The
system timer must be initialized before this macro is
used. The include/hal_intr.h
defined HAL_CLOCK_INITIALIZE()
macro is called
during initialization after the variant and platform initialization
functions are called, but before constructors are invoked.
Profiling Support
When using local memory based profiling the ARM architectural HAL
implements the mcount
function, allowing
profiling tools like gprof to determine the
application's call graph. It does not implement the profiling
timer. Instead that functionality needs to be provided by the variant
or platform HAL.
2024-03-18 | eCosPro Non-Commercial Public License |