CS444 Class 6

Last time: Interrupts, specifically receiver interrupts.

Note on IF and interrupt handlers

Since the interrupt cycle makes IF=0, the interrupt handler starts off execution with IF=0, and since there is no “sti()” or “set_eflags(...)” in either part of the interrupt handler code, the 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 some real OS interrupt handlers. Because IF=0 already, there is no need to use cli() inside an interrupt handler—it would be a dumb thing to do, though harmless.

mp5 of CS341 (solution still available)

I was wrong to say that mp5 of cs341 does no transmit interrupts. The version of last term does use transmit interrupts on COM1, while leaving COM2 as the usual Tutor console device, i.e., no interrupts enabled on COM2.  It runs like this

Turn on transmit COM1 ints

In COM1 (transmit) interrupt handler:

·         Output prompt string (null terminated) via transmit int handler

·         Call supplied callback at end of string, when null seen,

In output callback:

·         Shut down COM1 transmit and receiver ints

·         Turn on COM1 receiver ints

In COM1 (receiver) interrupt handler:

·         Accept COM1 user input via receiver int handler, also save chars in buffer

·         Call supplied callback at end, when CR seen

In input callback:

·         Shut down COM1 transmit and receiver ints

·         Turn on COM1 transmitter ints

And so on.  COM2 is still taking input as console, so you can shut this cycle down with “spi off”.

You can use bits of this code in hw1. Note that we shouldn’t use a callback from an interrupt handler back to top-level code in an OS, because the interrupt handler is kernel code and the top-level code is user code.  Also an OS needs to be able to handle input all the time, so it leaves receiver interrupts on once enabled.  The transmit interrupts do get turned on and off as we discussed last time.

This time: the trickier case of transmitter interrupts.

Transmitter Interrupts

Here the software has the char to transmit, the hardware may or may not be ready to transmit it (may be busy with earlier work).

event” here: transmitter becomes ready to transmit.  It sends in an interrupt with this good news, so the software can feed it with the next char to output.

Handling a sequence of chars:

Int handler: output next char

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 hw1 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 intsWhen data shows up to be output, via calls to ttywrite (or echoes of user input.)

 

Transmitter Interrupts in Detail

 

Algorithm for output of a string: by polling or by interrupts

 

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”

We have discussed how the interrupt handler needed to tell whether a particular interrupt came from a received char or transmitter-ready, and how the interrupt handler needs to shut down transmit interrupts when it runs out of data to output. That means we need to start up the interrupts when data shows up again to be output, and we can initialize the system with transmit interrupts off.

 

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):

  1. put the first part of the string into the output buffer (i.e. enqueue the chars until the queue is full, but no longer)
  2. turn transmitter interrupts on in the UART, and make sure the PIC is passing the IRQ through. Make IF=1 in the CPU
  3. at “program level” (in ttywrite), loop over the rest of the chars, trying to enqueue them, until they are all in the queue.  Meanwhile, they get output one-by-one by the int handler.
  4. return (at program level) from ttywrite when all chars are enqueued (some will still be in the queue, not yet output)
  5. the last chars (one queue-full) are output after ttywrite returns to its caller, one by one by the int. handler.  This is called “write behind”.
  6. When all the chars have been output, shut down transmitter ints.

 

In 3., we see a "busy loop", in fact a double loop, outer loop over chars to output, inner loop over attempts to enqueue a certain char.  It's unfortunate to have to have a busy loop, but that's the only thing we can do here.  In the future, we'll have a real OS and can block one process and let another use the CPU.  A real OS should never have a busy loop.

 

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.

Android note (optional material). Android runs mostly on ARM (32-bit), but also on x86 (32-bit). Recall that “32-bit” means 32-bit addresses, thus 4GB address spaces.  What about the ARM interrupt system?  It’s very similar to the x86—it has a separate interrupt controller, only two interrupt input pins on the CPU itself (vs. one for x86), for ordinary and “fast” interrupts, of higher priority.  The CPU has two bits in its processor status (like EFLAGS) register, I and F, for the two kinds of interrupts, instead of one IF in EFLAGS. The instruction set includes the SWI instruction (SWI = software interrupt), the system call instruction, that lifts execution from user mode to supervisor mode (actually there are several privileged modes for the CPU), while saving the old CPU state for later resumption.  Thus we can think of an x86 system as a somewhat simplified ARM system, in terms of interrupts.

 

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()

printf(...);  /* note that SAPC-library printf uses programmed i/o */

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.

 

Now add to example above:
In testio.c, suppose we have:

 
     write (TTY1, “123456789”, 9):
    kprintf("after write");
    for (i=0; i<100000000; i++)  /* busy loop, runs with IF = 1 */
        ;

We expect to see on the screen:  

        123after write456789

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 120-first two paragraph of p. 121. As discussed there, this technique of cli/sti only works for uniprocessor systems.  But it can be upgraded to more complex mechanisms with a little additional work.

 

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.

And interrupts can occur between any two machine instructions.

 

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:

  1. Do half an enqueue in ttywrite (changing some but not all of: head cursor, tail cursor, data in array)
  2. Interrupt: does a dequeue on the same queue, also changing the cursors and data, ending up with wrong data in queue, or worse--dequeue and enqueue should execute separately in time
  3. Continue with interrupted enqueue.

 

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.

 

Alternative method of ack’ing the transmitter: Using the PC UART’s IIR (Interrupt ID register) (not covered in class)

(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