Interrupts

All the input we've done so far has been based on polling. We continually check the status of the input device, and respond to it.

The next basic form of input handling is called interrupts. Think about what happens when you press the reset button: the processor stops whatever it was doing, picks up a new PC from a known address, and continues. This is the basic idea of interrupt handling: the processor is ``interrupted'' by the input.

When we talk about interrupts, we have two concerns: how do we have to set up our environment so that we can use them, and what happens when an interrupt is requested?

Setting Up For Interrupts

The first thing to know is that interrupts can be enabled or disabled, both globally (it's possible to set the CPU so it just doesn't listen at all) and locally (it's possible to set most individual devices so they can't request an interrupt, regardless of whether interrupts are enabled).

This lets us get things set up for interrupts, and then turn them on. The one step that's always needed to set up for interrupts is to initialize the stack pointer (as we'll see, interrupts save the state of the CPU on the stack). If we have other data structures that need to be initialized before we can enable interrupts (maybe buffers for the serial port for instance), we need to do that, too.

The global interrupt disable bit is the "I" bit in the CCR (it's bit 4, just by the way). When the CPU resets the I bit is set (it contains a 1); we enable interrupts by clearing it (putting a 0 in it). When the CPU comes out of a RESET the I bit contains a 1 (this is someplace the simulation is inaccurate); to enable interrupts we use a cli instruction. If later we want to disable interrupts, we can do that with an sei instruction.

This will globally enable interrupts, but may not locally enable them. So if we want to use a timer (for instance), we'll need to enable its interrupts separately. We may want to do this as part of our general initialization (before we enable interrupts), or when we decide to use a particular device; this depends on circumstances. Having said that, I find I normally enable interrupts for individual devices as needed, rather than in my initialization.

Interrupt Handling

The second question is how devices request interrupts, and what we do about it. We'll talk about this in several subsections.

Interrupting Devices

Looking at Page 3 in the pocket guide, we can see there are 21 conditions that can cause interrupts. The majority of these (14 of them) are timers of one sort or another (that's probably the clearest indication that this CPU is intended for real-time applications: lots of timers).

We can also see that 15 of the interrupts are masked by the I bit, five are non-maskable, and one is masked by the X bit. In what follows, the ones that are maskable by the I bit are in some sense "normal," so we'll talk about them first. Once we're done with them, we'll come back and talk about non-maskable interrupts.

Interrupts

A device requests an interrupt when it wants service from the CPU - for instance, the serial port might request an interrupt when it has received a character. A timer will request an interrupt when its time has expired. When the device wants to request an interrupt, two checks are made (in hardware): first, we see if interrupts are globally enabled by checking the I bit. Second, we see if the particular device who wants the interrupt has its interrupts enabled. If both of these conditions are satisfied, the interrupt is serviced before the next instruction is fetched.

The interrupts are prioritized. If more than one device requests an interrupt at the same time, the higher priority interrupt is the one that gets serviced. In the figure on page 3, RESET is the highest priority interrupt and SCI (Serial Communications Interface) is the lowest.

Once we've decided we will process the interrupt, and we've decided which interrupt to process, we take the following steps:

  1. All of the registers get pushed, including the PC and CC registers.
  2. A new PC is loaded from the interrupt vector specified for the device (for instance, IRQ interrupts load the new PC from fff2-fff3).
  3. The I bit is set, so interrupts are disabled.

Interrupt Service Routines

The interrupt service routine contains the code that is executed when the interrupt is serviced. It does, well, whatever is necessary. For instance, for a serial port interrupt, it might move an incoming character into a buffer for later processing. For a timer, it might mark when an event happened.

For some devices, part of the ISR's job is to figure out the details of what interrupt was requested. The SCR interrupt can be triggered by any of five different conditions; the same interrupt is received and the same handler called for all of them. Some of these conditions are normal circumstances (a character has arrived, a character has been sent) and some mark errors (a receiver overrun, for instance - that means a second character arrived at the serial port before I'd read the first one, and so one of them has been lost).

RTI

Finally, you call rti to return from the interrupt. rti pulls all the registers off the stack; this puts the program right back where it was before the interrupt with no evidence left behind that an interrupt happened.

Notice that this means your ISR doesn't need to worry about saving and restoring registers: the interrupt mechanism and the rti instruction take care of all that without our intervention. The one thing you do have to be careful of is that if you've used the stack for anything you need to have it all pulled off again so the stack pointer is pointing to the right place when the rti is executed.

Notice that a side effect of restoring all the registers, the old CCR is reestablished and interrupts are enabled again.

Example

Here's an example, that uses the IRQ interrupt: irq.asm

Uninterruptible Interrupts

Most devices can wait a while for service. How long they can wait can vary from a relatively short time for a fast-running time to somewhat longer for the serial port. A few interrupts are special, in that for one reason or another we don't want them to be disabled, no matter what. In general, the interrupts are uninterruptible either because the condition that requests them is catastrophic, or because the programmer has requested the interrupt directly.

RESET

The RESET interrupt is the prime example of an interrupt that shouldn't be masked. RESET is a very special case, in that it ends execution of the current program, and starts over from scratch. Worrying about whether data structures are inconsistent simply wouldn't make sense here.

Clock Monitor Fail and COP Failure

These are both "watchdog" timers. The COP (Computer Operating Properly) is intended to watch for software failures. If it's being used, then you need to make sure you keep setting it with new time values: if it ever fires, the assumption is that something is wrong and your code has entered a hard loop or is otherwise broken; in this case, the system does a reset (so it doesn't save the program state on the stack) but takes its interrupt vector from fffa-fffb instead of fffe-ffff. This gives us some flexibility: if we want to handle the two kinds of interrupts differently (maybe try to dump some debugging output on COP?) we can, but if we want both interrupts to just start executing the program over again, we can put the same address in both fffa-fffb and in fffe-ffff.

The Clock Monitor Fail is similar, but is looking for hardware failures. This timer watches the 2MHz system clock, and generates a reset if it appears to have stopped.

SWI

The swi instruction is provided to allow the programmer to request an interrupt.

This is so the programmer can request service from the operating system, almost just like a device can. Ordinarily, a programmer would put a code in one of the processor registers or on the stack, and then execute a swi. This goes through all the normal interrupt handling steps, and lands the program at a fixed location in the operating system. The handler now looks at the value in the register, and handles the request.

The point of making swi uninterruptible is that the programmer is the one that uses it. If the user is going to make an swi instruction while the machine is still executing within an interrupt service routine, the HC11 makes the assumption you know what you're doing, and does it.