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.
2024-03-18 | eCosPro Non-Commercial Public License |