Name
HAL Port — Implementation Details
Description
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.
Exports
The architectural HAL provides header files cyg/hal/hal_arch.h
, cyg/hal/hal_intr.h
and 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 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 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_variant_init
and
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
microprocessor.
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/hal_misc.c
.
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_GET
and
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,
hal_default_exception_vsr
in
vectors.S
This saves the CPU state and calls
hal_deliver_exception
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.
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 hal_deliver_interrupt
. This
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
CYGSEM_HAL_COMMON_INTERRUPTS_ALLOW_NESTING
)
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_DISABLE_INTERRUPTS
,
HAL_RESTORE_INTERRUPTS
,
HAL_ENABLE_INTERRUPTS
and
HAL_QUERY_INTERRUPTS
. These involve
manipulation of the CPU BASEPRI register. Similarly there are
default implementations of the interrupt controller macros
HAL_INTERRUPT_MASK
, and
HAL_INTERRUPT_UNMASK
macros. These
manipulate the NVIC interrupt mask registers, and the TICKINT bit
of the SYSTICK CSR register.
HAL_INTERRUPT_ACKNOWLEDGE
and
HAL_INTERRUPT_CONFIGURE
are no-ops at the
architectural level.
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
defined: HAL_VAR_INTERRUPT_MASK
,
HAL_VAR_INTERRUPT_UNMASK
,
HAL_VAR_INTERRUPT_ACKNOWLEDGE
,
HAL_VAR_INTERRUPT_SET_LEVEL
and
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
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.
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
configuration option
CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK
has no effect.
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
. 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.
Bit Indexing
The architectural HAL provides inline assembler implementations of
HAL_LSBIT_INDEX
and
HAL_MSBIT_INDEX
which use the CPU
count-leading-zero instruction.
Idle Thread Processing
The architecture HAL provides a default
HAL_IDLE_THREAD_ACTION
implementation that
executes a 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"
(CYGIMP_KERNEL_THREADS_IDLE_NO_HAL_ACTION
) which
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 CYG_HAL_STARTUP
setting
"JTAG" is used irrespective of the hardware debugger type this
would normally disable the use of WFI
within
the idle thread. However, when ITM is configured as available,
the CYGHWR_HAL_CORTEXM_SYSTEM_DEBUG_ALLOW_IDLE
option can be enabled to override the disabling, allowing the
normal WFI
idle behaviour.
Clock Support
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.
HAL I/O
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
those registers.
Cache Handling
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
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/cortexm.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
cortexm.ld
file.
Diagnostic Support
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
off-chip. The CYGHWR_HAL_CORTEXM_DIAGNOSTICS_INTERFACE
can be configured to direct the diagnostic output support used.
See Cortex-M Hardware Debug for more detail regarding using the ITM stimulus port.
SMP Support
The Cortex-M architectural HAL does not provide any SMP support.
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 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.
HAL_DELAY_US() Macro
cyg/hal/hal_intr.h
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.
Profiling Support
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.
2024-03-18 | eCosPro Non-Commercial Public License |