Note on hw1: Problem 1 is due Friday midnight (at very end of Friday).
You
are ready to do problem 2 and 3a, 3b. Wait until next week for 3c, the
transmitter interrupt part.
handouts: Slides on Interrupts, typewr.c: see links from the class web page
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 Interrupt Handling” 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.