xbdev - software development
Wednesday February 21, 2024
home | contact | Support


Assembly Language

What every pc speaks..1010...


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

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


        /* 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

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









Advert (Support Website)

Copyright (c) 2002-2024 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.