HAL Port — Implementation Details
This documentation explains how the eCos HAL specification has been mapped onto the Cortex-M hardware and should be read in conjunction with the Architecture Reference Manual and the Technical Reference Manual. 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.
The architectural HAL provides header files
cyg/hal/hal_io.h. These header
files export the functionality provided by all the Cortex-M HALs
for a given target, automatically including headers from the
lower-level HALs as appropriate. For example the platform HAL
may provide a header
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.
The conventional bootstrap mechanism involves a table of exception vectors at the base of memory. The first two words of this table give the initial program counter and stack pointer. For ROM startup only these two words are defined at the beginning of the ROM image. The rest of the vector table is constructed at runtime in on-chip SRAM.
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. The first such function is
hal_system_init which is called at the very
start of initialization. This function is supplied by the
platform HAL and should do minimal initialization to allow the
rest of the initialization code to run. Typically it will set up
GPIO lines, enable clocks and access to external RAM. This
function runs before the data and bss sections have been
initialized, so it cannot rely on global or static data. Full
initialization is handled by
hal_platform_init. The former should
complete clock and GPIO initialization and switch from the
startup clocking speed to the default rate, which may involve
enabling PLLs etc. The platform initialization routine will
complete any initialization needed for devices external to the
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
The current code assumes that there is no memory management or MPU and hence will not perform any MPU initialization. Other functional units may be initialized by the variant or platform HALs.
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 Cortex-M vector table provides exactly this
functionality, so it is used directly as the eCos VSR table. The
HAL_VSR_SET macros therefore manipulate the
vector table directly. The
hal_intr.h header provides
definitions for all the standard Cortex-M exception vectors.
The vector table is constructed at runtime at the base of internal SRAM, which is always located at address 0x20000000, and the Vector Table Offset Register set to use it. For ROM and JTAG 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 to a shared VSR,
vectors.S This saves the CPU state and calls
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.
Interrupts are numbered from zero starting at VSR table entry 15, which is the SysTick timer interrupt. The remaining interrupt numbers are defined by the variant HAL, and possibly the platform HAL. These definitions are used to declare interrupt handling tables in the architecture HAL.
When an interrupt occurs it is delivered to a shared VSR,
hal_default_interrupt_vsr, which saves some
state and calls
function is passed the interrupt number to be delivered,
generated by subtracting 15 from the value of the IPSR
register. It looks up the ISR in the interrupt tables and calls
it. If the return value of the ISR has the
CYG_ISR_CALL_DSR bit set then it calls
cyg_interrupt_post_dsr to mark the DSR for
execution and also sets the PENDSVSET bit in the NVIC ICSR
register to set the PendSVC exception pending.
Interrupts are delivered onto the main or interrupt stack, which
differs from the process stack that threads execute on. The
interrupt priority mechanism allows interrupts to nest on the
interrupt stack (irrespective of the CDL option
and only when the last interrupt has been
executed will the PendSVC exception be called. The PendSVC
handler arranges for
interrupt_end to be
called by pushing a new exception frame on the process stack,
preserving its own exception frame, and returning. This causes
interrupt_end to be called in thread mode on
the process stack, which will cause any pending DSRs to be
called, and a context switch to a new thread if necessary. When
execution resumes on this thread it returns to
hal_interrupt_end_done, which uses a SWI to
pop its own exception frame and use the preserved PendSVC frame
to resume the interrupted thread where it left off.
The architectural HAL provides default implementations of
HAL_QUERY_INTERRUPTS. These involve
manipulation of the CPU BASEPRI register. Similarly there are
default implementations of the interrupt controller macros
HAL_INTERRUPT_UNMASK macros. These
manipulate the NVIC interrupt mask registers, and the TICKINT bit
of the SYSTICK CSR register.
HAL_INTERRUPT_CONFIGURE are no-ops at the
HAL_INTERRUPT_SET_LEVEL manipulates the NVIC
interrupt priority registers. The valid range of interrupts
supported depends on the number of interrupt priority bits
supported by the CPU variant. Priority level 0 is reserved for
exceptions and the debug monitor. Interrupts are only allowed to
start at the first implemented priority below this: 0x10 if the
CPU implements 4 priority bits, 0x20 if it implements 3, and 0x01
if it implements all 8. This macro shifts the priority level
supplied to start at the implemented maximum and clamps the
higher end to 0xFF. So on a CPU that implements 4 priority bits,
level 0 will be mapped to 0x10, levels above 0xf0 will all be
mapped to 0xFF.
For all of these macros, a variant specific version may also be
HAL_VAR_INTERRUPT_CONFIGURE. These are each
called by the architecture macros after any architecture defined
operations are completed. These macros allow the variant HAL to
modify the architecture HAL support, or implement further
interrupts that are not directly supported by the NVIC. In
support of this, the variant HAL must define
CYGNUM_HAL_INTERRUPT_NVIC_MAX which separates
interrupts handled by the NVIC from any extended vectors defined
by the variant HAL.
Stacks and Stack Sizes
defines values for minimal and recommended thread stack sizes,
CYGNUM_HAL_STACK_SIZE_TYPICAL. These values
depend on a number of configuration options.
The Cortex-M architecture HAL always uses a separate stack for
startup and interrupt handling. This is usually allocated to
uninitialized memory at the top of the available internal or
external RAM, depending on the startup type. Thus the
has no effect.
Thread Contexts and setjmp/longjmp
defines a thread context data structure, the context-related
macros, and the
support. The implementations can be found in
src/context.S. The context structure is
defined as a discriminated union with different layouts for
thread, exception and interrupt saved states. This approach
allows the most efficient code and layout to be used in each
context. The only expense is that debug code must be slightly
more careful in accessing a saved state.
The architectural HAL provides inline assembler implementations of
HAL_MSBIT_INDEX which use the CPU
Idle Thread Processing
The architecture HAL provides a default
HAL_IDLE_THREAD_ACTION implementation that
WFI, wait for interrupt,
instruction. This puts the CPU into a low power mode ready to
respond quickly to the next interrupt.
A potential problem can occur with this however, as this instruction is
known to cause difficulties when debugging via a JTAG hardware debugger.
A frequent symptom is a report from the debugger that it was unable to
stop the target. Therefore if using a JTAG debugger, it is strongly
recommended to disable the use of
WFI by enabling
the configuration option titled "Disable HAL-specific idle action"
can be found in the eCos kernel package, in the "Thread-related options"
component. This automatically happens when additional eCos debugging
support is enabled using
CYGPKG_INFRA_DEBUG, or if
the HAL startup type (
CYG_HAL_STARTUP) is set to
"JTAG". But it needs to be set manually for other startup types, notably
when debugging an application installed into Flash, which would have
"ROM" startup type.
When using Single Wire Debug (SWD) hardware debuggers this is not
an issue. Since the
"JTAG" is used irrespective of the hardware debugger type this
would normally disable the use of
the idle thread. However, when ITM is configured as available,
option can be enabled to override the disabling, allowing the
WFI idle behaviour.
The architectural HAL provides a default implementation of the
various system clock macros such as
HAL_CLOCK_INITIALIZE. These macros use the
architecture defined SysTick timer to implement the eCos system
clock. The architecture HAL expects the variant HAL to define and
initialize a variable named
hal_cortexm_systick_clock, which should
contain the frequency in Hz of the clock supplied to the SysTick
timer input. To allow for varying CPU clock rates, the SysTick
timer is always programmed to take a 1MHz input clock, and
CYGNUM_HAL_RTC_PERIOD is then expressed in
terms of this.
The Cortex-M 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
The current Cortex-M implementations do not support caches, so no
cache handling is currently included in the architecture
port. Instead it is the responsibility of the variant HAL to
The architectural HAL will generate the linker script for eCos
applications. This involves the architectural file
src/cortexm.ld and a
.ldi memory layout file, typically provided
by the platform HAL. It is the
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
The architectural HAL implements diagnostic support for ITM
stimulus port 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
can be configured to direct the diagnostic output support used.
See Cortex-M Hardware Debug for more detail regarding using the ITM stimulus port.
The Cortex-M architectural HAL does not provide any SMP 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 Cortex-M 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.
provides a simple implementation of the
HAL_DELAY_US macro based around reading the
SysTick timer. The timer must therefore be initialized before
this macro is used, and
HAL_CLOCK_INITIALIZE() is called during
initialization after the variant and platform initialization
functions are called, but before constructors are invoked.
The Cortex-M variant may support the Data Watchpoint and Trace (DWT) feature, and if available then it is possible to use the DWT to provide non-intrusive PC sampling without any eCos run-time configuration or support being needed. The SWD hardware debugger being used controls the enabling and processing of PC sample data for subsequent use by profiling tools.
When using local memory based profiling the Cortex-M
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.
|2021-09-02||eCosPro Non-Commercial Public License|