CLOSE

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. 

  1. Configure the Programmable Interrupt Controller (PIC)
    1. Remap IRQs
      1. Initialize the PIC and remap IRQs to avoid conflicts with CPU exceptions.
      2. Seng initialization commands (ICW1, ICW2, ICW3, ICW4) to both PICs.
      3. Set interrupt vectors for IRQs.
    2. Mask IRQs
      1. Mask IRQ lines that are not yet configured or not in use.
  2. Write Interrupt Service Routines (ISRs)
    1. Set up IDT Entries
      1. Create IDT entries for each IRQ.
      2. Set the offset to point to the corresponding ISR.
      3. Populate the IDT with the entries.
    2. Define ISR Functions
      1. Write ISR functions for each IRQ.
      2. ISRs typically follow a common format, including saving CPU state, handling the interrupt, and sending End of Interrupt (EOI) signals to the PIC.
  3. Write IRQ Handlers
    1. Initialize IRQ Handlers
      1. Write IRQ handler functions to handle specific IRQ events.
      2. Each handler may perform different tasks based on the device generating the interrupt.
    2. Register IRQ Handlers
      1. Register IRQ handlers with the corresponding ISRs.
      2. This ensures that when an interrupt occurs, the appropriate handler is invoked.
  4. Enable Interrupts
    1. Enable Interrupts Globally
      1. Enable interrupts globally by setting the Interrupt Flag (IF) in the CPU's flags register.
  5. Handle Interrupts
    1. Respond to Interrupts
      1. When an IRQ occurs, the PIC forwards it to the CPU, which triggers the corresponding ISR.
      2. The ISR executes the IRQ handler, which performs the necessary actions to handle the interrupt.

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
  1. pusha: Pushes all general-purpose registers onto the stack to save their state.
  2. push ds, es, fs, gs: Saves the data segment registers.
  3. mov ax, 0x10: Loads the data segment selector (assumes the segment selector for data segments is 0x10).
  4. mov ds, es, fs, gs, ax: Sets the data segment registers to 0x10.
  5. mov eax, esp: Loads the current stack pointer into eax.
  6. push eax: Pushes the stack pointer, which points to the saved state, as an argument to irq_handler.
  7. mov eax, irq_handler: Loads the address of the irq_handler function (defined in C).
  8. call eax: Calls the irq_handler function.
  9. pop eax: Cleans up the stack.
  10. pop gs, fs, es, ds: Restores the data segment registers.
  11. popa: Restores the general-purpose registers.
  12. add esp, 8: Cleans up the stack by removing the pushed error code and IRQ number.
  13. iret: Returns from the interrupt, restoring the state and resuming execution.
  • The irq_handler function, which is called from the irq_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 C irq_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();