1

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.

What comes up upon startup: enter image description here

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.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • Looks like you're not re-enabling interrupts at the end of your interrupt handler. – zwol Feb 16 '23 at 03:39
  • @zwol how would I go about that? `__asm__ __volatile__("sti");`? –  Feb 16 '23 at 03:47
  • 1
    You don't need to re-enable interrupts. Since you are using interrupt gates in your IDT the state of the Interrupt Flag will be restored to what it was before the Interrupt occurred when the IRET is done. When an Interrupt Gate is executed the processor automatically turns off (IF=0) external Interrupts before the IRQ handler is called and then restores it back to the original value after. – Michael Petch Feb 16 '23 at 03:50
  • 1
    When it comes to the keyboard if you don't read a character from the keyboard port 0x60 when an interrupt occurs you won't get further keyboard(IRQ1) interrupts. Same for mouse interrupts.As well you are sending an EOI (End of Interrupt) as if the interrupt was from the second PIC(slave). For an interrupt on the 1st PIC (master)you should just do a write of 0x20 to port 0x20 and no more. Failure to do this could result in spurious interrupts being generated.Only send 0xa0 to port 0x20 **and** 0x20 to port 0x20 if the interrupt came from the 2nd (slave) PIC,otherwise just send 0x20 to port 0x20 – Michael Petch Feb 16 '23 at 04:00
  • 2
    I just noticed one other serious problem. Oops. You can not use a normal _C_ function as an interrupt handler because `irq_handler` is going to use a `ret` to return, but you need it to perform an `iret`. If you are using a more recent GCC compiler you can put `__attribute__((interrupt))` on the function definition. But the way your code is designed with `registers_t *` being passed you really should have an assembly stub that calls over to the _C_ function. An example is here in this SO answer: https://stackoverflow.com/a/37635449/3857942 – Michael Petch Feb 16 '23 at 04:12
  • There is another issue with the PIC remapping. Just **before** `outb(PIC1_DATA, ICW4_8086);` `io_wait();` `outb(PIC2_DATA, ICW4_8086);` `io_wait();` you forgot to enable cascading. So before the lines of code above you need: `outb(PIC1_DATA, 4);` `io_wait();` `outb(PIC2_DATA, 2);` `io_wait();`. It should be noted that since you ended up unmasking all the interrupts - when you get things working you will be flooded with IRQ0 as well which is the timer that will fire every 55ms. – Michael Petch Feb 16 '23 at 11:41
  • 1
    @MichaelPetch Using a normal function as an interrupt handler is what I meant. No IRET = flags not restored = interrupts not re-enabled (and various other bad things) – zwol Feb 16 '23 at 12:46

0 Answers0