CS644 UART Receiver interrupts.

Look at $pcex/typewr.c:  important points numbered here. See class notes.

 

/* typewr.c: echo on COM2 using UART receiver interrupts                 */

/* The needed interrupt envelope routines irq3int, etc. are in           */

/*   the library, and sti, cli, pic_enable_irq, etc., are there too.     */

#include <stdio.h>

#include <serial.h>

#include <cpu.h>

#include <pic.h>

 

extern const IntHandler irq3inthand; /* the assembler envelope routine    */

void irq3inthandc(void);  /* the C core int handler, called from envelope */

 

int done;                       /* global for communication with int hand */

 

/* Note in our enviroment, interrupts are normally enabled in the CPU, so we

   use cli() as necessary even at the very start of the program */

void main()

{

  if (sys_get_console_dev() != COM2)

    printf("Warning: console not COM2, so echos won't show on console\n");

 

  /* init global explicitly, so program works properly on reruns */

  done = 0;

 

  cli();                        /* disable ints while setting them up */  <---(1)

  printf("Calling set_intr_gate (irq = 3, n = 0x23) to arm interrupt...\n");

  set_intr_gate(COM2_IRQ+IRQ_TO_INT_N_SHIFT, &irq3inthand);               <---(2)

 

  printf("Commanding PIC to interrupt CPU for irq 3 (COM2_IRQ)\n");

  pic_enable_irq(COM2_IRQ);                                               <---(3)

 

  printf("enabling receiver interrupts in UART...\n");

  outpt(COM2_BASE+UART_IER, UART_IER_RDI); /* RDI = receiver data int */  <---(4)

  /*  now hardware reg's are all set up, time to allow CPU ints-- */

  sti();                                                                  <---(5)

  printf("...done, entering almost-infinite loop\n");

 

  while (!done)                 /* loop almost forever...*/               <---(6)

    ;

  /* here when user types 'q': shutdown ints we started up */

  cli();                                                                  <---(10)

  pic_disable_irq(COM2_IRQ);    /* done with irq 3 in PIC */              <---(11)

  outpt(COM2_BASE+UART_IER, 0); /* stop UART ints */                      <---(12)

  sti();                        /* but other ints still there */          <---(13)

}

 

/* this is called from irq3inthand, the assembler interrupt envelope routine */

/* that saves the C scratch regs on the stack, restores them after this,  */

/* then does the iret instruction.                                        */

void irq3inthandc(void)

{

  char ch;

 

  pic_end_int();                /* notify PIC that its part is done */    <---(7)

  ch = inpt(COM2_BASE+UART_RX); /* read char (ACKs UART too) */           <---(8)

  if (ch == 'q')

    done = 1;                   /* set global flag to let main know */

  outpt(COM2_BASE+UART_TX,ch); /* echo char (Transmitter should be ready) */

}                                                                         <---(9)

 

 

The Assembly Language Interrupt Handler Envelope

In the following code, the bold lines are the essential ones you should understand.  The others date from 

(old version) Linux and are only needed to guard against bad user-level code.

 

Source is$pclibsrc/irq3.s

 

# like linux SAVE_MOST and RESTORE_MOST macros in irq.h

.text

.globl _irq3inthand

KERNEL_DS = 0x18

 

 

_irq3inthand: cld   # D bit gets restored by iret (this can be dropped)

        push %es    # in case user code changes data segs (this can be dropped)

        push %ds        # (this can be dropped)

        pushl %eax      # save C scratch regs      <---(1) register saves

        pushl %edx

        pushl %ecx

        movl $KERNEL_DS, %edx  # (this can be dropped)

        mov %dx, %ds    # (this can be dropped)

        mov %dx, %es    # (this can be dropped)

        call _irq3inthandc  # call C interrupt handler  <---(2) call into C

        popl %ecx                                  <---(3) register restores

        popl %edx

        popl %eax

        pop %ds         #(this can be dropped)

        pop %es         #(this can be dropped)

        iret                                       <---(4) iret instruction

 

Other functions used in this program:

 

int inpt(int port), void outpt(int port, int val) Do IN or OUT instruction using argued

    port (low 16 bits of int port), and low 8 bits of data in value or return value.

    Prototyped in $pcinc/cpu.h, implemented in $pclibsrc/portio.s

 

void pic_enable_irq(int irq), void pic_disable_irq(int irq):  Prototyped in $pcinc/pic.hin,

    implemented in $pclibsrc/pic.c.  For IRQs 0-7, these read (using inpt from port 0x21)

    the 8-bit interrupt mask register from the PIC, then set or clear the one bit for

    the irq, and output the resulting mask back to the PIC.  For the timer, bit 0

    is being manipulated.  If bit 0 is ON in the mask register, it means the timer is

    masked off in the PIC, unable to send an IRQ to the CPU.  pic_enable_irq(0) clears

    bit 0, while pic_disable_int(0) sets it.

 

void pic_end_int(): also in pic.h/pic.c.  This sends the EOI (end of interrupt) command to

    the PIC by using outpt() to port 0x20.

 

cli() and sti(): in $pclibsrc/cpureg.s.  Just does cli or sti instruction and returns.

    cli clears the IF bit in EFLAGS and thus turns off the whole interrupt system.

    sti sets the IF bit and turns on the whole interrupt system.

 

void set_intr_gate(int n, IntHandler *addr): in cpu.h, $pclibsrc/cpu.c.  This function

    first locates the IDT in memory by doing the special sidt (store-IDT) instruction

    (locate_idt code is in cpureg.s). Then it forms a pointer to the interrupt gate

    in IDT[n], and puts the two halves of addr in the right spots in the gate. 

    It sets some flags in another part of the gate to finish the job of setting up

    the IDT slot to handle interrupts.