CS3 provides standard handlers for interrupts, exceptions and traps, but also allows you to easily define your own handlers as needed. In this section, we use the term interrupt as a generic term for this entire class of events.
Different processors handle interrupts in various ways, but there are two general approaches:
Some processors fetch an address from an array indexed by the interrupt number, and jump to that address. We call these address vector processors; ColdFire systems are a typical example.
Others multiply the interrupt number by some constant factor, add a base address, and jump directly to that address. Here, the interrupt vector consists of blocks of code, so we call these code vector processors; PowerPC systems are a typical example.
On address vector processors, the CS3 library provides an array
of pointers to interrupt handlers named
__cs3_interrupt_vector_
,
occupying a section named
form
.cs3.interrupt_vector
, where
form
identifies the particular
processor variant the vector is appropriate for. If the
processor supports more than one variety of interrupt vector
(for example, a full-length form and a shortened form), then
form
identifies the variety as well.
Each entry in the vector holds a reference to a symbol named
__cs3_isr_
,
where int
int
is the customary name of
that interrupt on the processor, or a number if there is no
consistently used name. The library further provides a
reasonable default definition for each
__cs3_isr_
handler routine.
int
To override an individual handler, provide your own definition
for the appropriate
__cs3_isr_
symbol. The definition need not be placed in any particular
object file section.
int
Interrupt handlers typically require special call/return and
register usage conventions that are target-specific and beyond
the scope of this document. As an alternative to writing
interrupt handlers in assembly language,
on some targets they may be written in C using the
interrupt
attribute. For example, to override
the __cs3_isr_access_error
handler, use the following
definition:
void __attribute__ ((interrupt)) __cs3_isr_access_error (void) { ... custom handler code ... }
To override the entire interrupt vector, you can
define
__cs3_interrupt_vector_
,
placing the definition in a section named
form
.cs3.interrupt_vector
. The linker script
reports an error if the .cs3.interrupt_vector
section is empty, to ensure that the definition of
__cs3_interrupt_vector_
occupies the proper section.
form
You may define the vector in C with an array of pointers using
the section
attribute to place it in the
appropriate section. For example, to override the interrupt
vector on M5208EVB boards, make the following definition:
typedef void handler(void); handler *__attribute__((section (".cs3.interrupt_vector"))) __cs3_interrupt_vector_coldfire[] = { ... };
On code vector processors, we follow the same conventions, with the following exceptions:
In addition to being named
__cs3_isr_
,
each interrupt handler must also occupy a section named
int
.cs3.interrupt_
.
Naturally, each handler must fit within a single interrupt
vector entry.
int
Instead of providing a default definition for
__cs3_interrupt_vector_
in the library, the linker script gathers the
form
.cs3.interrupt_
sections together, in the proper order and on the
necessary address boundaries, and defines the
int
__cs3_interrupt_vector_
symbol to refer to its start.
form
To override an individual handler on a code vector processor,
you provide your own definition for
__cs3_isr_
,
placed in an appropriate section. The linker script ensures
that each
int
.cs3.interrupt_
section is non-empty, so that placing a handler in the wrong
section elicits an error at link time.
int
CS3 does not allow you to override the entire interrupt vector on code vector processors, because the code vector must be constructed by the linker script, and thus cannot come from a library or object file. However, the portion of the linker script that constructs the interrupt vector occupies its own file, which other linker scripts can incorporate using the INCLUDE linker script command, making it easier to replace the linker script entirely and still take advantage of CS3's other features.
Some processors, like the Innovasic fido, use more than one interrupt vector: the processor provides several interrupt vector pointer registers, each used in different circumstances. Each register may point to a different vector, or some or all may share vectors.
On these processors, CS3 provides only a single pre-constructed interrupt vector, but defines a separate symbol for each interrupt vector pointer register; all the symbols point to the pre-constructed vector by default. The CS3 startup code initializes each register from the corresponding symbol. You can provide your own vectors by defining the appropriate symbols.
For example, the fido processor has five contexts, each of which
can use its own interrupt vector; on this architecture, CS3
defines the
standard __cs3_interrupt_vector_fido
symbol
referring to the pre-constructed vector, and then goes on to
define per-context
symbols __cs3_interrupt_vector_fido_ctx0
,
__cs3_interrupt_vector_fido_ctx1
, and so on,
all referring to __cs3_interrupt_vector_fido
.
The CS3 startup code sets each context's vector register to the
value of the corresponding symbol. By default, all the contexts
share an interrupt vector, but if your code provides its own
definition
for __cs3_interrupt_vector_fido_ctx1
, then
the startup code initializes context one's register to point to
that vector instead.
This arrangement requires you to use a different approach to
specify a handler for a secondary context that differs from the
corresponding handler in the primary context. For example, to
handle division-by-zero exceptions in context 1 with the
function ctx1_divide_by_zero
, you should
write the following:
typedef void (*handler_type) (void); handler_type __cs3_interrupt_vector_fido_ctx1[256]; extern handler_type __cs3_interrupt_vector_fido[256]; __attribute__((interrupt)) void ctx1_divide_by_zero (void) { /* Your code here. */ } __attribute__((constructor)) void initialize_vector_ctx1 (void) { /* Initialize our custom vector from the pre-constructed CS3 vector. */ memcpy (__cs3_interrupt_vector_fido_ctx1, __cs3_interrupt_vector_fido, sizeof (__cs3_interrupt_vector_fido)); /* Initialize custom interrupt handlers. */ __cs3_interrupt_vector_fido_ctx1[5] = ctx1_divide_by_zero; }
With this code in place, when a division-by-zero exception
occurs in context 1, the processor
calls ctx1_divide_by_zero
to handle it.
Defining initialize_vector_ctx1
with
the constructor
attribute arranges for CS3 to
call it before calling your program's main
function.