ARM interrupts

This chapter describes how to write interrupt handlers in both Forth and assembler. It details how to set up and control interrupt handlers. The first part of the chapter describes how to set up and use the interrupts, and the second part provides more technical detail.

Different ARM implementations use different interrupt controllers, although many are derived from, or similar to the ARM PL190 controller. The code for the ones we have used is collated into the file ARM\IntARM3.fth. The syntax setting for interrupt handlers varies slightly between the controllers.

Interrupts on the ARM

When an interrupt occurs and is accepted, an ARM processor branches through a location (a `vector') in low memory. The vector table starts at address 0, the vector used being dependent on the source of the interrupt. The code at the address stored in the vector table is then executed. For more information on interrupts for the ARM, refer to the processor's user guide. In this chapter we are mostly interested in the IRQ and FIQ interrupts, although the principles apply equally well to the other interrupts.

Writing Forth interrupt handlers

A Forth interrupt service routine (ISR) is just like any other Forth word. It can therefore be tested and debugged like a normal Forth word. Only when the word is fully tested need it be assigned to an interrupt.

The interrupt handler allows you to chain several interrupt handlers onto the primary IRQ and FIQ handlers. This makes complex code much easier to manage. The action of of each handler should be:

Setting an interrupt

  ' A/D-READY ADD-IRQ

which adds the word A/D-READY to the IRQ chain.

Some common problems

There are a few common problems that might cause an interrupt not to work correctly:

Stack faults

An interrupt service routine can use the stack while it is executing, but must clear up the stack before returning from the interrupt. The normal symptom of a stack fault is that the interrupt handler runs but then the target board crashes, either immediately or after a length of time.

Source is not cleared

Once an interrupt handler is triggered by an interrupt, the source of the interrupt must be told that the interrupt is being serviced. If this is not done, the source of the interrupt will carry on generating interrupts. Normally this appears as the interrupt handler executing once and then the target board `locking'. Interrupts are not enabled

Interrupts need to be enabled with EI before any interrupts will be serviced. The vectors must be set up or the interrupt handler assigned to the deferred word before the interrupts are enabled.

Writing assembler interrupt handlers

If you use the standard IRQ and FIQ chains, just write your handler as a CODE ... END-CODE definition and add it to the chain in the usual way. If you want to replace one of the MPE interrupt handlers, please use the code in ARM\INTARM3.FTH as an example if you have not written ARM interrupt handlers before.

Controlling the interrupts

Interrupts can be in one of two states, enabled or disabled.

Enabling interrupts

Disabling interrupts

To disable interrupts use DI. Once DI has been executed, all interrupts are disabled. Some MPE code uses DI to disable the IRQ interrupt, and DFI to disable the FIQ line.

A simple example

The following example patches a high-level interrupt service routine (ISR) onto a timer interrupt.

The timer ISR

The example ISR increments a variable that can be fetched in the foreground to detect that the timer is working. The ISR also needs to acknowledge that the interrupt has happened.


VARIABLE TICKS
: TIMER-SERVICE \ --
  _TCSR C@ DROP         \ must *read* the register
  _TCSR_VALUE _TCSR W!  \ clear interrupt
  1 TICKS +!                    \ increment counter
;

Initialising the timer

Before the timer ISR will run it requires initialising:


_TCSR_/32                 \ select required clock rate

_TCSR_TME OR              \ enable timer and
_TCSR_PASSWORD OR         \ use "password"
EQU _TCSR_VALUE           \ to build the *word* that
                          \ we will send to tscr.
: TIMER-INIT   \ --
  _TCSR_VALUE _TCSR W!    \ enable timer, select rate
  EI                      \ enable interrupts
;

Patching your ISR onto the timer

The ISR needs to be patched onto the timer interrupt. This interrupt has been setup so you need only to assign the timer code to the DEFERed word TOV-ISR.

  ASSIGN TIMER-SERVICE TO-DO TOV-ISR

Testing the timer is running

The timer needs to be initialised with TIMER-INIT and then the timer can be tested by checking the variable TICKS.


TIMER-INIT
TICKS ?
TICKS ?

This displays the current value of TICKS.

Interrupt handlers in detail

When an interrupt occurs and is accepted, an ARM CPU branches through a location (a `vector') in low memory. A conventional interrupt handler written in assembler simply saves any registers and fixed locations it will use, then calls or executes the required routine, and then restores the previous state of registers and locations, and returns. The Forth interrupt handlers work in the same way, with a few additions.

Interrupt handlers written entirely in assembler are written in the usual way. When a Forth VARIABLE is referred to by name inside a code section, its data address in RAM is returned. Thus variables can be shared between high level routines and subroutines or assembler interrupt handlers. The code for the interrupt handler can be found in the file ARM\INTARM3.FTH. This source code can be modified and updated as required. The interrupt handler code can be adapted for single-chip use. This file supports a number of different interrupt controllers. The functions supported are documented within the file itself and in the ARM specific target manual. The files INTARM.FTH and INTARM2.FTH are provided for compatibility with earlier releases, but are no longer supported by MPE. We strongly encourage you to convert code using the earlier files to use INTARM3.FTH.

Interrupt structure

A separate user area is reserved for the interrupt handler, and is shared by all the high-level interrupt handlers. Consequently high-level Forth interrupts cannot be nested if user variables are changed, although an interrupt that does not use the Forth interrupt handler can be nested. The layout of the interrupt RAM page is identical to that for a task page (see later).

When an interrupt that has a high-level handler occurs, all registers are pushed onto the stack. These are then loaded with the required values for interrupt processing.

The interrupt code causes the processor to jump to an area of memory that contains calls to two words. The first is a call to a word to run the interrupt; this can be reassigned. The second is a call to the return from interrupt word.

Setting an interrupt

To set the action of an interrupt, simply assign that word to do the appropriate deferred word. The return from interrupt is provided by the second word of the table entry. This means that the action word need contain no return action, and consequently it may be tested from the keyboard. In the earlier example, the variable TICKS can be inspected to see that the timer is running. Changes to this structure can be made by rebuilding the Forth kernel with the cross compiler.

In particular, some interrupts may need to be serviced by an assembler routine, and the overhead of the Forth dispatcher is unwanted. In this instance edit the interrupt entry code to call a different routine.

Since ARM interrupts (exceptions) are vectored, the final requirement is to set the vector itself.

If you wish to add vectors for processing other exceptions, you should use the code provided in INTARM3.FTH as a prototype, and follow a similar structure.

The words ASSIGN and TO-DO can be used to assign a Forth word to be executed by the interrupt handler in the following manner.

  ASSIGN action TO-DO xxxx-ISR

where xxxx is the name of the interrupt, as given in the table below, and action is the Forth word that the interrupt handler must execute. Thus to set up a complete interrupt system the vector must be assigned, and the word connected to the interrupt structure.

ASSIGN action TO-DO xxxx-ISR
<xxxx-INT0>  <vector-name> !

where <vector-name> is the vector name. Note that interrupt handlers written in assembler do not need to use these structures at all. They must still, however, set the CPU vectors in low memory.

Source Vector name Entry point Deferred word
SWI SWIV SWI_exception SWI_handler
Undef UndefV Undef_exception Undef_handler
Prefetch abort PabortV PAbort_exception PAbort_handler
Data abort DabortV DAbort_exception DAbort_handler
Unused UnusedV Unused_exception Unused_handler
IRQ IRQV IRQ_exception Chained by ADD-IRQ
FIQ FIQV FIQ_exception Chained by ADD-FIQ

Interrupt protection

The word [I saves the current interrupt status on the return stack, and disables interrupts, which can then be restored by I] which restores the interrupt mask to the state saved by [I. If simple enabling and disabling of interrupts is required, the words EI and DI respectively perform these functions. [I and I] are particularly necessary for critical sections of code, and for implementing semaphores. In general the use of [I ... I] requires less code than the use of SAVE-INT ... RESTORE-INT below, which are now deprecated.

The word SAVE-INT saves the current interrupt status, and disables interrupts, which can then be restored by RESTORE-INT which restores the interrupt mask to the state saved by SAVE-INT.

On the ARM compiler these words are implemented by code generators, and so it is not necessary to supply target definitions for them. If they are required interactively, example definitions can be found in LIBARM.FTH, and will be included automatically if required, providing that you use the relevant LIBRARIES ... END-LIBS sequence.

End of interrupt requirements

Certain interrupts on the ARM cores require that the programmer explicitly clear the interrupt source before returning from the interrupt. This action must be performed by the high-level interrupt routine. In many cases, resetting the appropriate bit in a status register clears the interrupt source. Glossary

This glossary contains details of the major words in the interrupt system. Other words exist, but are only used as fractions of the words below. The source code for all these words may be found in ARM\IntARM3.fth.

code DI         \ --
Disables interrupts.

code EI         \ --
Enables interrupts.

code [I         \ R: -- x
Save the current interrupt status on the return stack and disable interrupts. This word can only be used inside a colon definition and [I and I] must be used in matching pairs.

code I]         \ R: x --
Restore the interrupt status from the return stack. This word can only be used inside a colon definition and [I and I] must be used in matching pairs.

code RESTORE-INT        \ x --
Restore the interrupt enable state previously saved by SAVE-INT. OBSOLETE, use I] instead.

code SAVE-INT           \ -- x
Saves the current state of the interrupt enable on the stack, and disables interrupts. See RESTORE-INT. OBSOLETE, use [I instead.