HW1 now due Sunday
Handout: typewr.c,
the one-page example program
for COM2 receiver interrupts
Look
at handout $pcex/typewr.c: important points numbered
here.
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.)
Since
the interrupt cycle makes IF=0, and there is no
“sti()” or “set_eflags(...)” in
either part, this whole interrupt handler (as part and C part) executes
with
interrupts off. When
the final IRET
executes and restores the old EFLAGS and EIP, IF returns to 1. This is the normal setup
for a simple
interrupt handler, i.e., anything we need for this course, plus most
real OS
interrupt handlers.
Each
transmitter interrupt
signals readiness for another byte of output.
The transmitter is ack’d by giving it another
byte to output. Great
system until we run out of data to give
to it. What to do
then? Simplest
solution is to shut down transmitter
interrupts. (There
is another solution
for this UART.)
Quick intro on how to handle TX
interrupts:
Note
that the transmitter and
receiver share a single IRQ, for example IRQ3 for COM2.
Since the provided code is doing receiver
interrupts, it already has an irq3inthandc function implemented. All you have to do is add
to it to handle
another case.
How
can we tell whether it’s
a transmitter or receiver interrupt?
Answer:
the UART’s LSR tells
us via the THRE and DR bits.
If
it’s a TX int, dequeue a
char and output it. If
the queue is
empty, shut down TX ints. Be
careful not
to shut down RX ints! They
should be on
continuously. It’s
only TX ints that go
on and off depending on output availability.
So
when do we turn on TX
ints? When data
shows up to be output,
via calls to ttywrite (or echoes of user input.)
Output by Polling, AKA Programmed
I/O
Loop
over the chars of the
string:
Loop
over checking the transmit-ready bit,
THRE, of the LSR register, until it goes on.
Write
the char to the UART’s transmit
register.
Output by Interrupts
“Interrupt-driven output”
initialization:
set up the
interrupt gate in IDT, but leave transmitter interrupts off in the
UART. (Also, make sure the IRQ can pass through the PIC, and that the
CPU has interrupts enabled at the end of initialization)
transmit
interrupt handler: call
dequeue to get the
next char or find out there are none, and outb a real char to the
UART’s
transmit register, or shut down interrupts if there are none left
To
output a string (say in
ttywrite):
Note
that step 1. doesn’t
affect the UART. It
is just groundwork
so that the first run of the interrupt handler will find a char to
output.
Note:
enabling interrupts in
the UART transmitter (step
2.) causes
the UART to immediately send in an interrupt if it is ready to transmit.
Note:
the enqueue in ttywrite
is a “critical region” because both ttywrite and
the interrupt handler are
manipulating the queue data structure.
That means we need to turn off interrupts during the
enqueue
operation. Note
that interrupts need to
be on for part of each pass of the loop, to allow the system to
progress. More on
this below.
Note
that the queue holds
only 6 chars, so we can easily test fullness conditions.
Specific
example:
write (TTY1, “123456789”, 9):
1.
“123456” put in queue during setup.
2.
“123” output, one by one, by int. handler, and
“789” replace them in queue by
loop of enqueues in ttywrite.
3.
Everything is in queue, so ttywrite returns
4. ttywrite has returned, but 6 chars “456789” are
still in queue: they are output
one by one, by int. handler.
kprintf: kprintf(...) is used just like
printf for printing
debug output in programs like testio.c that enable interrupts. It is equivalent to
int
saved_eflags;
saved_eflags
= get_eflags(); /* get caller's IF value */
cli()
set_eflags(saved_eflags);
It
is important to turn off
interrupts before using printf when your program uses transmit
interrupts. Otherwise
you would have part of the code
trying to do programmed i/o and another part doing interrupt-driven i/o
all at
the same time. The
two methods would be
fighting over the transmitter! With
kprintf, as soon as execution reaches it, it locks out the
interrupt-driven
behavior by setting IF=0 and takes all the time it needs to output the
string
by programmed i/o. This
is just what you
want for debugging: intact
status messages
that tell you what’s true right at that moment.
because as soon as ttywrite returns, kprintf is executed and does its polling output without interrupts happening. So the 123 is output during ttywrite's execution, the "after write" is output during kprintf's execution, and the 456789 is output during the busy loop's execution.
Idea of a Critical Region--enqueue in ttywrite vs. interrupt handler dequeue
(also dequeue in ttyread vs. interrupt handler enqueue)
Ref:
Tan, pg 102-first paragraph of 104
When
the program level is executing in ttywrite, we have the situation of
two
concurrent activities:
--Program
level looping on enqueues
--Interrupt
level intermittently executing doing a dequeue each time
and
we’re talking about the same queue here, the output queue.
We
have to have IF=1 for at least part of each pass of the loop in
ttywrite, to
let the interrupts happen.
Luckily,
it’s OK to make IF=0 for a while, then turn it back
on—the PIC saves the
knowledge of an interrupt and signals the CPU until it pays attention.
Suppose
IF=1 solidly in the loop of enqueues. Then
an interrupt could
happen half way through the enqueue in ttywrite.
The interrupt handler would run and do a
dequeue on a partially-finished edit to the queue.
This can break the queue data structure.
Scenario:
How
can we arrange to avoid
this?
We’re
in charge, so we can turn off interrupts
during the execution of
enqueue, and protect it from this.
We say the call to enqueue in ttywrite is a “critical section” or a “critical region”.
But watch out, if
you turn off interrupts too long, the output can't drain at interrupt
level. Interrupts need to go on and off on each pass of the inner
loop.
Similarly,
in ttyread, we can
turn off interrupts during its call to dequeue, another critical region.
Here
is how to do it:
cli()
/* disable interrupts in the CPU by
setting IF=0 in EFLAGS */
enqueue(...) /*
critical code */
sti()
/* or set_eflags(saved_eflags)
like you see in ttyread: reenable interrupts
*/
This
is a form of mutual exclusion, or mutex. It isn’t the
only way to do it—we could manipulate
the interrupt mask register in the PIC, for example.
But it’s very simple to do this way, so
we’ll
use setting IF=0 as our form of kernel mutex.
It is important to reenable interrupts because they need to be on in every pass of the loop in step 3. of the string-output algorithm, to allow the interrupts to send chars to the UART.
Debugging Points
Use
kprintf generously to
report what’s happening.
Common
Problem: system “goes
away”, after printing some of your kprintf messages.
~r
gets you going again, but
memory info is lost.
Use
kprintf generously to
report what’s happening, blow by blow.
kprintf is your spy—it runs in between any two
program lines, and runs immediately,
no delay like we’re putting into write().
Another
idea is to use the
debugger, remote gdb. It works within interrupt handlers as well as in ttyread and ttywrite.
read
---> ttyread : upper half code, dequeues from input Q
^
|
input
Q
^
|
input interrupt handler: enqueues into
input Q
write --> ttywrite:
upper half code,
enqueues to output Q
|
v
output
Q
|
v
output interrupt handler: dequeues
from output Q
Echoes
How
about echoes? They
are generated when a char is processed
in the input interrupt handler. One
copy
of the char is enqueued in the input Q and another in an output echo Q,
separate from the ordinary output Q.
If
we put the above pictures next to each other we can draw in the echo Q
next to
the output Q and an arrow from the input int handler to it, and another
arrow
from it to the output int handler.
When
the output int handler
runs, it should first look in the echo Q, and output a char from there,
or if
there are no chars there, output one from the ordinary output Q. This way users see echoes
as soon as
possible.
What
if interrupts are off when
we generate an echo—should we turn on transmitter ints then?
Answer:
Yes, there needs to
be code in the receiver interrupt handler for this.
Note:
the UART has only one
IRQ for transmitter and receiver interrupts.
That means there is one interrupt handler that has to
handle both input
and output interrupts. You
can check for
receiver data-ready, and if that’s on, do the input interrupt
handling and
return. Else, do
the output interrupt
handling.
Not covered in class but may be useful for hw1:
Note:
don’t confuse & and
&&. When
we’re working with bits
we need &, the bit-wise AND, and also |, the bit-wise OR. These operators works the
same in C and Java,
but are not as commonly used in Java.
Reread K&R, pp. 48-49 and 52-53 if you need review
on this.
The
simple-UART treatment
expected in hw1 uses the LSR to discriminate between receiver and
transmitter
interrupts. After
all, the UART is
interrupting to tell us it’s data-ready (DR on) or
transmit-ready (THRE on),
where DR and THRE are bits in the LSR.
if
(inpt(COM2_BASE + UART_LSR) &
UART_LSR_DR) … receiver int case, COM2
Of
course, in tty.c you need
to use a variable for the base register.
Also note that this action does not acknowledge the
interrupt, so you
need to read the char in (DR on), or if THRE is on, transmit another
char or
turn off transmit interrupts.
(You
can ignore this coverage of IIR if you want)
The
UART in the PC is quite a complicated device, and we have only looked
at part
of its behavior. By
treating it as a simple
UART, we are seeing the bare-bones of device input/output, i.e., pretty
general
approaches. A more
serious programmer
would want to take advantage of its features.
One thing it is able to do is tell us what kind of
interrupt it is
currently reporting—a receiver int, a transmitter int, or two
other kinds we’re
not using. It shows
this information in
its IIR. Furthermore,
when it detects a
read of this register over the bus, it is ack’d for that
interrupt, and is
given permission to generate another IRQ if appropriate. The UART is acting as its
own little
interrupt controller, sending out one interrupt at a time for its four
interrupt sources.
So
all we have to do is read the IIR, and use the info to either
do the input interrupt handling or the output interrupt
handling. And we
can leave transmitter
interrupts on all the time, just like receiver interrupts, because we
have
successfully ack’d the device even in the case of no more
output.
Note
that the IIR uses a 2-bit field to report the interrupt type, and this
field
has mask UART_IIR_ID, and value UART_IIR_RDI in case of receiver int. The proper code to test
for RDI is therefore
“test under mask” like this, if iir has the
register value:
if
((iir & UART_IIR_ID) == UART_IIR_RDI) ...
End of coverage on IIR