Name

HAL Port — Implementation Details

Description

This documentation explains how the eCos HAL specification has been mapped onto Nios II hardware and should be read in conjunction with that specification. It should be noted that the architectural HAL is usually complemented by a hardware design 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_cache.h, cyg/hal/hal_io.h and cyg/hal/arch.inc. These header files export the functionality provided by all the Nios II 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.

One header file is worth a special mention: pkgconf/nios2_hwconfig.h. This file is provided by the hardware design HAL and contains definitions such as base addresses and interrupt vectors. It is automatically included and used by the architectural HAL headers, but its contents may prove useful to application developers.

Data Types

The architectural HAL assumes that the Nios II cpu in the hardware design uses 32-bit arithmetic, little-endian byte ordering, and software floating point.

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 clearing the instruction and data caches, if present, and copying initialized data from flash to RAM. For all startup types it will involve zeroing bss regions and setting up the stack and the general C environment. It may also include installing the exception vector code at the desired location as well as copying code and data to on-chip RAM or external SRAM as required. The platform HAL can override or extend this as required. The code assumes that all of flash is directly accessible. Platform HALs may override this as required, for example when booting from a serial flash rather more work is needed to copy data from flash to RAM. More information on the low-level startup code can be found in the source file src/vectors.S.

The architectural HAL also implements the next stage of the startup code, including initializing the VSR and virtual vector tables, setting up HAL diagnostics, and invoking C++ static constructors, prior to calling the first application entry point cyg_start. This code resides in src/nios2.c.

The current code assumes that there is no memory management or MMU and hence will not perform any MMU initialization.

Interrupts and Exceptions

The architectural HAL provides default implementations of HAL_DISABLE_INTERRUPTS, HAL_RESTORE_INTERRUPTS, HAL_ENABLE_INTERRUPTS and HAL_QUERY_INTERRUPTS. These just involve simple manipulation of the status control register. Similarly there are default implementations of the interrupt controller macros HAL_INTERRUPT_MASK, HAL_INTERRUPT_UNMASK, and HAL_QUERY_INTERRUPT_MASKED macros. These are slightly more complicated to cope with nested interrupt scenarios, involving a shadow mask as well as the ienable control register. HAL_INTERRUPT_ACKNOWLEDGE is a no-op because the hardware has no need for clearing an interrupt centrally. Instead interrupts must be acknowledged within each device as appropriate, using device-specific code.

HAL_INTERRUPT_CONFIGURE is a no-op. This macro is normally only relevant to GPIO interrupts, affecting level versus edge triggering, and on a Nios II an entire GPIO unit generates a single interrupt but the various inputs to that port can be controlled individually. Instead it is up to application code to set the various registers within each GPIO unit appropriately.

HAL_INTERRUPT_SET_LEVEL is also a no-op. However the architectural HAL does support prioritized nested interrupts when CYGSEM_HAL_COMMON_INTERRUPTS_ALLOW_NESTING is enabled. The implementation assumes that interrupt vector 0 has the highest priority, down to interrupt vector 31 as the lowest. In other words, assuming nested interrupt support is enabled, if the cpu is busy processing an interrupt from the device attached to interrupt vector 9 and an interrupt 0 occurs then the latter will be handled immediately and the processing of vector 9 resumes later. Since the assignment of interrupt vectors to devices in SOPC Builder is arbitrary this gives full flexibility without the overheads of managing priorities in software.

Interrupt handlers are managed by a simple table cyg_hal_interrupt_handlers so the implementation of HAL_INTERRUPT_ATTACH, HAL_INTERRUPT_DETACH, and HAL_INTERRUPT_IN_USE is straightforward.

By default interrupt handlers run on a separate interrupt stack. This saves memory because there is no need to allow for interrupt processing overhead on every thread stack. However switching to the interrupt stack requires a number of extra instructions so increases the interrupt latency. If the latter is more important than memory usage then CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK should be disabled.

That leaves VSR management and exceptions. On the Nios II interrupt and exception processing is somewhat simpler than on most other architectures. Typically the cpu indirects through a table in memory, the VSR table, with the table index depending on the interrupt vector or the exception being thrown. The eCos macro HAL_VSR_SET updates an entry in the table, allowing applications to take over completely certain interrupt sources and process them as quickly as possible, bypassing the overheads of the default general-purpose VSR handler used by eCos. For example a custom VSR written in assembler could save only a few registers before manipulating the hardware, whereas the general-purpose VSR handler needs to save much of the cpu state before calling the application's interrupt handler written in C. The net result is reduced interrupt latency for critical interrupts, at the cost of more complicated application code.

The Nios II implementation is very different. When an interrupt or exception occurs the cpu jumps to a fixed location in memory defined in the hardware design, the exception vector. The code at that location needs to determine whether it was invoked as the result of an interrupt or a processor exception. There is no hardware equivalent of the VSR table. eCos needs to provide the implementation of the code at the exception vector and there is no simple way for applications to provide a customized version. That makes it difficult for an application to handle critical interrupts with a minimum latency. It also causes other complications, for example it makes it difficult for a RAM startup eCos application to handle interrupts while the gdb stubs inside RedBoot handle exceptions including breakpoints.

To avoid these problems, the Nios II architectural HAL implements a VSR table in software. The code at the exception vector simply indirects through slot 0 of the VSR table, which involves a three instruction overhead compared with a more conventional implementation. Applications needing very fast handling of critical interrupts can use HAL_VSR_SET to install a custom handler in slot 0. That handler can check whether or not a critical interrupt is pending and process it immediately, Otherwise it can chain to the original VSR handler. The overall effect is to provide a mechanism for very fast handling of certain interrupts, at the cost of an extra three instructions for ordinary interrupts which are handled by interrupt handlers written in C.

The default handler for VSR 0 checks whether or not any interrupts are pending. If so then it saves the current cpu state, or the minimum subset thereof if CYGDBG_HAL_COMMON_INTERRUPTS_SAVE_MINIMUM_CONTEXT is enabled, switches to the interrupt stack if necessary, and invokes the appropriate interrupt handler installed via HAL_INTERRUPT_ATTACH or the higher-level functions like cyg_interrupt_create or cyg_drv_interrupt_create.

If the default handler for VSR 0 determines that no interrupt is pending then it must have been invoked as the result of a processor exception. The cpu provides only minimal support for detecting the nature of the exception. A breakpoint trap can be detected by examining the instruction that caused the exception. Anything else is treated as an illegal instruction. Both exceptions are processed by indirecting through further slots in the VSR table, thus allowing the gdb stubs code inside RedBoot to handle exceptions inside an eCos application. It should be noted that this mechanism is critically dependent on reliable interrupt reporting. If it is possible for an interrupt line to glitch, causing an interrupt but leaving the ipending register clear before the VSR 0 handler reads it, then this will be interpreted as an illegal instruction exception. Conceivably this can also affect device drivers if a write to a hardware register may result in an interrupt being cleared in a couple of cycles just as that interrupt is about to happen.

The full implementation of the interrupt and exception handling code can be found in src/nios2asm.S, and that code can be used as the starting point for custom application VSRs.

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. Specifically if the use of a separate interrupt stack CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK is disabled to reduce interrupt latency then thread stacks have to be rather larger to cope with the interrupt processing overhead. If nested interrupts CYGSEM_HAL_COMMON_INTERRUPTS_ALLOW_NESTING are also enabled then thread stacks must be much larger.

The Nios II architectural HAL always provides a separate stack to run the startup code and for exception processing. This stack will also be used for interrupts if CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK is enabled, and its size is determined by CYGNUM_HAL_COMMON_INTERRUPTS_STACK_SIZE.

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/nios2asm.S. The context structure is straightforward, containing space for the integer registers and the status, ienable and ipending registers. Any floating point arithmetic is assumed to be implemented in software. A single data structure is used for the thread context in all eCos configurations. However some fields will only be used in certain configurations, for example when CYGDBG_HAL_COMMON_INTERRUPTS_SAVE_MINIMUM_CONTEXT is disabled. The use of a single data structure avoids complications when debugging a RAM startup application on top of a ROM RedBoot because both need to use the same structure.

Bit Indexing

The architectural HAL provides an assembler implementation of HAL_LSBIT_INDEX, optimized for faster context switching to higher priority threads. HAL_MSBIT_INDEX uses a straightforward C implementation since it is not performance-sensitive.

Idle Thread Processing

The Nios II instruction set does not include an idle instruction so the idle thread always simply spins, rather than suspend the cpu until an interrupt occurs.

Clock Support

The architectural HAL provides a default implementation of the various system clock macros such as HAL_CLOCK_INITIALIZE. These macros assume that the hardware design implements the system clock using a simple Avalon timer. A platform HAL can provide an alternative implementation if necessary but that is unlikely ever to be necessary because including an Avalon timer in the design is generally straightforward. The timer may be designed with either a fixed period, typically 10 milliseconds to give a 100Hz system clock, or with a variable period in which case there will a configuration option CYGNUM_HAL_RTC_PERIOD to control the clock frequency. Ideally the system clock should be designed with the readable snapshot option enabled. Otherwise the HAL will not be able to provide the HAL_CLOCK_READ macro and there will be no support for clock timings with a finer granularity than the interval between interrupts.

HAL I/O

The various I/O macros for accessing hardware registers such as HAL_READ_UINT8 are implemented using the ldbuio and related instructions. This ensures that all I/O accesses bypass any data cache that may be included the hardware design.

Cache Handling

The hardware design may include instruction and data caches, and there is some control over parameters such as the cache sizes and the line sizes. cyg/hal/hal_cache.h defines those cache macros which are appropriate for the current hardware design, typically the INVALIDATE, INVALIDATE_ALL, FLUSH, STORE and SYNC ones. The hardware does not allow the caches to be enabled or disabled so the IS_ENABLED macro always returns a 1 and the ENABLE and DISABLE macros are never defined. Note that this may confuse some existing code which assumes that these macros are always available.

In addition cyg/hal/hal_arch.h defines macros CYGARC_CACHED_ADDRESS and CYGARC_UNCACHED_ADDRESS which assume that the cpu only addresses 31 bits worth of address space, and that addresses with the top bit set access the same memory locations as those with the top bit clear, but bypassing the cache. This is the default behaviour for Nios II processors. The header file also provides a HAL_MEMORY_BARRIER macro which issues a sync instruction to cause pending memory operations to complete.

Linker Scripts

The architectural HAL will generate the linker script for eCos applications. This involves the architectural file src/nios2.ld and a .ldi memory layout file, typically provided by the platform HAL but using some definitions from the hardware design 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 nios2.ld file. This includes macros for placing code and data in on-chip RAM as well as external flash and SDRAM, using linker sections .iram_text, .iram_data and .iram_bss. Code should only be placed in on-chip RAM if the hardware design includes a connection between the RAM and the cpu's instruction master port.

Diagnostic Support

By default the architectural HAL provides a diagnostics and debug channel using the first uart in the hardware design. If the design does not include any uarts then all diagnostics output will instead be discarded. The configuration option CYGIMP_HAL_NIOS2_DIAGNOSTICS_PORT can be used to select discard mode even when a uart is available. If the hardware includes an ethernet device then debugging is still possible over the network. Alternatively when debugging via JTAG it is possible to direct the diagnostics output to a gdb hwdebug file I/O channel. By default this will also discard diagnostics output. However if the application is running inside a gdb session and the gdb set hwdebug command has been used then the diagnostics will be output via gdb. Platform HALs may implement alternative diagnostics facilities.

SMP Support

The Nios II architectural HAL does not provide any SMP support.

Debug Support

The architectural HAL provides basic support for gdb stubs. Due to a conflict between jtag and stubs gdb support, breakpoints are always implemented using a fixed-size list of breakpoints, as per the configuration option CYGNUM_HAL_BREAKPOINT_LIST_SIZE. A hardware design may include hardware breakpoint support but these are not accessible to the gdb stubs code, only via jtag. Hence if a debug session requires the use of hardware breakpoints, for example when debugging code in flash, a jtag-based debug solution must be used instead of gdb stubs.

HAL_DELAY_US() Macro

cyg/hal/hal_intr.h provides a simple implementation of the HAL_DELAY_US macro using a busy loop. It requires that the hardware design HAL provides a count value HAL_NIOS2_DELAY_US_LOOPS appropriate to the cpu speed and the absence or presence of the instruction cache.

Other Functionality

If the hardware design includes a system id register then all RedBoot builds will include some extra initialization code checking the register's current value against the one specified by the hardware design HAL, reporting any mismatches. This helps to guard against accidentally running the wrong build of RedBoot or the wrong hardware design. Note that if there is a serious incompatibility between the two then system bootstrap may fail long before this check gets to run, or the diagnostics channel may be inoperable preventing the warning from reaching the user.

The architectural HAL provides the support needed by the gprof profiling package. This includes the mcount needed for callgraph profiling and hal_enable_profile_timer for timer-based profiling. The latter can be implemented in two ways. If the hardware design includes a dedicated Avalon timer labelled “profiling” then this will be used. Otherwise the sys_clk timer will be used for profiling as well as for the main system clock. Overloading the system clock in this way is less desirable because it means the profiling sampling is likely to miss any code that runs after clock events.

Otherwise the Nios II architectural HAL only implements the functionality provided by the eCos HAL specification and does not export anything extra.