Protected Mode (PIC - Programmable Interrupt Controller, IRQ).....
by
bkenwright@xbdev.net
Interrupts are very important! I mean you can keep checking your
devices and clocks to see if somethings happened or if a certain amount of time
has passed...but interrupts are our solution. If a device does
something...or we need something to happen every so often, well we can have an
interrupt trigger our cpu, which will then cause some code to be done.
There are different types of interrupts....what we want to talk about here is
the IRQ and PIC and how external devices trigger interrupts in our computer.
The PIC has the ability to be re-programmed to use a
different set of interrupt values than the default ones. The default
(BIOS-defined) PIC values are;
PIC IRQ INT
0 0 8
0 1 9
0 2 A
0 3 B
0 4 C
0 5 D
0 6 E
0 7 F
1 8 70
1 9 71
1 A 72
1 B 73
1 C 74
1 D 75
1 E 76
1 F 77
The PIC is programmable, and can be remapped quite
easily, but each PIC vector must be divisible by 8, as the 8259A discards
the lower 3 bits.
In x86 protected mode, it is good to remap the PICs
beyond 0x20 as Intel have designated the first 32 interrupts as "reserved" for
cpu exceptions. eg:
remap_pics(0x20, 0x28);
will remap the pics using 16 linear interrupts from 32 to
47 (0x20-0x2F).
Remember, we in protected mode we have our IDT - the Interrupt Descriptor
Table, where we have a piece of code is called for each interrupt.
Sometimes called an interrupt vector table, the reason its called a vector
table, is because we have a list of addresses, one after the other, each address
is the location of the function that is called when the interrupt is triggered.
The following table shows interrupt assignments in the Pentium:
Vector number Description
0 Divide Error (Division by zero)
1 Debug Interrupt (Single step)
2 NMI Interrupt
3 Breakpoint
4 Interrupt on overflow
5 BOUND range exceeded
6 Invalid Opcode
7 Device not available (1)
8 Double fault
9 Not used in DX models and Pentium (2)
10 Invalid TSS
11 Segment not present
12 Stack exception
13 General protection fault
14 Page fault
15 Reserved
16 Floating point exception (3)
17 Alignment check (4)
18 – 31 Reserved on 3/486, See (5) for Pentium
32 – 255 Maskable, user defined interrupts
Don't go mixing your hex and decimal...as 32 in decimal is 0x20 in hex.
So 0x20-0x27 (8 interrupts) and 0x28-0x2F (8 interrupts), a total of 16 (0xF)
interrupts.
These exceptions are
mapped to call interrupts 0 to 16, and the interrupts raised by the PIC range
from 8-15 and 70-77. This is a problem, as you probably noticed, the exception
interrupts, and the interrupts from the master PIC (IRQ 0-7) overlap. This is a
fuck up made a long time ago by IBM, and we need to sort it out. Changing the
interrupt vectors executed by the CPU is out of the question, so it’s a strike
of luck (or good engineering) that the PIC allows us to reprogram it! The first
32 interrupts on the Intel x86 architecture are reserved, so we need to map the
IRQ interrupt vectors beyond that point. Each interrupt vector must be devisable
by 8, so we can remap all the IRQ’s to interrupt vectors 32-48 (0x20-0x2F)
(that’s 16 interrupt vectors (or pointers to pointers to ISR’s)) – so lets do
that.
You can either do this
in C or in ASM. I did it in ASM simply because it was quicker, but its up to
you.
Basically we have four
ports to remember: PIC1 has a code port at 0x20 and a data port at 0x21, where
as PIC2 has its code port at 0xA0 and its data at 0xA1. The trick is to send
each one of these an initialisation command to make them know we’re going to
change something. This command is 0x11; once this is sent to each code port, we
can send data to their data ports. Send the beginning of each PICs interrupt
vectors to their respective data ports, so you send 0x20 to 0x21, and 0x28 to
0xA1.
After that we just need
to ensure that they work like they did before – so we send the master command
0x04 to PIC1 and the slave command 0x02 to PIC2, which ensure that’s the two
PICs still have the same interrelationship as before (PIC2 is cascaded on IRQ2).
We are finished coding them as soon as we send them their operational mode code,
which is standard 8086 mode (check the data sheet for info on that mode) 0x01,
which is sent to each data port. We finish off by masking the interrupts off,
because we haven’t written any ISRs yet, so they wouldn’t have anything to call.
The complete routine looks like this in ASM:
8259 PIC Setup |
/* Init master interrupt
controller */
outb(0x11, 0x20);
/* Start init sequence */
outb(0x00, 0x21);
/* Vector base */
outb(0x04, 0x21);
/* edge tiggered, Cascade (slave) on IRQ2 */
outb(0x01, 0x21);
/* Select 8086 mode */
outb(0xff, 0x21);
/* Mask all */
/* Init slave interrupt controller */
outb(0x11, 0xa0);
/* Start init sequence */
outb(0x08, 0xa1);
/* Vector base */
outb(0x02, 0xa1);
/* edge triggered, Cascade (slave) on IRQ2 */
outb(0x01, 0xa1);
/* Select 8086 mode */
outb(0xff, 0xa1);
/* Mask all */ |
Lets do this in asm as well...of course, we do
PIC1&2 together...so we init both PIC1 and PIC2, then we set there start
addresses, then set which IRQ's are on.
PIC Settups - Interrupt Vectors 0x20 for IRQ 0 to 7 and
0x28 for IRQ 8 to 15 |
remap_pic:
mov al, 0x11 ;; INIT command
\
out 0x20, al ;; Send the INIT command to PIC1
|
;;
|- Start
out 0xA0, al ;; Send the INIT command to PIC2
|
;;
/
mov al, 0x20 ;; The start of the PIC1 interrupts
\
out 0x21, al ;; Send the port to PIC1 DATA
|
;;
|- Set PIC1 and PIC2 Start Vectors
mov al, 0x28 ;; The start of the PIC2 interrupts
|
out 0xA1, al ;; Send the port to PIC2 DATA
/
mov al, 0x04 ;; Set the code for MASTER
\
out 0x21, al ;; Set PIC1 as master
|- Set Master and Slave
;;
|
mov al, 0x02 ;; Set the code for SLAVE
/
out 0xA1, al
mov al, 0x01 ;; 8086 mode code for both 8259 PIC chips
out 0x21, al
out 0xA1, al
mov al, 0xFB ;; This masks off all IRQs except the
cascaded
out 0x21, al
mov al, 0xFF ;; Masks off all IRQs on PIC2
out 0xA1, al
|
Small snippet code, just to show how you can
switch off all the IRQ's...basically what we do at the end of the other
code...but we are switching them all off, even the cascaded interrupt :)
Which IRQ's are ON or OFF! |
; Disable all IRQs.
;-------------------------------------------------------------------------
disable_irqs:
mov al, 0xFF
mov dx, 0x21
out dx, al ; outb(0x21, 0xFF)
mov al, 0xFF
mov dx, 0xA1
out dx, al ; outb(0x21, 0xFF)
|
The 8259 programmable interrupt controller (PIC) chips
These chips make hardware interupts (IRQs) behave like software interrupts (INT
instruction). (Remember, though, that hardware interrupts are asynchronous,
meaning they can happen at any time.)
The BIOS programs the 8259 chips so that IRQs 0-7 are mapped to interrupts
8-15. Because interupts 8-15 are reserved for use by the CPU, this mapping makes
it difficult to determine if an interrupt came from the CPU or external
hardware. Unless you need DOS or BIOS compatability, the PICs should be
re-programmed so IRQs do not use any of the CPU-reserved interrupts (see code
snippet below).
The 8259 chips can mask (enable and disable) individual IRQs. Zeroing a bit
at port 21h or A1h enables the corresponding IRQ:
Bits at I/O port 21h
(1st 8259) |
IRQ |
Bits at I/O port A1h
(2nd 8259) |
IRQ |
b0 |
IRQ 0 (timer) |
b0 |
IRQ 8 (realtime clock) |
b1 |
IRQ 1 (keyboard) |
b1 |
IRQ 9 |
b2 |
IRQ 2 (cascade; reserved for 2nd 8259) |
b2 |
IRQ 10 |
b3 |
IRQ 3 (COM2,4) |
b3 |
IRQ 11 |
b4 |
IRQ 4 (COM1,3) |
b4 |
IRQ 12 (PS/2 mouse) |
b5 |
IRQ 5 (LPT) |
b5 |
IRQ 13 ('386 coprocessor) |
b6 |
IRQ 6 (floppy) |
b6 |
IRQ 14 (primary IDE drives) |
b7 |
IRQ 7 |
b7 |
IRQ 15 (secondary IDE drives) |
The cascade bit must be zero to enable IRQs 8-15.
Examples:
/* Enable IRQ0 (timer) and IRQ1 (keyboard) at the
8259 PIC chips, disable others: */
outportb(0x21, ~0x03);
outportb(0xA1, ~0x00);
/* Enable IRQ6 (floppy) and IRQs 14-15 (IDE) at the
8259 chips, leave others as they are: */
outportb(0x21, inportb(0x21) & ~0x44);
outportb(0xA1, inportb(0xA1) & ~0xC0);
Handlers for IRQs must issue End Of Interrupt (EOI) to one or both PIC
chips. For IRQ 0-7:
outportb(0x20, 0x20);
For IRQ 8-15, both 8259 chips must get EOI:
outportb(0xA0, 0x20);
outportb(0x20, 0x20);
PIC Basic Routine (As above but more flexible for choosing
start vector of PIC1 and PIC2) |
mov cl, 0x20 ; PIC 1.
mov ch, 0x28 ; PIC 2.
;-------------------------------------------------------------------------
; Remap PICs to: CL = pic1 CH = pic2
;-------------------------------------------------------------------------
remap_pics:
; IWC1
;------
mov al, 0x11
mov dx, 0x20
out dx, al ; outb(0x20, 0x11)
mov al, 0x11
mov dx, 0xA0
out dx, al ; outb(0xA0, 0x11)
; IWC2
;------
mov al, cl
mov dx, 0x21
out dx, al ; outb(0x21, pic1)
mov al, ch
mov dx, 0xA1
out dx, al ; outb(0xA1, pic2)
; IWC3
;------
mov al, 0x04
mov dx, 0x21
out dx, al ; outb(0x21, 4)
mov al, 0x02
mov dx, 0xA1
out dx, al ; outb(0xA1, 2)
; IWC4
;------
mov al, 0x01
mov dx, 0x21
out dx, al ; outb(0x21, 0x01)
mov al, 0x01
mov dx, 0xA1
out dx, al ; outb(0xA1, 0x01)
;-------------------------------------------------------------------------
|
What we need to see now is how we would go about putting a whole demo program
together so we can see this in action...nothing much than I've showed
above...but still its always best to see it actually in action without it
actually causing our pc to crash or something.
Download File (click) -
asm_1.asm |
; Run in dos (not under windows) and it will take us to 32 bit protected mode
....
etc etc
|
etc etc..
|