I am currently trying to build my first OS with the help of JamesM's tutorial and the OSDev wiki. I run my code with qemu-system-i386.
I have managed to load the GDT and the IDT (and so far they seem to be correct), but, although I have remapped the PIC and filled the IDT accordingly, I cannot get the IRQs to work. As advised on the wiki, I have checked that I receive software interrupts, and I have enabled all the IRQs on the PIC mask.
Here is my code :
Code: Select all
/* ---------- kernel.c ---------- */
void kernel_main(void) {
terminal_initialize();
gdt_init();
idt_init();
timer_init(50);
}
Code: Select all
/* ---------- descr_tbl.c ---------- */
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <kernel/descrtbl.h>
// ------------------------------ GDT ------------------------------ //
gdt_entry_t gdt[5];
gdt_ptr_t gdt_ptr;
void gdt_setentry(size_t num,
uint32_t base,
uint32_t limit,
uint8_t access,
uint8_t granularity) {
// See https://wiki.osdev.org/GDT
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24) & 0xFF;
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = (limit >> 16) & 0x0F;
gdt[num].granularity |= granularity & 0xF0;
gdt[num].access = access;
}
// In descrtbl_lib.S
extern void gdt_flush(uint32_t);
void gdt_init(void) {
gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1;
gdt_ptr.base = (uint32_t) &gdt;
gdt_setentry(0, 0, 0, 0, 0); // Null segment
gdt_setentry(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // Code segment
gdt_setentry(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // Data segment
gdt_setentry(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment
gdt_setentry(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment
gdt_flush((uint32_t) &gdt_ptr);
}
// ------------------------------ IDT ------------------------------ //
#define MASTER_PIC_CMD 0x20
#define MASTER_PIC_DATA 0x21
#define SLAVE_PIC_CMD 0xA0
#define SLAVE_PIC_DATA 0xA1
idt_entry_t idt[256];
idt_ptr_t idt_ptr;
void idt_setentry(uint8_t num,
uint32_t base,
uint16_t selector,
uint8_t flags) {
// See https://wiki.osdev.org/IDT
idt[num].base_lo = base & 0xFFFF;
idt[num].base_hi = (base >> 16) & 0xFFFF;
idt[num].selector = selector;
idt[num].always0 = 0;
idt[num].flags = flags /* | 0x60 in user mode */;
}
// In descrtbl_lib.S
extern void idt_flush(uint32_t);
void idt_init(void) {
idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1;
idt_ptr.base = (uint32_t) &idt;
memset(&idt, 0, sizeof(idt_entry_t) * 256);
// Load the exceptions : see https://wiki.osdev.org/Exceptions
idt_setentry(0, (uint32_t) isr0, 0x08, 0x8E);
...
idt_setentry(31, (uint32_t) isr31, 0x08, 0x8E);
// Initialize the PICs
outb(MASTER_PIC_CMD, 0x11);
io_wait();
outb(SLAVE_PIC_CMD, 0x11);
io_wait();
// Give the vector offset
outb(MASTER_PIC_DATA, 0x20);
io_wait();
outb(SLAVE_PIC_DATA, 0x28);
io_wait();
// Acknowledge the other PIC
outb(MASTER_PIC_DATA, 0x04);
io_wait();
outb(SLAVE_PIC_DATA, 0x02);
io_wait();
// 8086/88 mode
outb(MASTER_PIC_DATA, 0x01);
io_wait();
outb(SLAVE_PIC_DATA, 0x01);
io_wait();
// Set the PICs' masks
outb(MASTER_PIC_DATA, 0x00);
outb(SLAVE_PIC_DATA, 0x00);
// Fill the IDT with the IRQs
idt_setentry(32, (uint32_t) irq0, 0x08, 0x8E);
...
idt_setentry(47, (uint32_t) irq15, 0x08, 0x8E);
idt_flush((uint32_t) &idt_ptr);
}
Code: Select all
/* ---------- descr_lib.S ---------- */
.global gdt_flush
gdt_flush:
mov 4(%esp), %eax
lgdt (%eax)
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
#jmp $0x08, $.reload_cs
.reload_cs:
ret
.global idt_flush
idt_flush:
mov 4(%esp), %eax
lidt (%eax)
ret
.global isr0
isr0:
cli
push $0
push $0
jmp isr_common_stub
...
.global isr31
isr31:
cli
push $0
push $31
jmp isr_common_stub
.global irq0
irq0:
cli
push $0
push $32
jmp irq_common_stub
...
.global irq15
irq15:
cli
push $0
push $47
jmp irq_common_stub
.extern isr_handler
isr_common_stub:
pusha # Push the registers
mov %ds, %ax
push %eax # Push the current ds descriptor
mov $0x10, %ax # Load the kernel ds descriptor
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
call isr_handler
cli
pop %eax # Reload the original ds descriptor
hlt
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
popa # Reload the registers
add $8, %esp # Clean the stack
sti
iret # Pop cs, eip, eflags, ss and esp
irq_common_stub:
pusha # Push the registers
mov %ds, %ax
push %eax # Save the current ds descriptor
mov $0x10, %ax # Load the kernel ds descriptor
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
call irq_handler
pop %ebx # Reload the original data segment descriptor
mov %bx, %ds
mov %bx, %es
mov %bx, %fs
mov %bx, %gs
popa # Reload the registers
add $8, %esp
sti
iret # Pop cs, eip, eflags, ss and esp
Code: Select all
/* ---------- isr.c ---------- */
#include <stdio.h>
#include <kernel/isr.h>
#include <kernel/tty.h>
isr_t interrupt_handlers[256];
void register_interrupt_handler(uint8_t n, isr_t handler) {
interrupt_handlers[n] = handler;
}
// Called by the isr_common_stub when the kernel is interrupted by an exception
void isr_handler(registers_t regs) {
// Handle the exceptions
if (regs.int_no <= 31) {
char* exceptions[] = {"Division By Zero",
...
"Unknown"};
printf("\nException %d: %s", regs.int_no, exceptions[regs.int_no]);
}
}
// Called by irq_common_stub when the kernel is interrupted by an IRQ
void irq_handler(registers_t regs) {
// Handle the IRQs
// Send an EOI
if (regs.int_no >= 40) {
// Send the EOI to the slave PIC
outb(0xA0, 0x20);
}
// Send the EOI to the master PIC
outb(0x20, 0x20);
if (interrupt_handlers[regs.int_no] != 0) {
isr_t handler = interrupt_handlers[regs.int_no];
handler(regs);
}
}
Code: Select all
/* ---------- timer.c ---------- */
#include <stdio.h>
#include <kernel/isr.h>
#include <kernel/timer.h>
#define PIT_CHAN_0_DATA_PORT 0x40
#define PIT_CHAN_1_DATA_PORT 0x41
#define PIT_CHAN_2_DATA_PORT 0x42
#define PIT_CMD_PORT 0x43
uint32_t tick = 0;
static void timer_callback(registers_t regs) {
tick++;
printf("%d\n", tick);
}
void timer_init(uint32_t frequency) {
// Register timer_callback as the interrupt handler for IRQ0
register_interrupt_handler(32, &timer_callback);
uint32_t divisor = 1193180 / frequency;
// Set the PIT in repeating mode and prepare it for initialization
outb(PIT_CMD_PORT, 0x36);
// Send the frequency divisor
uint8_t lo = (uint8_t) (divisor & 0xFF);
uint8_t hi = (uint8_t) ((divisor >> 8) & 0xFF);
outb(PIT_CHAN_0_DATA_PORT, lo);
outb(PIT_CHAN_0_DATA_PORT, hi);
}
I believe the problem lies with the PIC since I receive software interrupts and since the are_interrupts_enabled function reports that the IRQs are disabled even after I load the IDT with idt_load.
Would anyone happen to have an idea of what is wrong in my code ?
Thank you for reading.