Thurs., Sept. 22

HW1 now due Sunday midnight.  Questions?

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.

Transmitter Interrupts

 

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

 

Transmitter Interrupts

 

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 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.

 

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 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.

 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.

Interrupt Handler Points

 

 

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.  

 Dataflow pictures, very rough here:  those stick things are supposed to be arrows showing data flow.

 

 

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.

 

 

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