Interrupt Vectors and Handlers

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:

On address vector processors, the CS3 library provides an array of pointers to interrupt handlers named __cs3_interrupt_vector_form, occupying a section named .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_int, where 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_int handler routine.

To override an individual handler, provide your own definition for the appropriate __cs3_isr_int symbol. The definition need not be placed in any particular object file section.

Interrupt handlers may be written in C using the interrupt attribute. For example, to override the __csr_isr_nmi handler, use the following definition:

void __attribute__ ((interrupt)) __csr_isr_nmi (void)
{
  ... custom handler code ...
}

To override the entire interrupt vector, you can define __cs3_interrupt_vector_form, placing the definition in a section named .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_form occupies the proper section.

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 a Cyclone III Cortex-M1 board, make the following definition:

typedef void handler(void);
handler *__attribute__((section (".cs3.interrupt_vector")))
  __cs3_interrupt_vector_micro[] =
{ ... };

On code vector processors, we follow the same conventions, with the following exceptions:

To override an individual handler on a code vector processor, you provide your own definition for __cs3_isr_int, placed in an appropriate section. The linker script ensures that each .cs3.interrupt_int section is non-empty, so that placing a handler in the wrong section elicits an error at link time.

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.