OSD Home

- # - Interrupt/exception classification
- # - Sequence of events after interrupt/exception
- # - How does the CPU 'find' the handlers?
- # - Which interrupts should be handled?
- # - The 8259 programmable interrupt controller (PIC) chips
- # - Distinguishing between CPU exceptions, IRQs, and traps
- # - Code snippets
- # - Links
- REPORT BUGS OR ERRORS IN THIS DOCUMENT

## Interrupt/exception classification

Terms taken from Intel 386 Programmers Reference Manual: http://my.execpc.com/~geezer/os/386intel.zip

- Interrupts come from hardware outside the CPU:
  - Maskable interrupts (IRQs)
  - Non-maskable interrupt (NMI)
  Maskable interrupts can be enabled (turned on) at the CPU using the `STI` instruction, or disabled (turned off) using `CLI`. Both NMI and the IRQs can also be masked with hardware outside the CPU.
- Exceptions come from inside the CPU:
  - Processor-detected exceptions:
    - Fault: happens 'before' the current instruction. Examples: divide by 0, invalid opcode, page fault.
    - Trap: happens 'after' the current instruction. All of the programmed exceptions below are traps.
    - Abort: location of exception can't be determined. Examples: double fault, coprocessor segment overrun.
  - Programmed exceptions
    - `INT` instruction
    - `INT3` instruction
    - `INTO` instruction
    - `BOUND` instruction

The 32 reserved interrupts/exceptions:

number | interrupt/exception | notes
--- | --- | ---
0 | divide error | could be overflow as well as zero denominator
1 | debug exception
2 | non-maskable hardware interrupt (NMI)
3 | `INT3` instruction | debugger breakpoint
4 | `INTO` instruction detected overflow
5 | `BOUND` instruction detected overrange
6 | invalid instruction opcode
7 | no coprocessor | `ESC`, `WAIT` instructions
8 | double fault | possible for 386+ in real mode if INT vector exceeds IDT limit
9 | coprocessor segment overrun | not in real mode
10 | invalid task state segment (TSS) | not in real mode
11 | segment not present | not in real mode
12 | stack fault | possible in real mode if stack access straddles offset 0 or FFFFh
13 | general protection fault (GPF) | possible in real mode if non-stack memory access straddles 0 or FFFFh, or if instruction is longer than 15 bytes, or if a 32-bit address greater than FFFFh is used
14 | page fault | not in real mode
15 | (reserved)
16 | coprocessor error | `ESC`, `WAIT` instructions
17 | alignment check | not in real mode; 486+ only
18 | machine check | Pentium+ only
19-31 | (reserved)

## Sequence of events after interrupt/exception

#### CPU actions after interrupt/exception

1. If there is a pmode privilege transition, the CPU loads the SS and (E)SP from the current Task State Segment (TSS).

2. If there is a pmode privilege transition, the old values of SS and (E)SP are pushed on the new stack.

3. CPU pushes CS, (E)IP, and (E)FLAGS registers on the (new) stack. Some pmode exceptions also push an error code.

4. CPU clears IF bit in (E)FLAGS, disabling further interrupts. This does not happen with protected mode trap gates.

5. CPU does a far jump; loading CS and (E)IP registers, to interrupt handler code.

There are ten possible exception/interrupt stack frames on the 32-bit x86. Three of them are associated with 16-bit pmode; two with virtual 8086 mode. The other five are:

    <- 16 bits ->
    <- 32 bits ->
    <- 32 bits ->
    <- 32 bits ->
    <- 32 bits ->
... | ... | ... | ... | ...
... | ... | ... | ... | ... | user SS
... | ... | ... | ... | user SS | user ESP
... | ... | EFLAGS | user ESP | EFLAGS
FLAGS | EFLAGS | ... | CS | EFLAGS | ... | CS
CS | ... | CS | EIP | ... | CS | EIP
IP | EIP | error code | EIP | error code
16-bit real mode | 32-bit pmode, no privilege change; no error code. | 32-bit pmode, error code; no privilege change | 32-bit pmode, privilege change; no error code | 32-bit pmode, privilege change; error code

#### Handler code prologue

6. The handler must save on the stack all registers that it modifies. Since the programmed exceptions are 'expected', the handler can modify some registers without saving them (e.g. DOS or BIOS interrupts that return data in the registers).

In any case, it is often easier to just save all registers on the stack. If the handler needs to return a value in a register, it can manipulate the stacked register values.

7. The handler must put known-good values into segment registers before using them.

#### Handler code epilogue

8. When the handler is done, it must restore saved registers from the stack.

9. If the exception pushed an error code, the handler must pop it now and discard it.

10. The handler must terminate with an `IRET` instruction, not the usual `RET`.

#### Actions taken by the `IRET` instruction

11. `IRET` pops (E)IP, CS, and (E)FLAGS. If a pmode privilege transition occurs, it also pops SS and (E)SP.

## How does the CPU 'find' the handlers?

In real mode, there is an Interrupt Vector Table (IVT) at address 0000:0000, with 256 4-byte entries. Each entry is a far address:

byte in IVT entry | usage
--- | ---
0 | LSB of handler offset (IP 7:0)
1 | MSB of handler offset (IP 15:8)
2 | LSB of handler segment (CS 7:0)
3 | MSB of handler segment (CS 15:8)

In protected mode, there is an Interrupt Descriptor Table (IDT), at any address, with up to 256 8-byte entries. Each entry is a protected mode gate descriptor:

byte in IDT entry | usage
--- | ---
0 | byte 0 (LSB) of handler offset (EIP 7:0)
1 | byte 1 of handler offset (EIP 15:8)
2 | LSB of handler selector (CS 7:0)
3 | MSB of handler selector (CS 15:8)
4 | (not used, set to 0)
5 | access byte (8Eh, 8Fh, 0EEh, or 0EFh)
6 | byte 2 of handler offset (EIP 23:16)
7 | byte 3 (MSB) of handler offset (EIP 31:24)

Note that the 32-bit EIP value is broken into two 16-bit fields in the gate descriptor, with four bytes between them. Few object file formats support the type of relocation needed for such a value, so the IDT must be built at run-time.

The IDT may contain interrupt gates (access byte = 8Eh or 0EEh) or trap gates (access byte = 8Fh or 0EFh). Interrupt gates clear the IF bit when an interrupt occurs, disabling further hardware interrupts. Trap gates leave the IF bit unchanged.

The access byte should be set to 8Eh or 8Fh for ring 0 (kernel privilege) gates, or to 0EEh or 0EFh for ring 3 (user privilege) gates. If code running at ring 3 tries to use an `INT` instruction to transit a ring 0 gate, a general protection fault will occur instead.

You must use the `LIDT` instruction to tell the CPU the size and location of the IDT. Like `LGDT`, `LIDT` accepts a 6-byte 'pseudo-descriptor' (2-byte table limit followed by 4-byte linear address of table).

## Which interrupts should be handled?

You do not need an IVT or IDT if:

- Your code is sufficiently correct that it does not cause processor-detected exceptions (this is the hard part), and
- Your code does not use programmed exceptions (`INT nn` and related instructions), and
- Hardware interrupts are disabled.

Otherwise, you should handle the 32 reserved exceptions (0-31), any additional programmed exceptions that are used by your OS code, plus (if you enable hardware interrupts) the 16 IRQs. It is not necessary to completely fill the IDT, but you may want to fill the real-mode IVT, as a debugging measure.

## The 8259 programmable interrupt controller (PIC) chips

These chips make hardware interupts (IRQs) behave like software interrupts (INT instruction). (Remember, though, that hardware interrupts are asynchronous, meaning they can happen at any time.)

The BIOS programs the 8259 chips so that IRQs 0-7 are mapped to interrupts 8-15. Because interupts 8-15 are reserved for use by the CPU, this mapping makes it difficult to determine if an interrupt came from the CPU or external hardware. Unless you need DOS or BIOS compatability, the PICs should be re-programmed so IRQs do not use any of the CPU-reserved interrupts (see code snippet below).

The 8259 chips can mask (enable and disable) individual IRQs. Zeroing a bit at port 21h or A1h enables the corresponding IRQ:

Bits at I/O port 21h (1st 8259) | IRQ | Bits at I/O port A1h (2nd 8259) | IRQ
--- | --- | --- | ---
b0 | IRQ 0 (timer) | b0 | IRQ 8 (realtime clock)
b1 | IRQ 1 (keyboard) | b1 | IRQ 9
b2 | IRQ 2 (cascade; reserved for 2nd 8259) | b2 | IRQ 10
b3 | IRQ 3 (COM2,4) | b3 | IRQ 11
b4 | IRQ 4 (COM1,3) | b4 | IRQ 12 (PS/2 mouse)
b5 | IRQ 5 (LPT) | b5 | IRQ 13 ('386 coprocessor)
b6 | IRQ 6 (floppy) | b6 | IRQ 14 (primary IDE drives)
b7 | IRQ 7 | b7 | IRQ 15 (secondary IDE drives)

The cascade bit must be zero to enable IRQs 8-15.

Examples:

            /* Enable IRQ0 (timer) and IRQ1 (keyboard) at the
            8259 PIC chips, disable others: */
            outportb(0x21, ~0x03);
            outportb(0xA1, ~0x00);

            /* Enable IRQ6 (floppy) and IRQs 14-15 (IDE) at the
            8259 chips, leave others as they are: */
    	outportb(0x21, inportb(0x21) & ~0x44);
    	outportb(0xA1, inportb(0xA1) & ~0xC0);

Handlers for IRQs must issue End Of Interrupt (EOI) to one or both PIC chips. For IRQ 0-7:

            outportb(0x20, 0x20);

For IRQ 8-15, both 8259 chips must get EOI:

            outportb(0xA0, 0x20);
            outportb(0x20, 0x20);

## Distinguishing between CPU exceptions, IRQs, and traps

Distinguishing between CPU exceptions, IRQs, and traps is done by design. Example:

- Reserve interrupts 0-31 for CPU-generated exceptions.
- Reserve interrupts 32-47 for hardware interrupts (program the 8259 chips appropriately)
- Reserve interrupt 48 for the syscall interrupt. This is the only interrupt with a ring 3 gate descriptor.
- There are no IDT entries for interrupts 49 and above.

Software interrupt (INT instruction) | Kernel privilege (ring 0) | User privilege (ring 3)
--- | --- | ---
0-31 | Avoid (reserved for CPU-generated exceptions) | Causes General Protection Fault instead
32-47 | Avoid (reserved for IRQs) | Causes General Protection Fault instead
48 | Avoid | OK (syscall software interrupt)
49-255 | Causes Double Fault instead | Causes Double Fault instead

## Code snippets

Re-program the PICs so IRQs 0-15 go to INTs 32-47.

There is, of course, no standard for interrupt handlers written in asm. Unfortunately, there is no real standard for defining interrupt handlers in C, either. Here are some code snippets for various enviroments:

OS | C | NASM
--- | --- | ---
16-bit DOS (real mode) | Turbo C interrupt demo using DOS. The combination of real mode and Turbo C's `interrupt` keyword and `getvect()` and `setvect()` functions makes this code quite simple. | xxx - to do
32-bit DOS (protected mode) | DJGPP interrupt demo using DOS (DPMI) | xxx - to do
16-bit x86, no OS | Borland C interrupt demo that does NOT use DOS. The `getvect()` and `setvect()` functions use DOS interrupts INT 21h AH=35h and INT 21h AH=25h. This demo uses stand-alone versions of these functions. | Similar to code at left, but `getvect()` and `setvect()` are implemented in a separate NASM file, so it works with the free Turbo C compilers. Thanks to Martin Coleman for this code!
32-bit x86, no OS | DJGPP interrupt demo that does NOT use DOS. Though DJGPP has functions for installing interrupt handlers under DPMI (DOS Protected Mode Interface), GCC in general has no built-in support for interrupts. You must write all or part of the interrupt handling code in asm. | xxx - to do
Windows | Win95: One Win95 program can spawn another in debug mode, and intercept the debug events generated when the second program uses `INT 03h`. All other software interrupts cause a blue-screen. Apart from this method, interrupt handlers are allowed only in kernel components (VxDs and drivers). Windows CE: Since this OS was designed for use in embedded systems, I presume you can install arbitrary interrupt handlers, but I don't know if this is true, or what the details are. Windows NT: Works as Win95 does, but software interrupts other than `INT 03h` are allowed. |
Linux | xxx - outside of a kernel module, is it possible to install hardware and software interrupt handlers under Linux? |

## Links

Hooking Interrupt and Exception Handlers in Linux, by mammon_: http://www.eccentrix.com/members/mammon/Text/linux_hooker.txt

## TO DO

    - make the C interrupt demos work with Watcom C
    - re-entrancy:
    	http://www.ganssle.com/articles/areentra.htm
    - atomicity and race conditions
    - the handler must be kept SHORT AND SWEET. Deferred interrupt processing:
            Linux bottom halves, Windows DPC

## REPORT BUGS OR ERRORS IN THIS DOCUMENT