I swear this was done yesterday, however it was much too late to post it :)
Wishing to make my interrupt handling mechanism portable to other architectures in the future, I’ve tried to collect information on how interrupts work on as much past and current architectures as possible. Here’s a condensed forms of what I’ve found…
- In order to prioritize interrupts, there are some “Interrupt priority” bits in the processor’s control registers. Interrupts can have 4 priority levels, and there’s level 0 for normal software. An interrupt is not handled until its priority is strictly higher than the current value of the “Interrupt priority” bits, and when it occurs those bits’ values are raised to the interrupt’s priority level. This way, higher-priority interrupts can preempt lower-priority interrupts, but interrupts at a given level do not preempt themselves, which could have lots of unwanted consequences.
- The PDP-11 uses a variant of vectored interrupts where each piece of hardware which may generate interrupt has got the vectors stored in some memory-mapped registers.
- Before running an ISR, the processor saves its PSW (status information which changes on execution of very common instructions) and PC (previously executed code location) on the stack. All other registers must be saved manually as needed.
- This system is made by the same company as the PDP 11 (DEC) and is viewed by said company as its successor, so most things work roughly in the same way, only with more available interrupt priority levels.
- A major difference, though, is that vectors are not stored at a fixed and directly visible location in memory. Instead, the OS stores them on a page, whose location is indicated to the CPU through a special register.
- Another difference is that it is now possible to use a separate stack to handle interrupts, which comes particularly handy when the stack of some software is corrupted, intentionally or not.
MIPS on the PIC32MX
- There are two ways the processor may operate. In single-vector mode, all interrupts are handled by the same code. Multi-vector mode offers the option to have different pieces of code manage different interrupts, but not quite in the same way as above since there’s not enough available interrupt vectors to manage each interrupt with a unique ISR. Even there, there will still be some vector sharing. The location of the interrupt vector(s) is configurable.
- When several interrupts share the same vector, there must be a way to identify which interrupt exactly has been triggered. The MIPS way to do this is to have a look at some special processor registers, where one bit corresponding to the recently triggered interrupt is set.
- Similar registers may be used to deactivate (“mask”) specific interrupts, unlike on above architectures where they only is a standard way to deactivate interrupts according to their priority.
- In multi-vector mode, it is also possible to use an interrupt priority mechanism similar to the one described above.
- Unlike above, the processor saves its full state on the stack before entering the ISR. Interrupts are handled on the same stack that’s used by other software.
ARM (in 2001, more up to date information needed as it seems to have changed !)
- On all previous architectures, the processor could see interrupts coming from a wide range of hardware locations. On ARM, however, the processor only sees two external interrupt sources : IRQ (Interrupt ReQuest) and FIQ (Fast Interrupt reQuest). It is not recommended by ARM to manage FIQ at the OS level, as it should be reserved to hardware where speed is a critical issue, requiring first-class interrupt handling by a specialized driver. FIQ interrupts can preempt IRQ interrupts, and benefit from some other performance optimization.
- Vectors for FIQ and IRQ, along with vectors for exceptions (interrupts from the interior of the CPU, generally caused by a faulty instruction), are located at a specific location of memory, in the first few bytes.
- Each source of interrupts may be processed on its own separate stack. However, ARM, in a very original way, does not save the processor state on said stack, but uses extra registers instead. As usual, the information that’s saved is relatively minimal, only including minimal status information, the location of the previously running code, and that of its stack.
- The OS has to manage one interrupt pin (IRQ), where interrupts are coming from various sources, so this is akin to the “single-vector mode” of the PIC32MX. However, as the ARM architecture holds the current world record of standardization minimalism, there’s no standard way to do that. The ISR must contact an external chip called the Interrupt Controller (IC) and communicate with it in an unspecified way in order to discover where the interrupt comes from.
- There’s a range of interrupt vectors (256 to be precise) which are managed like on MIPS independently from the hardware they relate to. The first 32 are reserved for exceptions like divide by zero, overflow, or invalid instruction, and for NMI (Non Maskable Interrupt), a special kind of external interrupt that, as the name says, can’t be disabled. Other vectors are available for use by software (typically to call kernel functions) or hardware.
- The vectors are stored on a table, where ISR locations are strangely entangled with configuration information for various legacy reasons, in a way that only x86 gurus master.
- It is possible to disable all external interrupts but NMIs by clearing the IF bit of the processor’s control registers, which is done automatically by hardware when an interrupt occurs. So there’s no interrupt priority mechanism in the manner described above : only NMIs may preempt running interrupts. NMIs can’t be disabled, as said before, but if one occurs further ones are blocked until the associated ISR is done.
- On the other hand, using the external PIC or its IO-APIC successor, along with some other routing hardware for more advanced manipulation, it is possible to use a weaker kind of interrupt prioritization (also available on MIPS, although I didn’t mention it) which affects the order in which interrupts will be processed.Mapping external interrupts to interrupt vectors is also done via PICs, and PICs may also be used to mask individual interrupts. Thankfully, although much more complicated to operate than they should, these devices are standard and relatively well documented. A similar priority mechanism is used for exceptions, but their relative priorities cannot be changed.
- Interrupts can optionally be handled on a separate stack. On said stack, x86 processors save the same minimal state information as most other CPUs, along with an error code including some information on what has happened for exceptions.
Alright, now it’s time to build an interrupt handling scheme which works on all of this :)