Implementing Interrupt Request (IRQ) handling involves several steps, including configuring the Programmable Interrupt Controller (PIC), setting up Interrupt Service Routines (ISRs), and writing IRQ handlers.
- Configure the Programmable Interrupt Controller (PIC)
- Remap IRQs
- Initialize the PIC and remap IRQs to avoid conflicts with CPU exceptions.
- Seng initialization commands (
ICW1
,ICW2
,ICW3
,ICW4
) to both PICs. - Set interrupt vectors for IRQs.
- Mask IRQs
- Mask IRQ lines that are not yet configured or not in use.
- Remap IRQs
- Write Interrupt Service Routines (ISRs)
- Set up IDT Entries
- Create IDT entries for each IRQ.
- Set the offset to point to the corresponding ISR.
- Populate the IDT with the entries.
- Define ISR Functions
- Write ISR functions for each IRQ.
- ISRs typically follow a common format, including saving CPU state, handling the interrupt, and sending End of Interrupt (EOI) signals to the PIC.
- Set up IDT Entries
- Write IRQ Handlers
- Initialize IRQ Handlers
- Write IRQ handler functions to handle specific IRQ events.
- Each handler may perform different tasks based on the device generating the interrupt.
- Register IRQ Handlers
- Register IRQ handlers with the corresponding ISRs.
- This ensures that when an interrupt occurs, the appropriate handler is invoked.
- Initialize IRQ Handlers
- Enable Interrupts
- Enable Interrupts Globally
- Enable interrupts globally by setting the Interrupt Flag (IF) in the CPU's flags register.
- Enable Interrupts Globally
- Handle Interrupts
- Respond to Interrupts
- When an IRQ occurs, the PIC forwards it to the CPU, which triggers the corresponding ISR.
- The ISR executes the IRQ handler, which performs the necessary actions to handle the interrupt.
- Respond to Interrupts
1 irq.c
#include <system.h> // Include system header for required definitions and functions
// Declare external assembly ISR functions
extern void _irq0();
extern void _irq1();
extern void _irq2();
extern void _irq3();
extern void _irq4();
extern void _irq5();
extern void _irq6();
extern void _irq7();
extern void _irq8();
extern void _irq9();
extern void _irq10();
extern void _irq11();
extern void _irq12();
extern void _irq13();
extern void _irq14();
extern void _irq15();
// Array to store custom IRQ handler functions
void *irq_routines[16] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
// Function to install a custom IRQ handler
void irq_install_handler(int irq, void *handler) {
irq_routines[irq] = handler; // Store the handler function in the array
}
// Function to uninstall a custom IRQ handler
void irq_uninstall_handler(int irq) {
irq_routines[irq] = 0; // Remove the handler function from the array
}
// Function to remap the PIC
void irq_remap() {
outportb(0x20, 0x11); // Initialize master PIC
outportb(0xA0, 0x11); // Initialize slave PIC
outportb(0x21, 0x20); // Remap master PIC IRQs to 0x20-0x27
outportb(0xA1, 0x28); // Remap slave PIC IRQs to 0x28-0x2F
outportb(0x21, 0x04); // Tell master PIC about slave PIC at IRQ2
outportb(0xA1, 0x02); // Tell slave PIC its cascade identity
outportb(0x21, 0x01); // Set master PIC to 8086 mode
outportb(0xA1, 0x01); // Set slave PIC to 8086 mode
outportb(0x21, 0x0); // Unmask all IRQs on master PIC
outportb(0xA1, 0x0); // Unmask all IRQs on slave PIC
}
// Function to set up the IDT entries for IRQs
void irq_install() {
irq_remap(); // Remap the PIC
// Set the IDT entries for each IRQ
idt_set_gate(32, (unsigned)_irq0, 0x08, 0x8E);
idt_set_gate(33, (unsigned)_irq1, 0x08, 0x8E);
idt_set_gate(34, (unsigned)_irq2, 0x08, 0x8E);
idt_set_gate(35, (unsigned)_irq3, 0x08, 0x8E);
idt_set_gate(36, (unsigned)_irq4, 0x08, 0x8E);
idt_set_gate(37, (unsigned)_irq5, 0x08, 0x8E);
idt_set_gate(38, (unsigned)_irq6, 0x08, 0x8E);
idt_set_gate(39, (unsigned)_irq7, 0x08, 0x8E);
idt_set_gate(40, (unsigned)_irq8, 0x08, 0x8E);
idt_set_gate(41, (unsigned)_irq9, 0x08, 0x8E);
idt_set_gate(42, (unsigned)_irq10, 0x08, 0x8E);
idt_set_gate(43, (unsigned)_irq11, 0x08, 0x8E);
idt_set_gate(44, (unsigned)_irq12, 0x08, 0x8E);
idt_set_gate(45, (unsigned)_irq13, 0x08, 0x8E);
idt_set_gate(46, (unsigned)_irq14, 0x08, 0x8E);
idt_set_gate(47, (unsigned)_irq15, 0x08, 0x8E);
}
// The main IRQ handler function
void irq_handler(struct regs *r) {
void (*handler)(struct regs *r); // Define a handler function pointer
// Get the custom handler for this IRQ from the array
handler = irq_routines[r->int_no - 32];
// If a custom handler is installed, call it
if (handler) {
handler(r);
}
// If the interrupt came from the slave PIC (IRQ8-IRQ15)
if (r->int_no >= 40) {
outportb(0xA0, 0x20); // Send End of Interrupt (EOI) signal to the slave PIC
}
// Send EOI signal to the master PIC
outportb(0x20, 0x20);
}
1 IRQ Handler Installation and Uninstallation Functions
- These functions allow for registration and deregistration of custom handlers for each IRQ.
void *irq_routines[16] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
void irq_install_handler(int irq, void *handler) {
irq_routines[irq] = handler;
}
void irq_uninstall_handler(int irq) {
irq_routines[irq] = 0;
}
irq_routines
: An array to store pointers to custom IRQ handlers.irq_install_handler
: Registers a handler for a specific IRQ.irq_uninstall_handler
: Unregisters a handler for a specific IRQ.
2 PIC Remapping:
The PIC must be remapped to avoid conflicts with the CPU's existing interrupt vectors.
void irq_remap() {
outportb(0x20, 0x11); // Initialize master PIC
outportb(0xA0, 0x11); // Initialize slave PIC
outportb(0x21, 0x20); // Remap master PIC IRQs to 0x20-0x27
outportb(0xA1, 0x28); // Remap slave PIC IRQs to 0x28-0x2F
outportb(0x21, 0x04); // Set up cascading
outportb(0xA1, 0x02); // Set up cascading
outportb(0x21, 0x01); // Environment info
outportb(0xA1, 0x01); // Environment info
outportb(0x21, 0x0); // Unmask all IRQs on master PIC
outportb(0xA1, 0x0); // Unmask all IRQs on slave PIC
}
- The
outportb
function sends data to the specified I/O port. - PIC remapping is necessary to avoid conflicts with the CPU's exceptions (first 32 interrupt vectors).
3 IDT Entry Setup for IRQs
Each IRQ vector is mapped to its corresponding ISR.
void irq_install() {
irq_remap();
idt_set_gate(32, (unsigned)_irq0, 0x08, 0x8E);
idt_set_gate(33, (unsigned)_irq1, 0x08, 0x8E);
idt_set_gate(34, (unsigned)_irq2, 0x08, 0x8E);
idt_set_gate(35, (unsigned)_irq3, 0x08, 0x8E);
idt_set_gate(36, (unsigned)_irq4, 0x08, 0x8E);
idt_set_gate(37, (unsigned)_irq5, 0x08, 0x8E);
idt_set_gate(38, (unsigned)_irq6, 0x08, 0x8E);
idt_set_gate(39, (unsigned)_irq7, 0x08, 0x8E);
idt_set_gate(40, (unsigned)_irq8, 0x08, 0x8E);
idt_set_gate(41, (unsigned)_irq9, 0x08, 0x8E);
idt_set_gate(42, (unsigned)_irq10, 0x08, 0x8E);
idt_set_gate(43, (unsigned)_irq11, 0x08, 0x8E);
idt_set_gate(44, (unsigned)_irq12, 0x08, 0x8E);
idt_set_gate(45, (unsigned)_irq13, 0x08, 0x8E);
idt_set_gate(46, (unsigned)_irq14, 0x08, 0x8E);
idt_set_gate(47, (unsigned)_irq15, 0x08, 0x8E);
}
idt_set_gate
: A function that sets an entry in the Interrupt Descriptor Table (IDT)._irq0
to_irq15
: Assembly ISR stubs for each IRQ.
4 Main IRQ Handler
This handler processes the interrupt, calls the appropriate registered handler, and sends End of Interrupt (EOI) signals to the PIC.
void irq_handler(struct regs *r) {
void (*handler)(struct regs *r);
handler = irq_routines[r->int_no - 32];
if (handler) {
handler(r);
}
if (r->int_no >= 40) {
outportb(0xA0, 0x20); // Send EOI to slave PIC
}
outportb(0x20, 0x20); // Send EOI to master PIC
}
struct regs
: A structure representing the CPU state.handler = irq_routines[r->int_no - 32]
: Maps the interrupt number to the corresponding IRQ handler.- Sending EOI (
0x20
) to the PIC ensures that it is ready to handle the next interrupt.
2 irq_asm.asm
This assembly file will contain the definitions of interrupt service routines (ISRs) for IRQs (Interrupt Request) 0 through 15 and provides a common stub to handle them. The ISRs push the IRQ number and an error code onto the stack and then jump to a common handler (irq_common_stub
). The common handler saves the CPU state, calls a C function to handle the IRQ, and then restores the CPU state before returning from the interrupt.
global _irq0
global _irq1
global _irq2
global _irq3
global _irq4
global _irq5
global _irq6
global _irq7
global _irq8
global _irq9
global _irq10
global _irq11
global _irq12
global _irq13
global _irq14
global _irq15
global _irq16
global _irq17
global _irq18
global _irq19
global _irq20
global _irq21
global _irq22
global _irq23
global _irq24
global _irq25
global _irq26
global _irq27
global _irq28
global _irq29
global _irq30
global _irq31
_irq0:
cli
push byte 0
push byte 32
jmp irq_common_stub
_irq1:
cli
push byte 0
push byte 33
jmp irq_common_stub
_irq2:
cli
push byte 0
push byte 34
jmp irq_common_stub
_irq3:
cli
push byte 0
push byte 35
jmp irq_common_stub
_irq4:
cli
push byte 0
push byte 36
jmp irq_common_stub
_irq5:
cli
push byte 0
push byte 37
jmp irq_common_stub
_irq6
cli
push byte 0
push byte 38
jmp irq_common_stub
_irq7:
cli
push byte 0
push byte 39
jmp irq_common_stub
_irq8:
cli
push byte 0
push byte 40
jmp irq_common_stub
_irq9:
cli
push byte 0
push byte 41
jmp irq_common_stub
_irq10:
cli
push byte 0
push byte 42
jmp irq_common_stub
_irq11:
cli
push byte 0
push byte 43
jmp irq_common_stub
_irq12:
cli
push byte 0
push byte 44
jmp irq_common_stub
_irq13:
cli
push byte 0
push byte 45
jmp irq_common_stub
_irq14:
cli
push byte 0
push byte 46
jmp irq_common_stub
_irq15:
cli
push byte 0
push byte 47
jmp irq_common_stub
extern irq_handler
irq_common_stub:
pusha
push ds
push es
push fs
push gs
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp
push eax
mov eax, irq_handler
call eax
pop eax
pop gs
pop fs
pop es
pop ds
popa
add esp, 8
iret
1 Definitions and Declarations:
global _irq0
global _irq1
global _irq2
global _irq3
global _irq4
global _irq5
global _irq6
global _irq7
global _irq8
global _irq9
global _irq10
global _irq11
global _irq12
global _irq13
global _irq14
global _irq15
- These lines declare the
_irq0
to_irq15
labels as global, making them accessible from other files. This is necessary for linking with other parts of the operating system, such as the IDT setup code in C.
2 Interrupt Service Routines
Each ISR follows a similar pattern. Let's look at _irq0
as an example:
_irq0:
cli ; Clear interrupts to prevent nesting
push byte 0 ; Push an error code of 0
push byte 32 ; Push the IRQ number (32 is IRQ0)
jmp irq_common_stub ; Jump to the common handler
- This ISR disables interrupts (
cli
), pushes an error code (0 in this case, as hardware interrupts don't have error codes), pushes the IRQ number (32 for IRQ0), and then jumps to the common stub (irq_common_stub
). - The other ISRs (
_irq1
to_irq15
) follow the same structure, with the IRQ number incremented by 1 for each.
3 Common Handler Stub
The irq_common_stub
handles the common tasks for all ISRs:
extern irq_handler
irq_common_stub:
pusha ; Push all general-purpose registers
push ds ; Save the data segment registers
push es
push fs
push gs
mov ax, 0x10 ; Load the data segment selector (0x10)
mov ds, ax ; Set the data segment registers
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp ; Get the current stack pointer
push eax ; Push the stack pointer (struct regs)
mov eax, irq_handler ; Get the address of irq_handler
call eax ; Call irq_handler (C function)
pop eax ; Clean up the stack
pop gs ; Restore segment registers
pop fs
pop es
pop ds
popa ; Restore general-purpose registers
add esp, 8 ; Clean up the pushed error code and IRQ number
iret ; Return from interrupt
- pusha: Pushes all general-purpose registers onto the stack to save their state.
- push ds, es, fs, gs: Saves the data segment registers.
- mov ax, 0x10: Loads the data segment selector (assumes the segment selector for data segments is 0x10).
- mov ds, es, fs, gs, ax: Sets the data segment registers to 0x10.
- mov eax, esp: Loads the current stack pointer into
eax
. - push eax: Pushes the stack pointer, which points to the saved state, as an argument to
irq_handler
. - mov eax, irq_handler: Loads the address of the
irq_handler
function (defined in C). - call eax: Calls the
irq_handler
function. - pop eax: Cleans up the stack.
- pop gs, fs, es, ds: Restores the data segment registers.
- popa: Restores the general-purpose registers.
- add esp, 8: Cleans up the stack by removing the pushed error code and IRQ number.
- iret: Returns from the interrupt, restoring the state and resuming execution.
- The
irq_handler
function, which is called from theirq_common_stub
, is typically written in C. It is responsible for handling the specific IRQ, calling any registered custom handlers, and sending an End of Interrupt (EOI) signal to the PIC.
4 Summary:
- The ISRs (
_irq0
to_irq15
) handle hardware interrupts by disabling interrupts, pushing error codes and IRQ numbers onto the stack, and jumping to a common handler stub. - The
irq_common_stub
saves the CPU state, sets up segment registers, and calls the Cirq_handler
function. - The
irq_handler
function in C handles the specific IRQ, calls custom handlers if registered, and sends EOI signals to the PIC.
3 Define extern declarations for the irq.c
functions
Add the interrupt handlers function declaration in any header file, but for the time being we put it where other declarations are include/system.h
:
/* Interrupt Handlers */
extern void irq_install();
extern void irq_install_handler(int irq, void *handler);
extern void irq_uninstall_handler(int irq);
4 call irq_install
from the kernel main:
Nothing will work until we call the irq_install
from the kernel main. Add the irq_install()
in the main.c
file after isrs_install()
:
gdt_install();
idt_install();
isrs_install();
.......................
irq_install();
.......................
init_video();