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.