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.