CS444 Class 5

 

handout: Slides on Interrupts,  typewr.c: s

typewr.c: the one-page example program for COM2 receiver interrupts

 

Current reading: Chap. 5, 5.1, 5.2, 5.3

 

Notes on Tanenbaum’s coverage on I/O.

 

p. 333: We use IN and OUT i/o instructions of the x86 CPU.  Although the x86 is capable of memory-mapped i/o, it is not used with typical i/o devices.  It is used for video memory, for example.

p. 335: Don’t worry about bus architecture—it’s hidden from us anyway.  Just use the one-bus model to think about how it works.

p. 336: DMA—just know what this means.  Skip details in 5.1.4.

p. 340: In line 8, the bold “interrupt vector” should be “interrupt vector number”, the number such as 0x23 for COM2 that tells the CPU which entry of the IDT (int. descriptor table) to use to load the interrupt vector itself, i.e., the address of the int. service procedure (ISP).

p. 341: Don’t worry about pipelining and super-scalar for this course.  The bottom line is on pp. 342-343, where he says that the Pentium series CPUs do all the bookkeeping necessary to make sure that an interrupt happens in effect between two specific instructions.

 

Interrupts

 

Ref:  Tanenbaum, Sec. 1.3.5, 5.1.5.

 

The basic idea of an interrupt is a way to divert execution from its usual flow through the code, like interrupts in real life like phone calls. After the phone call, we have to be able to resume our ordinary work.  Similarly, the interrupt causes special code (the interrupt handler) to execute for a while, and then the ordinary code gets resumed, just the same way it would have executed if the interrupt had not happened.

 

Look at Tan. Fig. 5-5, pg. 339.  Add a device for COM2. Connect the i/o devices to the bus. See how the interrupt controller (or PIC for programmable int. controller) sits between the devices and the CPU, much like a secretary sits between clients and an executive.  Our software, running on the CPU, can tell the PIC what signals to allow through to the CPU, and can control whether the CPU pays any attention to interrupts by changing the IF bit in EFLAGS, the main control register of the CPU.

 

 

Again we looked at the picture of the system with the bus connecting devices, PIC, and CPU, and also memory.  The interrupting devices are connected by IRQ lines to the PIC, and the PIC has one line to, and one line from, the CPU.  

 

The separate lives of the CPU, PIC, and i/o devices

 

It is very important to understand that the registers in the i/o devices and PIC can hold data over time, completely independently from the CPU.  The devices, PIC, and CPU interact with each other in very specific ways, something like people coordinating work via phone calls.

 

The x86 instruction set: tells most of what the CPU can do

 

MOV, CMP, ADD, CALL, RET,   for ordinary programming

SYSCALL, SYSENTER, INT: the system call instruction, various versions

IN, OUT    for access to i/o devices (in kernel code usually, but not always)

IRET    for return from system call or interrupt handler (in kernel code)

CLI, STI:  set and clear IF flag (in EFLAGS) to turn interrupt system on and off (in kernel code)

  Also POPFL, used to set EFLAGS from a given value (at top of stack)

 

The INT n instruction (also SYSCALL, SYSENTER, newer variants)

--saves CPU state  (CS, EIP, and EFLAGS) on the stack (the kernel mode/user mode information is in CS)

--enters kernel mode

--accesses IDT[n] to get syscall handler address

--loads handler address in EIP, causing syscall handler execution (in kernel) next.

 

This all happens in one atomic instruction cycle.  IDT is an array in kernel memory—more on this later.  This is the system call instruction we saw at work outputting “hi!!!\n”, all in one instruction as seen from the user code (though involving of course hundreds of thousands of instruction cycles in the CPU, executing kernel code.)

 

IRET

--restores CS, EIP, and EFLAGS from the stack, restoring user mode if it was previously in user mode, and returning execution to the interrupted code.

 

The interrupt cycle in the CPU: another CPU feature (beyond its instructions)

When the CPU detects an incoming interrupt signal from the PIC, it springs into its special handling of it. The CPU composes an instruction “int $nn” using the nn interrupt vector number, and jams it into its own instruction pipeline.  That helps us understand how it can keep track of behavior logically before and after the interrupt. In addition it sets IF=0. See Slide 10 for details.

 

The interrupt cycle (but not the system call exception cycle that is so similar) sets IF=0 in EFLAGS. This means that interrupt handlers start off running with interrupts off, to ensure they have complete control of the system.  Our simple interrupt handlers leave IF=0 for their whole execution, and depend on the final IRET instruction to restore the old EFLAGS, which must have IF=1.  The IRET also restores the old CS and EIP, to resume the interrupted execution.  (The fact that the interrupt location can be boiled down to a single old-CS:EIP value, so it happened between two certain instructions, is a major achievement of x86 pipelined CPU design.)

 

Look at the slides.   Some notes—

 

Slide 6  “CPU Actions for Interrupts”  CPU checks for interrupts between instructions.  See Tan. pp 341-342 for discussion about how difficult this simple idea is for a pipelined CPU, but that the Pentium family CPUs do all this work, making the job of writing OS’s much easier for us than it might be.

 

pic_end_int(), pic_enable_irq(int irqn), pic_disable_irq(int irqn) are functions implemented in the SAPC library, in directory $pclibsrc.  They are special to UMB’s SAPC environment, although historically based on Linux sources.  The code could be ported to a Windows environment, but could not run there because user code is not allowed to interact directly with hardware (in Windows or UNIX.)  It needs a kernel-like environment to run in, running in kernel mode on the CPU.  The SAPC/Tutor setup provides this kernel-like environment.  (You could run Tutor off a floppy on your home PC, but it’s not easy to set up mtip for downloading to go with it.)

 

Slides 12-15: The UART is two devices in one package: receiver + transmitter.  They share one IRQ line, however, so have only one interrupt handler (that is, one for COM1, another one for COM2.)   These slides only cover receiver interrupts.

 

Note that COM1 and COM2 are already in active use (or ready for it) when we start using them in a program on the SAPC.  COM2 is the console line, so the Tutor prompt is output on COM2 and our “go 100100” is input on that line.  COM1 is used for the remote gdb protocol.  Both these activities use only programmed i/o, i.e., no interrupts.

 

typewr.c: code with extra markup handed out


Steps 1-5 are the initialization steps, setting up the system to do interrupts

Step 6 is essential.  Without doing this loop, the system would just exit to Tutor too soon, before we could show anything.  UART interrupts won’t happen once Tutor is in control because it (being a debugger) is completely in charge and logically operating outside the program environment.  It does report on the program environment via its “dd” command, for example.

 

The shutdown part, steps 10-13, is important to keep the SAPC usable (without reboot.)  If you leave interrupts enabled in the device and in the PIC, then the next time someone downloads a program, it will wipe out your interrupt handler.  As soon as the user says “go 100100”, handing the execution environment over to the user program, an interrupt will crash the system.  The annoyed user will have to reboot and download again.

Steps 7, 8 and 9 are the crux of the interrupt handler.  9 marks the curly brace, which only has a ret instruction, for function return.  But it’s returning to the assembly code that contains the very important iret instruction.

 

There is a little more code (aside from the startup code) that gets executed, from $pclibsrc/irq3.s: see the back side of the typewr.c handout or the actual code in $pclibsrc/irq3.s.  The bold lines are the essential ones you should understand.  The others date from Linux and are only needed to guard against bad user-level code.

Note that irq3inthand is the starting address for the whole interrupt handler, the address that set_intr_gate installs in IDT[0x23] as the interrupt vector.  Since this code calls _irq3inthandc, we must name our C interrupt handler “irq3inthandc”.  (The leading underscore is a technical detail of the C cross-compiler we’re using.  It adds an underscore to all names of functions and variables.)

 

Next: transmitter interrupts—a little trickier than receiver interrupts.