Una petición de interrupción es una señal para indicar al procesador que algo requiere su atención inmediata.Estas peticiones pueden ser generadas tanto por dispositivos hardware(IRQ) como por programas e incluso en circunstancias especiales (errores generalmente) por el propio procesador.
Antes de comenzar es recomendable que leas estos artículos anteriores:
Sin interrupciones la CPU debería chequear constantemente los dispositivos para comprobar su actividad, estas permiten que los dispositivos puedan permanecer en silencio hasta el momento que requieren atención del procesador.
Los dispositivos hardware comunican sus interrupciones a través de unas líneas en el bus de control, cada una de estas líneas se llama IRQ. Estas líneas llegan al controlador de interrupciones PIC y este encola y prioriza las interrupciones antes de comunicárselas a la CPU.
Estas IRQs son limitadas, en total se disponen de 16(0-15) esto es debido a que disponemos de dos PICs cada uno de ellos con 8 IRQs pero hay que tener en cuenta que están conectados en cascada, el mas cercano a la CPU es el MASTER y el mas lejano el SLAVE, la conexión del MASTER(IRQ2) con el SLAVE(IRQ9) consume una IRQ en cada PIC.
Cuando se produce una interrupción hardware se siguen los siguientes pasos:
La rutina de servicio comentada anteriormente se localiza consultando la IDT(nterrupt Descriptor Table), esta no es mas que una tabla que relaciona el id de interrupción con la posición de memoria de la ISR, los códigos van de 0-255, si ocurre una interrupción y no hay entrada en la IDT para dicha interrupción el procesador entrará en panic y se reiniciará.
NOTA: En modo real 16 bits la IDT es llamada IVT(interrupt vector table)
No todas las interrupciones tienen porque ser hardware.
Las excepciones siguen un orden de prioridad:
En la siguiente tabla podemos ver la relación de las interrupciones con sus IRQs tal y como las carga la BIOS por defecto:
Master 8259:
IVT | INT # | IRQ # | Description
-----------+-------+-------+------------------------------
0x0020 | 0x08 | 0 | PIT
0x0024 | 0x09 | 1 | Keyboard
0x0028 | 0x0A | 2 | 8259A slave controller
0x002C | 0x0B | 3 | COM2 / COM4
0x0030 | 0x0C | 4 | COM1 / COM3
0x0034 | 0x0D | 5 | LPT2
0x0038 | 0x0E | 6 | Floppy controller
0x003C | 0x0F | 7 | LPT1
Slave 8259:
IVT | INT # | IRQ # | Description
-----------+-------+-------+------------------------------
0x01C0 | 0x70 | 8 | RTC
0x01C4 | 0x71 | 9 | Unassigned
0x01C8 | 0x72 | 10 | Unassigned
0x01CC | 0x73 | 11 | Unassigned
0x01D0 | 0x74 | 12 | Mouse controller
0x01D4 | 0x75 | 13 | Math coprocessor
0x01D8 | 0x76 | 14 | Hard disk controller 1
0x01DC | 0x77 | 15 | Hard disk controller 2
Cuando se entra en modo protegido la propia CPU utilizará los códigos 0x0 - 0x1F para interrupciones internas creando un solapamiento(bug de intel) por lo tanto tendremos que remapear las interrupciones originales a otros códigos.
Lista de interrupciones en modo protegido:
IVT | INT # | Description
-----------+-----------+-----------------------------------
0x0000 | 0x00 | Divide by 0
0x0004 | 0x01 | Reserved
0x0008 | 0x02 | NMI Interrupt
0x000C | 0x03 | Breakpoint (INT3)
0x0010 | 0x04 | Overflow (INTO)
0x0014 | 0x05 | Bounds range exceeded (BOUND)
0x0018 | 0x06 | Invalid opcode (UD2)
0x001C | 0x07 | Device not available (WAIT/FWAIT)
0x0020 | 0x08 | Double fault
0x0024 | 0x09 | Coprocessor segment overrun
0x0028 | 0x0A | Invalid TSS
0x002C | 0x0B | Segment not present
0x0030 | 0x0C | Stack-segment fault
0x0034 | 0x0D | General protection fault
0x0038 | 0x0E | Page fault
0x003C | 0x0F | Reserved
0x0040 | 0x10 | x87 FPU error
0x0044 | 0x11 | Alignment check
0x0048 | 0x12 | Machine check
0x004C | 0x13 | SIMD Floating-Point Exception
0x00xx | 0x14-0x1F | Reserved
0x0xxx | 0x20-0xFF | User definable(32-255)
Los código conflictivos son:
0x0020 | 0x08 | 0 | PIT
0x0024 | 0x09 | 1 | Keyboard
0x0028 | 0x0A | 2 | 8259A slave controller
0x002C | 0x0B | 3 | COM2 / COM4
0x0030 | 0x0C | 4 | COM1 / COM3
0x0034 | 0x0D | 5 | LPT2
0x0038 | 0x0E | 6 | Floppy controller
0x003C | 0x0F | 7 | LPT1
0x0020 | 0x08 | Double fault
0x0024 | 0x09 | Coprocessor segment overrun
0x0028 | 0x0A | Invalid TSS
0x002C | 0x0B | Segment not present
0x0030 | 0x0C | Stack-segment fault
0x0034 | 0x0D | General protection fault
0x0038 | 0x0E | Page fault
0x003C | 0x0F | Reserved
Podemos ver la lista completa de interrupciones en esta web:
https://dos4gw.org/Interrupts_-_List
Las interrupciones del modo protegido abarcan los ids de interrupción 0x00-0x1F(0-31), por lo tanto quedan libres 0x20-0x1f(32-255), es en este último rango donde vamos a remapear las interrupciones originales de los PICs.
Tras el remapeo quedará del siguiente modo:
La reprogramación de los PICs será a través de los registros I/O-ports:
0x20: Port de control del MASTER
0x21: Port de datos del MASTER
0xA0: Port de control del SLAVE
0xA1: Port de datos del SLAVE
Los PICs aceptan dos tipos de comandos: los ICW(Inicialization Command Word) que lo inicializan, y los OCW(Operation Command Word) que permiten programar la modalidad de funcionamiento, nosotros solo vamos a utilizar los ICW.
Antes de que los PICs de un sistema comiencen a trabajar deben recibir una secuencia de ICWs que los inicialice, el PIC espera recibir secuencialmente estos comandos unos tras otro, los tres primeros son obligatorios, el ICW4 es opcional.
El ICW1 se compone de la suma de dos valores:
En nuestro caso sí que vamos a enviar un ICW4 por lo tanto inicamos la reprogramación con 0x11, este comando hace que el PIC se ponga a la espera de 3 comandos mas en el puerto de datos:
NOTA: Recordemos que el PIC-M abarca las IRQs 0-7 y PIC-S 8-15, este detalle es importante cuando se indica como están conectados los PICs entre ellos
A parte de la reconfiguración también haremos un enmascaramiento de las interrupciones para mas adelante desenmascarar las que nos interesen.
El código de remapeo y enmascaramiento en C sería el siguiente.
vi kernel/irqs.h
void remap_irqs();
void mask_irq(unsigned char IRQline);
void unmask_irq(unsigned char IRQline);
vi kernel/irqs.c
#include "irqs.h"
#include "../drivers/ports.h"
void remap_irqs() {
port_byte_out(0x20, 0x11); // Reprogramming command PIC-M
port_byte_out(0xA0, 0x11); // Reprogramming command PIC-S
port_byte_out(0x21, 0x20); // vector offset PIC-M to 0x20 (32 decimal)
port_byte_out(0xA1, 0x28); // vector offset PIC-S to 0x28 (40 decimal)
port_byte_out(0x21, 0x04); // PIC-S at IRQ2 (0000 0100)
port_byte_out(0xA1, 0x02); // PIC-M at IRQ9 (0000 0010)
port_byte_out(0x21, 0x01); // 8086 CPU PIC-M
port_byte_out(0xA1, 0x01); // 8086 CPU PIC-S
port_byte_out(0x21, 0xFF); // disable all IRQs on PIC-M
port_byte_out(0xA1, 0xFF); // disable all IRQs on PIC-S
//port_byte_out(0x21, 0x00); // enable all IRQs on PIC-M
//port_byte_out(0xA1, 0x00); // enable all IRQs on PIC-S
}
void mask_irq(unsigned char IRQline) {
unsigned short port;
unsigned char value;
if(IRQline < 8) {
port = 0x21;
} else {
port = 0xA1;
IRQline -= 8;
}
value = port_byte_in(port) | (1 << IRQline);
port_byte_out(port, value);
}
void unmask_irq(unsigned char IRQline) {
unsigned short port;
unsigned char value;
if(IRQline < 8) {
port = 0x21;
} else {
port = 0xA1;
IRQline -= 8;
}
value = port_byte_in(port) & ~(1 << IRQline);
port_byte_out(port, value);
}
Una vez remapeadas las IRQs debemos definir nuestras rutinas de servicio en ASM, estas a su vez llamarán al código en C que se encargará de obtener el id de interrupción y actuar en consecuencia.
Hay varios aspectos que debemos señalar para comprender el funcionamiento de las ISRs:
El procedimiento a seguir por las ISRs es el siguiente:
NOTA: Las ISRs asociadas a una IRQ son exactamente iguales pero con la diferencia de que justo después de haber llamado a la ISR en C no se popea el valor de ds en ax si no en ex.
vi kernel/interrupts.asm
; Defined in isr.c
[extern isr_handler]
[extern irq_handler]
; Common ISR code
isr_common_stub:
; 1. Save CPU state
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
mov ax, ds ; Lower 16-bits of eax = ds.
push eax ; save the data segment descriptor
mov ax, 0x10 ; GDT data segment descriptor: 8*2=16 -> 0x10h
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; 2. Call C handler
call isr_handler
; 3. Restore state
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8 ; Cleans up the pushed error code and pushed ISR number
sti ; Reenable interruptions
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
; Common IRQ code. Identical to ISR code except for the 'call' and the 'pop ebx'
irq_common_stub:
pusha
mov ax, ds
push eax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call irq_handler ; Different than the ISR code
pop ebx ; Different than the ISR code
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
popa
add esp, 8
sti ; Reenable interruptions
iret
; We don't get information about which interrupt was caller when the handler is run, so we will need to have a different handler for every interrupt.
; Furthermore, some interrupts push an error code onto the stack but others don't, so we will push a dummy error code for those which don't, so that
; we have a consistent stack for all of them.
; First make the ISRs global
global isr0
global isr1
global isr2
global isr3
global isr4
global isr5
global isr6
global isr7
global isr8
global isr9
global isr10
global isr11
global isr12
global isr13
global isr14
global isr15
global isr16
global isr17
global isr18
global isr19
global isr20
global isr21
global isr22
global isr23
global isr24
global isr25
global isr26
global isr27
global isr28
global isr29
global isr30
global isr31
; IRQs ISRs
; PIC-M
global irq0
global irq1
global irq2
global irq3
global irq4
global irq5
global irq6
global irq7
; PIC-S
global irq8
global irq9
global irq10
global irq11
global irq12
global irq13
global irq14
global irq15
; 0: Divide By Zero Exception
isr0:
cli ; Disable interruptions temporaly
push byte 0
push byte 0
jmp isr_common_stub
; 1: Debug Exception
isr1:
cli
push byte 0
push byte 1
jmp isr_common_stub
; 2: Non Maskable Interrupt Exception
isr2:
cli
push byte 0
push byte 2
jmp isr_common_stub
; 3: Int 3 Exception
isr3:
cli
push byte 0
push byte 3
jmp isr_common_stub
; 4: INTO Exception
isr4:
cli
push byte 0
push byte 4
jmp isr_common_stub
; 5: Out of Bounds Exception
isr5:
cli
push byte 0
push byte 5
jmp isr_common_stub
; 6: Invalid Opcode Exception
isr6:
cli
push byte 0
push byte 6
jmp isr_common_stub
; 7: Coprocessor Not Available Exception
isr7:
cli
push byte 0
push byte 7
jmp isr_common_stub
; 8: Double Fault Exception (With Error Code!)
isr8:
cli
push byte 8
jmp isr_common_stub
; 9: Coprocessor Segment Overrun Exception
isr9:
cli
push byte 0
push byte 9
jmp isr_common_stub
; 10: Bad TSS Exception (With Error Code!)
isr10:
cli
push byte 10
jmp isr_common_stub
; 11: Segment Not Present Exception (With Error Code!)
isr11:
cli
push byte 11
jmp isr_common_stub
; 12: Stack Fault Exception (With Error Code!)
isr12:
cli
push byte 12
jmp isr_common_stub
; 13: General Protection Fault Exception (With Error Code!)
isr13:
cli
push byte 13
jmp isr_common_stub
; 14: Page Fault Exception (With Error Code!)
isr14:
cli
push byte 14
jmp isr_common_stub
; 15: Reserved Exception
isr15:
cli
push byte 0
push byte 15
jmp isr_common_stub
; 16: Floating Point Exception
isr16:
cli
push byte 0
push byte 16
jmp isr_common_stub
; 17: Alignment Check Exception
isr17:
cli
push byte 0
push byte 17
jmp isr_common_stub
; 18: Machine Check Exception
isr18:
cli
push byte 0
push byte 18
jmp isr_common_stub
; 19: Reserved
isr19:
cli
push byte 0
push byte 19
jmp isr_common_stub
; 20: Reserved
isr20:
cli
push byte 0
push byte 20
jmp isr_common_stub
; 21: Reserved
isr21:
cli
push byte 0
push byte 21
jmp isr_common_stub
; 22: Reserved
isr22:
cli
push byte 0
push byte 22
jmp isr_common_stub
; 23: Reserved
isr23:
cli
push byte 0
push byte 23
jmp isr_common_stub
; 24: Reserved
isr24:
cli
push byte 0
push byte 24
jmp isr_common_stub
; 25: Reserved
isr25:
cli
push byte 0
push byte 25
jmp isr_common_stub
; 26: Reserved
isr26:
cli
push byte 0
push byte 26
jmp isr_common_stub
; 27: Reserved
isr27:
cli
push byte 0
push byte 27
jmp isr_common_stub
; 28: Reserved
isr28:
cli
push byte 0
push byte 28
jmp isr_common_stub
; 29: Reserved
isr29:
cli
push byte 0
push byte 29
jmp isr_common_stub
; 30: Reserved
isr30:
cli
push byte 0
push byte 30
jmp isr_common_stub
; 31: Reserved
isr31:
cli
push byte 0
push byte 31
jmp isr_common_stub
; IRQ ISRs
irq0:
cli ; Disable interruptions temporaly
push byte 0
push byte 32
jmp irq_common_stub
irq1: ; KEYBOARD
cli
push byte 1
push byte 33
jmp irq_common_stub
irq2:
cli
push byte 2
push byte 34
jmp irq_common_stub
irq3:
cli
push byte 3
push byte 35
jmp irq_common_stub
irq4:
cli
push byte 4
push byte 36
jmp irq_common_stub
irq5:
cli
push byte 5
push byte 37
jmp irq_common_stub
irq6:
cli
push byte 6
push byte 38
jmp irq_common_stub
irq7:
cli
push byte 7
push byte 39
jmp irq_common_stub
irq8:
cli
push byte 8
push byte 40
jmp irq_common_stub
irq9:
cli
push byte 9
push byte 41
jmp irq_common_stub
irq10:
cli
push byte 10
push byte 42
jmp irq_common_stub
irq11:
cli
push byte 11
push byte 43
jmp irq_common_stub
irq12:
cli
push byte 12
push byte 44
jmp irq_common_stub
irq13:
cli
push byte 13
push byte 45
jmp irq_common_stub
irq14:
cli
push byte 14
push byte 46
jmp irq_common_stub
irq15:
cli
push byte 15
push byte 47
jmp irq_common_stub
El siguiente paso es declarar como externas las ISRs de nuestro ASM, algunas funciones relacionadas con las ISRs y las interrupciones y una estructura llamada interrupt_stack_data que contendrá todos los valores que se hayan pusheado a la pila antes de llamar a la función, de este modo podremos leer el id de interrupción y el código de error generado por la interrupción, recordemos que no todas las interrupciones generan estos códigos.
vi kernel/isrs.h
#ifndef ISRS_H
#define ISRS_H
/* ISRs reserved for CPU exceptions, extern because they are defined in ASM code */
extern void isr0(void);
extern void isr1(void);
extern void isr2(void);
extern void isr3(void);
extern void isr4(void);
extern void isr5(void);
extern void isr6(void);
extern void isr7(void);
extern void isr8(void);
extern void isr9(void);
extern void isr10(void);
extern void isr11(void);
extern void isr12(void);
extern void isr13(void);
extern void isr14(void);
extern void isr15(void);
extern void isr16(void);
extern void isr17(void);
extern void isr18(void);
extern void isr19(void);
extern void isr20(void);
extern void isr21(void);
extern void isr22(void);
extern void isr23(void);
extern void isr24(void);
extern void isr25(void);
extern void isr26(void);
extern void isr27(void);
extern void isr28(void);
extern void isr29(void);
extern void isr30(void);
extern void isr31(void);
/* IRQs interruptions, extern because they are defined in ASM code */
// PIC-M
extern void irq0(void);
extern void irq1(void);
extern void irq2(void);
extern void irq3(void);
extern void irq4(void);
extern void irq5(void);
extern void irq6(void);
extern void irq7(void);
// PIC-S
extern void irq8(void);
extern void irq9(void);
extern void irq10(void);
extern void irq11(void);
extern void irq12(void);
extern void irq13(void);
extern void irq14(void);
extern void irq15(void);
/* Struct which aggregates many registers */
typedef struct {
unsigned int ds; /* Data segment selector */
unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
unsigned int int_no, err_code; /* Interrupt number and error code (if applicable) */
unsigned int eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
} interrupt_stack_data;
void enable_interrupts();
void disable_interrupts();
void isr_install();
void isr_handler(interrupt_stack_data interrupt_stack_data);
void irq_handler(interrupt_stack_data interrupt_stack_data);
#endif
En isrs.c llamamos a la función set_idt_gate que nos generará los vectores de interrupción en la tabla IDT, le pasamos el número de interrupción y la dirección de memoria donde se encuentran nuestras ISRs escritas en ASM. Definimos un array con los mensajes asociados a cada interrupción/IRQ, el id de interrupción actuará a modo de índice. También podemos ver un par de funciones para habilitar/deshabilitar las interrupciones, finalmente la función a la que llaman todas las ISRs cuando se produce una interrupción es isr_handler o irq_handler en caso de tratarse de una IRQ.
vi kernel/isrs.c
#include "isrs.h"
#include "idt.h"
#include "../drivers/screen.h"
#include "../drivers/ports.h"
// IDT vectors
void isr_install() {
set_idt_gate(0, (unsigned int)isr0);
set_idt_gate(1, (unsigned int)isr1);
set_idt_gate(2, (unsigned int)isr2);
set_idt_gate(3, (unsigned int)isr3);
set_idt_gate(4, (unsigned int)isr4);
set_idt_gate(5, (unsigned int)isr5);
set_idt_gate(6, (unsigned int)isr6);
set_idt_gate(7, (unsigned int)isr7);
set_idt_gate(8, (unsigned int)isr8);
set_idt_gate(9, (unsigned int)isr9);
set_idt_gate(10, (unsigned int)isr10);
set_idt_gate(11, (unsigned int)isr11);
set_idt_gate(12, (unsigned int)isr12);
set_idt_gate(13, (unsigned int)isr13);
set_idt_gate(14, (unsigned int)isr14);
set_idt_gate(15, (unsigned int)isr15);
set_idt_gate(16, (unsigned int)isr16);
set_idt_gate(17, (unsigned int)isr17);
set_idt_gate(18, (unsigned int)isr18);
set_idt_gate(19, (unsigned int)isr19);
set_idt_gate(20, (unsigned int)isr20);
set_idt_gate(21, (unsigned int)isr21);
set_idt_gate(22, (unsigned int)isr22);
set_idt_gate(23, (unsigned int)isr23);
set_idt_gate(24, (unsigned int)isr24);
set_idt_gate(25, (unsigned int)isr25);
set_idt_gate(26, (unsigned int)isr26);
set_idt_gate(27, (unsigned int)isr27);
set_idt_gate(28, (unsigned int)isr28);
set_idt_gate(29, (unsigned int)isr29);
set_idt_gate(30, (unsigned int)isr30);
set_idt_gate(31, (unsigned int)isr31);
// IRQs
// PIC-M
set_idt_gate(32, (unsigned int)irq0);
set_idt_gate(33, (unsigned int)irq1);
set_idt_gate(34, (unsigned int)irq2);
set_idt_gate(35, (unsigned int)irq3);
set_idt_gate(36, (unsigned int)irq4);
set_idt_gate(37, (unsigned int)irq5);
set_idt_gate(38, (unsigned int)irq6);
set_idt_gate(39, (unsigned int)irq7);
// PIC-S
set_idt_gate(40, (unsigned int)irq8);
set_idt_gate(41, (unsigned int)irq9);
set_idt_gate(42, (unsigned int)irq10);
set_idt_gate(43, (unsigned int)irq11);
set_idt_gate(44, (unsigned int)irq12);
set_idt_gate(45, (unsigned int)irq13);
set_idt_gate(46, (unsigned int)irq14);
set_idt_gate(47, (unsigned int)irq15);
set_idt(); // Load IDT descriptor in idtr register
}
void enable_interrupts(){
__asm__ ("sti");
}
void disable_interrupts(){
__asm__ ("cli");
}
/* To print the message which defines every exception */
char *exception_messages[] = {
"Division By Zero\n",
"Debug\n",
"Non Maskable Interrupt\n",
"Breakpoint\n",
"Into Detected Overflow\n",
"Out of Bounds\n",
"Invalid Opcode\n",
"No Coprocessor\n",
"Double Fault\n",
"Coprocessor Segment Overrun\n",
"Bad TSS\n",
"Segment Not Present\n",
"Stack Fault\n",
"General Protection Fault\n",
"Page Fault\n",
"Unknown Interrupt\n",
"Coprocessor Fault\n",
"Alignment Check\n",
"Machine Check\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n",
"Reserved\n"
};
/* To print the message which defines every IRQ */
char *irq_messages[] = {
// PIC-M
"Programmable interval timer IRQ\n",
"Keyboard IRQ\n",
"8259A slave controller IRQ\n",
"COM2 / COM4 IRQ\n",
"COM1 / COM3 IRQ\n",
"LPT2 IRQ\n",
"Floppy controller IRQ\n",
"LPT1 IRQ\n",
// PIC-S
"RTC IRQ\n",
"Unassigned IRQ\n",
"Unassigned IRQ\n",
"Unassigned IRQ\n",
"Mouse controller IRQ\n",
"Math coprocessor IRQ\n",
"Hard disk controller 1 IRQ\n",
"Hard disk controller 2 IRQ\n"
};
void isr_handler(interrupt_stack_data interrupt_stack_data) {
print_string("Exception interrupt detected\n", -1, -1);
if ( interrupt_stack_data.int_no >= 0 && interrupt_stack_data.int_no <= 31){
print_string(exception_messages[interrupt_stack_data.int_no], -1, -1);
print_string("--------------------------------\n", -1, -1);
} else {
print_string("++ ERROR: Unknown exception interrupt triggered", -1, -1);
}
}
void irq_handler(interrupt_stack_data interrupt_stack_data) {
print_string("Inside irq_handler\n", -1, -1);
// If IRQ was generated by PIC-M send EOI to Master, if IRQ was generated by PIC-S send EOI to Slave/Master
if ( interrupt_stack_data.int_no >= 32 && interrupt_stack_data.int_no <= 39){
/*if ( interrupt_stack_data.int_no == 33 ){
print_string("Keyboard Key Pressed\n", -1, -1);
}*/
print_string("PIC-M IRQ detected\n", -1, -1);
print_string("Sending EOI to PIC-M\n", -1, -1);
port_byte_out(0x20, 0x20);
} else if ( interrupt_stack_data.int_no >= 40 && interrupt_stack_data.int_no <= 47){
print_string("PIC-S IRQ detected\n", -1, -1);
print_string("Sending EOI to PIC-S\n", -1, -1);
port_byte_out(0xA0, 0x20);
print_string("Sending EOI to PIC-M\n", -1, -1);
port_byte_out(0x20, 0x20);
} else {
print_string("++ ERROR: Unknown IRQ interrupt triggered", -1, -1);
}
port_byte_in(0x60); // Read keyboard key only to be able to continue testing it
print_string("--------------------------------\n", -1, -1);
}
El handler de las IRQs debe avisar a los PICs de que ha recibido la petición de interrupción mediante el comando EOI(End Of Interrupt), si la interrupción proviene del PIC-M solo se le envía a él, si proviene del PIC-S se debe enviar a ambos PICs, los PICs no generan mas IRQs hasta que se les haya notificado el EOI.
Un detalle a destacar es que si se produce una IRQ de teclado pero no se lee el dato del buffer del teclado, este se queda a la espera de que se lea y no generará mas IRQs hasta que el buffer quede vacío.
El siguiente paso es definir nuestra tabla IDT, esta tendrá el siguiente aspecto:
La tabla IDT(nterrupt Descriptor Table) no solo contiene los descriptores de las rutinas de servicio si no que además contienen algunos flags y niveles de protección.
Cada una de las entradas de la IDT es llamada gate, cada gate tiene el siguiente aspecto:
Para localizar la ISR necesitamos el descriptor de la tabla GDT que hace referencia al segmento de código y la dirección donde se encuentra la ISR en ASM, como podemos ver en la imagen anterior los bits que componen la dirección no son consecutivos así que habrá que leer sus partes y unirlas.
Los campos de una entrada IDT son los siguientes:
Las flags se detallan a continucación:
En nuestro caso el valor final de las FLAGS será 0x8E:
P=1, DPL=00b, S=0, X=1, type=110b => 1000_1110b=10001110=0x8E
Para indicarle al microporcesador donde se encuentra nuestra IDT lo haremos a través del registro idtr:
Declaramos las funciones relacionadas con la IDT y el descriptor de la GDT del segmento de código GDT_CS, recordemos que es el segundo segmento y cada uno ocupa 8bytes partiendo desde 0.
vi kernel/idt.h
#ifndef IDT_H
#define IDT_H
/* GDT Code Segment selector */
#define GDT_CS 0x08
/* Functions implemented in idt.c */
void set_idt_gate(int n, unsigned int isr_address);
void set_idt();
#endif
En el fichero siguiente definimos dos funciones, set_idt_gate que nos rellena la tabla IDT con los id de interrupción y las posiciones de memoria de las ISRs en ASM y set_idt que carga el descriptor de la tabla IDT en el registro idtr, también definiremos algunas estructuras como idt_gate que contendrá la información de una entrada IDT, idt_register es el descriptor que contiene la base de la IDT y su tamaño.
idt es un array de 256 posiciones de estructuras idt_gate y idt_reg es el descriptor de tipo idt_register.
vi kernel/idt.c
#include "idt.h"
/* How every interrupt gate is defined */
typedef struct {
unsigned short low_address; /* Lower 16 bits of handler function address */
unsigned short sel; /* GDT Code segment selector */
unsigned char always0;
unsigned char flags;
unsigned short high_address; /* Higher 16 bits of handler function address */
} __attribute__((packed)) idt_gate;
/* A pointer to the array of interrupt handlers.
* Assembly instruction 'lidt' will read it */
typedef struct {
unsigned short limit;
unsigned int base;
} __attribute__((packed)) idt_register;
#define IDT_ENTRIES 256
idt_gate idt[IDT_ENTRIES];// Array of gates
idt_register idt_reg;
void set_idt_gate(int n, unsigned int isr_address) {
idt[n].low_address = (isr_address & 0xFFFF);
idt[n].sel = GDT_CS;
idt[n].always0 = 0;
idt[n].flags = 0x8E;
idt[n].high_address = (isr_address >> 16) & 0xFFFF;
}
void set_idt() {
idt_reg.base = (unsigned int) &idt;
idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate) - 1;
/* Don't make the mistake of loading &idt -- always load &idt_reg */
__asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
}
Finalmente remapeamos las IRQs e instalamos las ISRs desde la función principal del kernel, generamos algunas interrupciones por código y nos mantenemos a la espera de que se pulse alguna tecla del teclado, si esto ocurre leemos la tecla para liberar el buffer del teclado y así poder seguir generando interrupciones pulsando teclas, todavía no leemos el código de la tecla.
vi kernel/kernel.c
#include "../drivers/screen.h"
#include "util.h"
#include "irqs.h"
#include "isrs.h"
void main() {
clear_screen();
print_string("Welcome to MercuryOS v0.1b by Kr0m\n", 0, 0);
print_string("\n", -1, -1);
print_string(">> Remapping and masking all IRQs\n", -1, -1);
remap_irqs();
print_string("Done\n", -1, -1);
print_string(">> Installing ISRs and setting idtr register to IDT table descriptor\n", -1, -1);
isr_install();
print_string("Done\n", -1, -1);
print_string(">> Unmasking Keyboard IRQ\n", -1, -1);
unmask_irq(1);
print_string("Done\n", -1, -1);
print_string(">> Enabling interrupts: 32 bits protected mode jump disabled it\n", -1, -1);
enable_interrupts();
print_string("Done\n", -1, -1);
print_string(">> Generating test interrupts\n", -1, -1);
/* Test the interrupts */
print_string("--------------------------------\n", -1, -1);
__asm__ __volatile__("int $2");
__asm__ __volatile__("int $3");
//__asm__ __volatile__("int $33");
print_string(">> Press any key to test IRQ1 keyboard interruption\n", -1, -1);
}
Como el kernel ha crecido habrá que aumentar el tamaño de sectores a cargar en el bootloader:
vi boot_sect.asm
KERNEL_SIZE equ 19
Tendremos que añadir kernel/interrupts.o a nuetro Makefile.
vi Makefile
OBJ = ${C_SOURCES:.c=.o kernel/interrupts.o}
Compilamos el kernel y lo cargamos en Qemu:
gmake run
Todos los cambios en ficheros existentes se han explicado en detalle pero ahún así dejo un tar.gz del código entero del proyecto.
Si tenemos dudas de si estamos utilizando PICs o algún otro conjunto de chips para atender las interrupciones podemos ejecutar Qemu con la opción -M help:
qemu-system-i386 os-image -M help
Supported machines are:
pc Standard PC (i440FX + PIIX, 1996) (alias of pc-i440fx-4.1)
pc-i440fx-4.1 Standard PC (i440FX + PIIX, 1996) (default)
pc-i440fx-4.0 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-3.1 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-3.0 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.9 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.8 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.7 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.6 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.5 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.4 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.3 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.2 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.12 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.11 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.10 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.1 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-2.0 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-1.7 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-1.6 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-1.5 Standard PC (i440FX + PIIX, 1996)
pc-i440fx-1.4 Standard PC (i440FX + PIIX, 1996)
pc-1.3 Standard PC (i440FX + PIIX, 1996)
pc-1.2 Standard PC (i440FX + PIIX, 1996)
pc-1.1 Standard PC (i440FX + PIIX, 1996)
pc-1.0 Standard PC (i440FX + PIIX, 1996)
pc-0.15 Standard PC (i440FX + PIIX, 1996) (deprecated)
pc-0.14 Standard PC (i440FX + PIIX, 1996) (deprecated)
pc-0.13 Standard PC (i440FX + PIIX, 1996) (deprecated)
pc-0.12 Standard PC (i440FX + PIIX, 1996) (deprecated)
q35 Standard PC (Q35 + ICH9, 2009) (alias of pc-q35-4.1)
pc-q35-4.1 Standard PC (Q35 + ICH9, 2009)
pc-q35-4.0.1 Standard PC (Q35 + ICH9, 2009)
pc-q35-4.0 Standard PC (Q35 + ICH9, 2009)
pc-q35-3.1 Standard PC (Q35 + ICH9, 2009)
pc-q35-3.0 Standard PC (Q35 + ICH9, 2009)
pc-q35-2.9 Standard PC (Q35 + ICH9, 2009)
pc-q35-2.8 Standard PC (Q35 + ICH9, 2009)
pc-q35-2.7 Standard PC (Q35 + ICH9, 2009)
pc-q35-2.6 Standard PC (Q35 + ICH9, 2009)
pc-q35-2.5 Standard PC (Q35 + ICH9, 2009)
pc-q35-2.4 Standard PC (Q35 + ICH9, 2009)
pc-q35-2.12 Standard PC (Q35 + ICH9, 2009)
pc-q35-2.11 Standard PC (Q35 + ICH9, 2009)
pc-q35-2.10 Standard PC (Q35 + ICH9, 2009)
isapc ISA-only PC
none empty machine
Como podemos ver por defecto utiliza PIIX.
Si te ha gustado el artículo puedes invitarme a un redbull aquí.