I am trying to work on an x86 operating system and coding it in C. My OS uses GRUB-Multiboot and thus far I have been able to get a GDT working.
I am currently working on my IDT and have run into a problem. When my OS boots up, it receives one IRQ and no further ones. I do have the keyboard line enabled and I should be receiving IRQs from that whenever I click a key but this is not the case.
Here is the code for idt.c
:
#include <stdint.h>
#include "idt.h"
#include "lib/stdio.h"
#include "include/io.h"
// Define the Interrupt Descriptor Table (IDT)
struct idt_entry_t idt[IDT_ENTRIES];
struct idt_entry_t idt_entries[IDT_ENTRIES];
// Load the IDT pointer
struct idt_ptr_t idt_ptr = {
.limit = sizeof(idt) - 1,
.base = (uint32_t)&idt
};
typedef struct registers {
uint32_t ds; // Data segment selector
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha
uint32_t int_no, err_code; // Interrupt number and error code (if applicable)
uint32_t eip, cs, eflags, useresp, ss; // Pushed by the processor automatically
} registers_t;
// Define an IRQ handler function
void irq_handler(registers_t *regs)
{
nanos_printf("Received an IRQ!\n");
// Send an EOI (end-of-interrupt) signal to the PICs
outb(0xA0, 0x20); // Send reset signal to slave
outb(0x20, 0x20); // Send reset signal to master
return;
}
void isr_handler()
{
nanos_printf("\n\nSTOP\n\nException occurred... halting...");
// Halt the CPU
for (;;);
}
void idt_load()
{
asm volatile("lidt %0" : : "m"(idt_ptr));
}
void idt_init(void)
{
// Disable interrupts
__asm__ __volatile__("cli");
// Setup the PIC(s)
outb(0x20, 0x11);
outb(0xA0, 0x11);
outb(0x21, 0x20);
outb(0xA1, 0x28);
outb(0x21, 0x04);
outb(0x21, 0x01);
outb(0x21, 0xFB);
outb(0xA1, 0xFF);
// Print a message to indicate that IDT is being loaded
nanos_printf("Loading IDT\n");
// Initialize the IDT pointer
idt_ptr.limit = sizeof(idt) - 1;
idt_ptr.base = (uint32_t)&idt;
// Set each IDT entry to the default handler
for (int i = 0; i < IDT_ENTRIES; i++)
{
idt_set_gate(i, (uint32_t)isr_handler, 0x08, 0x8E);
}
// Set the IRQ entries in the IDT
idt_set_gate(32, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(33, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(34, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(35, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(36, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(37, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(38, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(39, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(40, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(41, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(42, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(43, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(44, (uint32_t)irq_handler, 0x08, 0x8E);
idt_set_gate(45, (uint32_t)irq_handler, 0x08, 0x8E);
// Send initialization control word 1 and 2 to both PICs
outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // Initialization Control Word 1
io_wait();
outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
io_wait();
// Send initialization control word 3 to both PICs
outb(PIC1_DATA, 0x20); // Initialization Control Word 3: IRQ 0-7 map to IDT entries 0x20-0x27
io_wait();
outb(PIC2_DATA, 0x28); // Initialization Control Word 3: IRQ 8-15 map to IDT entries 0x28-0x2F
io_wait();
// Send initialization control word 4 to both PICs
outb(PIC1_DATA, ICW4_8086);
io_wait();
outb(PIC2_DATA, ICW4_8086);
io_wait();
// Unmask IRQs
outb(PIC1_DATA, 0x0);
outb(PIC2_DATA, 0x0);
// Load IDT
idt_load();
// Enable interrupts
__asm__ __volatile__("sti");
nanos_printf("IDT loaded\n");
}
void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags)
{
// Set the base address
idt[num].base_lo = base & 0xFFFF;
idt[num].base_hi = (base >> 16) & 0xFFFF;
// Set the selector
idt[num].sel = sel;
// Set the always0 field
idt[num].always0 = 0;
// Set the flags
idt[num].flags = flags;
}
And here is idt.h
:
#ifndef IDT_H
#define IDT_H
#include <stdint.h>
// Number of entries in the IDT
#define IDT_ENTRIES 256
// Struct for IDT entry
struct idt_entry_t {
uint16_t base_lo; // Lower 16 bits of handler function address
uint16_t sel; // Kernel segment selector
uint8_t always0; // Must always be zero
uint8_t flags; // Flags for entry (present, privilege level, type)
uint16_t base_hi; // Upper 16 bits of handler function address
} __attribute__((packed));
// Struct for IDT pointer
struct idt_ptr_t {
uint16_t limit; // Size of IDT
uint32_t base; // Base address of IDT
} __attribute__((packed));
// Define macros for setting flags in IDT entries
#define IDT_PRESENT_BIT 0x80
#define IDT_RING0 0x00
#define IDT_RING3 0x60
#define IDT_INT_GATE 0x0E
#define IDT_TRAP_GATE 0x0F
#define IDT_SIZE 0x08
// Define an IRQ handler function
void irq_handler();
// Define an ISR handler function
void isr_handler();
// Define a function to load the IDT
void idt_load();
// Define a function to initialize the IDT
void idt_init(void);
// Define a function to set a gate in the IDT
void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags);
#endif // IDT_H
The issue seems to lie within the IRQ handler itself not returning. I do send reset signals to both the master and slave PIC inside of irq_handler()
which is inside of idt.c
.
And yes, I do have an infinite loop at the end of my kernel_main()
:
void kernel_main(void)
{
clear_screen();
nanos_printf("Hello NanOS!\n");
gdt_init();
nanos_printf("Initialized Global Descriptor Table.\n");
idt_init();
nanos_printf("Initialized Interrupt Descriptor Table.\n");
while (true) {}
}
Problem: I tried to click keys on the keyboard to receive a keyboard IRQ but I only receive one IRQ at the start. I dabbled in OSDEV a few years back and from what I remember the PICs receive more than one IRQ upon startup anyways.