Handout: typewr.c, the one-page example program for COM2 receiver interrupts
Also: timetest1.c, $pcex/timetest.c with edits for hw3
Look at the handout covering the example program $pcex/typewr.c: important points are numbered there and discussed here:
Steps 1-5 are the initialization steps, setting up the system to do interrupts. Steps 7 – 9 are in the C interrupt handler.
1. cli();
2. set_intr_getc: fills IDT[0x23]
· implementation in $pclibsrc
· IDT: interrupt descriptor table(in memory)
· &irq3inthand: addr of assembler interrupt handler
3. unmask IRQ3 in PIC
· pic_enable_irq(COM2_IRQ)
4. enable ints in the device
· outpt(COM2_BASE + UART_IER, UART_IER_RDI)
· this causes outb of certain bit pattern to IER(interrupt enable reg, see serial.h) reg of UART
5. sti();
6. busy loop, needed: if drop, system just shuts down immediately
Before step 7, the assembler interrupt handler runs (see back side of handout):
· interrupt cycle loads address of irq3inthand into EIP
· irqint3hand saves C scratch regs (C itself saves non-scratch regs at start of function execution)
· irqint3hand calls C interrupt handler irq3inthandc
7. irq3inthandc: send EOI command to PIC
· causes outb to port 0x20
8. irq3inthandc: pick up received char
· int handler execution
o proceded by CPU int cycle
o execution arrive at irq3inthand(save registers, call irqinthandc)
· Note: done flag is a global var, as usual for communication between int handler and the register code
o return to irq3inthand
o pop the register back in place(restore interrupted code’s reg scratch)
o pop the saved EIP, CS and EFLAGS off stack back to place
9. irq3inthandc: echo received char (without looping on transmit-ready bit because it should be ready)
After step 9: irq3inthandc returns to irq3inthand, registers are restored, final iret resumes the interrupted code, here the busy loop in main.
Since the interrupt cycle makes IF=0, and there is no “sti()” or “set_eflags(...)” in either part of the interrupt handler, this whole interrupt handler (as part and C part) executes with interrupts off. When the final IRET executes and restores the old EFLAGS, CS 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.
Note: Interrupts are always enabled in user code execution in a real OS, so the kernel can get control at interrupts.
- sends in IRQ, same IRQ# as the receiver
--> ends up in same int handler
- so int means: a receiver or transmitter event
--> access LSR reg to find out if: DR—receiver has a new char
THRE—transmitter is ready for another char
- Ack the transmitter by sending it a char, and/or the receiver by reading in the char
Answer: turn off transmitter interrupt.
Each time the downcount reaches 0, the timer generates an int, which comes to the PIC on IR0, making it IRQ0, highest priority.
Tick interval: Time between ticks, specified by init count.
eg. every 20m: init count = 23860
longest 55ms: init count = 0, meaning 64k
![]() |
Timer 0: very simple, always interrupting.
SAPC: has IRQ0 masked off at the PIC
Init: unmask IRQ0 in the PIC
setup an in handler: IDT[0x20]
make IF=1 for CPU
Int cycle saves the PC(EIP) of the interrupted code on the stack.
-- we can access it & save it in memory
Every tick: get a PC of main code
--> statistics on execution
“PC sampling”: Solaris/Linux
Finding the PC on the stack: tick – this gcc pushed args on stack
irq0inthandc(stackLayout sm) gives us a window on the stack data.
Promised pictures of stack:
Here’s the picture of the stack at the end of the interrupt cycle. ESP is pointing to the old EIP.
Old EIP (or PC) |
|
0 |
Old CS |
Old EFLAGS |
Now the interrupt handler irq0inthand saves registers to the stack, so the stack grows to
Old ECX |
||
Old EDX |
||
Old EAX |
||
0 |
Old DS |
|
0 |
Old ES |
|
Old EIP (or PC) |
||
0 |
Old CS |
|
Old EFLAGS |
||
And then irq0inthand calls irq0inthandc, pushing its return address on the stack:
Return PC |
||
Old ECX |
||
Old EDX |
||
Old EAX |
||
0 |
Old DS |
|
0 |
Old ES |
|
Old EIP (or PC) |
||
0 |
Old CS |
|
Old EFLAGS |
||
Now normally when i386-gcc passes arguments, it does it by pushing it on the stack, for example, for myfunction(int count), it pushes the int count on the stack and calls myfunction, so the count value ends up just under the return PC:
Stack, when execution arrives at myfunction(int count):
Return PC |
Count value |
Thus we could access the “Old ECX” value by using one argument, like this: irq0inthandc(int oldEAX), but of course we want to dig down further in the stack than this. We can pass a whole struct to a C function, and in that case the struct is pushed on the stack before the call instruction. So we can use an argued struct to view the stack, as set up in timetest1.c.