Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ FONT_SRC = src/font/font8x16.c
CONSOLE_SRC = src/drivers/console/console.c
GDT_SRC = src/arch/x86/gdt.c
GDT_FLUSH_SRC = src/arch/x86/gdt_flush.asm
IDT_SRC = src/arch/x86/idt.c
IDT_FLUSH_SRC = src/arch/x86/idt_flush.asm
ISR_SRC = src/arch/x86/isr.asm
IRQ_SRC = src/arch/x86/irq.asm
MMAP_SRC = src/mem/mmap.c
PMM_SRC = src/mem/pmm.c

# Object files
BOOT_OBJ = $(BUILD_DIR)/boot.o
Expand All @@ -33,9 +39,15 @@ FONT_OBJ = $(BUILD_DIR)/font8x16.o
CONSOLE_OBJ = $(BUILD_DIR)/console.o
GDT_OBJ = $(BUILD_DIR)/gdt.o
GDT_FLUSH_OBJ = $(BUILD_DIR)/gdt_flush.o
IDT_OBJ = $(BUILD_DIR)/idt.o
IDT_FLUSH_OBJ = $(BUILD_DIR)/idt_flush.o
ISR_OBJ = $(BUILD_DIR)/isr.o
IRQ_OBJ = $(BUILD_DIR)/irq.o
MMAP_OBJ = $(BUILD_DIR)/mmap.o
PMM_OBJ = $(BUILD_DIR)/pmm.o

# All object files
OBJS = $(BOOT_OBJ) $(KERNEL_OBJ) $(VIDEO_OBJ) $(FONT_OBJ) $(CONSOLE_OBJ) $(GDT_OBJ) $(GDT_FLUSH_OBJ)
OBJS = $(BOOT_OBJ) $(KERNEL_OBJ) $(VIDEO_OBJ) $(FONT_OBJ) $(CONSOLE_OBJ) $(GDT_OBJ) $(GDT_FLUSH_OBJ) $(IDT_OBJ) $(IDT_FLUSH_OBJ) $(ISR_OBJ) $(IRQ_OBJ) $(MMAP_OBJ) $(PMM_OBJ)

# Output files
KERNEL_ELF = $(BUILD_DIR)/kernel.elf
Expand Down Expand Up @@ -113,6 +125,42 @@ $(GDT_FLUSH_OBJ): $(GDT_FLUSH_SRC)
@echo "Compiling GDT flush..."
$(NASM) $(NASMFLAGS) $(GDT_FLUSH_SRC) -o $(GDT_FLUSH_OBJ)

# Compile IDT
$(IDT_OBJ): $(IDT_SRC)
@mkdir -p $(BUILD_DIR)
@echo "Compiling IDT..."
$(CC) $(CFLAGS) -c $(IDT_SRC) -o $(IDT_OBJ)

# Compile IDT flush (assembly)
$(IDT_FLUSH_OBJ): $(IDT_FLUSH_SRC)
@mkdir -p $(BUILD_DIR)
@echo "Compiling IDT flush..."
$(NASM) $(NASMFLAGS) $(IDT_FLUSH_SRC) -o $(IDT_FLUSH_OBJ)

# Compile ISR (assembly)
$(ISR_OBJ): $(ISR_SRC)
@mkdir -p $(BUILD_DIR)
@echo "Compiling ISR..."
$(NASM) $(NASMFLAGS) $(ISR_SRC) -o $(ISR_OBJ)

# Compile IRQ (assembly)
$(IRQ_OBJ): $(IRQ_SRC)
@mkdir -p $(BUILD_DIR)
@echo "Compiling IRQ..."
$(NASM) $(NASMFLAGS) $(IRQ_SRC) -o $(IRQ_OBJ)

# Compile MMAP
$(MMAP_OBJ): $(MMAP_SRC)
@mkdir -p $(BUILD_DIR)
@echo "Compiling MMAP..."
$(CC) $(CFLAGS) -c $(MMAP_SRC) -o $(MMAP_OBJ)

# Compile PMM
$(PMM_OBJ): $(PMM_SRC)
@mkdir -p $(BUILD_DIR)
@echo "Compiling PMM..."
$(CC) $(CFLAGS) -c $(PMM_SRC) -o $(PMM_OBJ)

# Clean build artifacts
clean:
@echo "Cleaning build directory..."
Expand Down
7 changes: 7 additions & 0 deletions include/arch/x86/idt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#include <stdint.h>

void idt_init(void);
void idt_set_gate(uint8_t num, uint32_t base, uint16_t selector, uint8_t flags);

1 change: 1 addition & 0 deletions include/drivers/video/video.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ struct multiboot_tag_framebuffer {
void video_init(void* mbinfo);
void video_clear_screen(void);
void video_draw_char(int cx, int cy, char c);
void video_scroll_up(void);

4 changes: 4 additions & 0 deletions include/mem/mmap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma once
#include <stdint.h>

void mmap_dump(uint32_t magic, void* mbinfo);
13 changes: 13 additions & 0 deletions include/mem/pmm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
#include <stddef.h>

#define PAGE_SIZE 4096u

void pmm_init(uint32_t magic, void* mbinfo);
void* pmm_alloc_page(void);
void pmm_free_page(void* page);

uint32_t pmm_total_pages(void);
uint32_t pmm_free_pages(void);

5 changes: 5 additions & 0 deletions linker.ld
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ ENTRY(_start)
SECTIONS {
. = 1M;

kernel_start = .;

.multiboot : {
*(.multiboot)
}
Expand All @@ -24,4 +26,7 @@ SECTIONS {
*(COMMON)
*(.bss*)
}

kernel_end = .;

}
18 changes: 2 additions & 16 deletions src/arch/x86/gdt.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include "arch/x86/gdt.h"
#include "drivers/console/console.h"

// gdt entry 8byte
struct gdt_entry {
Expand All @@ -11,7 +10,7 @@ struct gdt_entry {
uint8_t base_high; // base [24:31]
} __attribute__((packed)); // do not add padding in struct

// gdtr pointer struct
// gdtr pointer sturct
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in the comment: "sturct" should be "struct".

Suggested change
// gdtr pointer sturct
// gdtr pointer struct

Copilot uses AI. Check for mistakes.
struct gdt_ptr {
uint16_t limit; // gdt size
uint32_t base; // gdt arr base address
Expand All @@ -20,7 +19,7 @@ struct gdt_ptr {
static struct gdt_entry gdt[3];
static struct gdt_ptr gp;

// Implemented in assembly (see gdt_flush.asm)
// extern implement from assembly
extern void gdt_flush(uint32_t);

// gdt_entry constructor
Expand Down Expand Up @@ -62,17 +61,4 @@ void gdt_init(void) {

// flush gdt info to cpu
gdt_flush((uint32_t)&gp);

// Verify GDT base is not zero
struct gdt_ptr_dump {
uint16_t limit;
uint32_t base;
} __attribute__((packed));

struct gdt_ptr_dump gdtr;
__asm__ __volatile__("sgdt %0" : "=m"(gdtr));

if (gdtr.base != 0) {
console_puts("GDT OK\n");
}
}
209 changes: 209 additions & 0 deletions src/arch/x86/idt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#include "arch/x86/idt.h"
#include "drivers/console/console.h"

// IDT entry structure (8 bytes)
struct idt_entry {
uint16_t base_low; // offset bits 0-15
uint16_t selector; // code segment selector
uint8_t zero; // reserved, always 0
uint8_t flags; // flags: P, DPL, type
uint16_t base_high; // offset bits 16-31
} __attribute__((packed));

// IDTR structure (6 bytes)
struct idt_ptr {
uint16_t limit; // IDT size - 1
uint32_t base; // IDT base address
} __attribute__((packed));

// IDT with 256 entries (x86 supports 256 interrupts)
static struct idt_entry idt[256];
static struct idt_ptr idtp;

// External assembly function to load IDTR
extern void idt_flush(uint32_t);

// Interrupt handler stubs (will be called from assembly)
extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();
extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();
extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();

// IRQ handlers
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();

// Interrupt stack frame structure
// Stack layout (from top to bottom after pusha and segment registers):
// GS, FS, ES, DS (pushed manually)
// EDI, ESI, EBP, ESP, EBX, EDX, ECX, EAX (pusha)
// int_no, err_code (pushed manually)
// EIP, CS, EFLAGS (pushed by CPU)
// (useresp, ss only in user mode)
struct interrupt_frame {
uint32_t gs, fs, es, ds; // Segment registers (top of stack)
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // pusha order
Comment on lines +81 to +87
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interrupt_frame struct member order for pusha registers appears to be incorrect. The pusha instruction pushes registers in the order: EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI. This means in memory (from low to high address), they appear as EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI. However, the struct defines them as "edi, esi, ebp, esp, ebx, edx, ecx, eax" (low address to high address). When ESP is passed to the C handler and cast to this struct, the first field should be at the lowest address. Since GS was pushed last, it's at the lowest address, so the struct starts correctly with gs. After the segment registers come the pusha registers, where EAX should be at the lowest address, not EDI. The correct order should be: eax, ecx, edx, ebx, esp, ebp, esi, edi.

Suggested change
// EDI, ESI, EBP, ESP, EBX, EDX, ECX, EAX (pusha)
// int_no, err_code (pushed manually)
// EIP, CS, EFLAGS (pushed by CPU)
// (useresp, ss only in user mode)
struct interrupt_frame {
uint32_t gs, fs, es, ds; // Segment registers (top of stack)
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // pusha order
// EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI (pusha, from low to high address)
// int_no, err_code (pushed manually)
// EIP, CS, EFLAGS (pushed by CPU)
// (useresp, ss only in user mode)
struct interrupt_frame {
uint32_t gs, fs, es, ds; // Segment registers (top of stack)
uint32_t eax, ecx, edx, ebx, esp, ebp, esi, edi; // pusha order

Copilot uses AI. Check for mistakes.
uint32_t int_no, err_code; // Interrupt number and error code
uint32_t eip, cs, eflags; // CPU pushed registers
uint32_t useresp, ss; // User mode stack (if applicable)
} __attribute__((packed));

// Generic interrupt handler (called from assembly stubs)
void idt_handler(struct interrupt_frame* frame) {
console_puts("[IDT] Exception occurred: ");
// Simple number output
if (frame->int_no < 10) {
console_putc('0' + frame->int_no);
} else {
console_putc('0' + (frame->int_no / 10));
console_putc('0' + (frame->int_no % 10));
}
console_puts("\n");
}

// IRQ handler (called from assembly stubs)
void irq_handler(struct interrupt_frame* frame) {
// Send EOI to PIC if IRQ >= 8
if (frame->int_no >= 40) {
// Send EOI to slave PIC
__asm__ __volatile__("outb %%al, $0xA0" : : "a"(0x20));
}
// Send EOI to master PIC
__asm__ __volatile__("outb %%al, $0x20" : : "a"(0x20));

// Handle the IRQ
console_puts("[IDT] IRQ occurred: ");
uint8_t irq = frame->int_no - 32;
if (irq < 10) {
console_putc('0' + irq);
} else {
console_putc('0' + (irq / 10));
console_putc('0' + (irq % 10));
}
console_puts("\n");
}

// Set an IDT gate
void idt_set_gate(uint8_t num, uint32_t base, uint16_t selector, uint8_t flags) {
idt[num].base_low = base & 0xFFFF;
idt[num].base_high = (base >> 16) & 0xFFFF;
idt[num].selector = selector;
idt[num].zero = 0;
idt[num].flags = flags;
}

void idt_init(void) {
// Set up IDTR
idtp.limit = sizeof(idt) - 1;
idtp.base = (uint32_t)&idt;

// Initialize all IDT entries to zero
for (int i = 0; i < 256; i++) {
idt_set_gate(i, 0, 0x08, 0);
}

// Set up exception handlers (ISR 0-31)
// Flags: 0x8E = 10001110
// Bit 7: Present (1)
// Bits 6-5: DPL = 00 (ring 0)
// Bit 4: Storage segment = 0 (interrupt gate)
// Bits 3-0: Type = 1110 (32-bit interrupt gate)
idt_set_gate(0, (uint32_t)isr0, 0x08, 0x8E);
idt_set_gate(1, (uint32_t)isr1, 0x08, 0x8E);
idt_set_gate(2, (uint32_t)isr2, 0x08, 0x8E);
idt_set_gate(3, (uint32_t)isr3, 0x08, 0x8E);
idt_set_gate(4, (uint32_t)isr4, 0x08, 0x8E);
idt_set_gate(5, (uint32_t)isr5, 0x08, 0x8E);
idt_set_gate(6, (uint32_t)isr6, 0x08, 0x8E);
idt_set_gate(7, (uint32_t)isr7, 0x08, 0x8E);
idt_set_gate(8, (uint32_t)isr8, 0x08, 0x8E);
idt_set_gate(9, (uint32_t)isr9, 0x08, 0x8E);
idt_set_gate(10, (uint32_t)isr10, 0x08, 0x8E);
idt_set_gate(11, (uint32_t)isr11, 0x08, 0x8E);
idt_set_gate(12, (uint32_t)isr12, 0x08, 0x8E);
idt_set_gate(13, (uint32_t)isr13, 0x08, 0x8E);
idt_set_gate(14, (uint32_t)isr14, 0x08, 0x8E);
idt_set_gate(15, (uint32_t)isr15, 0x08, 0x8E);
idt_set_gate(16, (uint32_t)isr16, 0x08, 0x8E);
idt_set_gate(17, (uint32_t)isr17, 0x08, 0x8E);
idt_set_gate(18, (uint32_t)isr18, 0x08, 0x8E);
idt_set_gate(19, (uint32_t)isr19, 0x08, 0x8E);
idt_set_gate(20, (uint32_t)isr20, 0x08, 0x8E);
idt_set_gate(21, (uint32_t)isr21, 0x08, 0x8E);
idt_set_gate(22, (uint32_t)isr22, 0x08, 0x8E);
idt_set_gate(23, (uint32_t)isr23, 0x08, 0x8E);
idt_set_gate(24, (uint32_t)isr24, 0x08, 0x8E);
idt_set_gate(25, (uint32_t)isr25, 0x08, 0x8E);
idt_set_gate(26, (uint32_t)isr26, 0x08, 0x8E);
idt_set_gate(27, (uint32_t)isr27, 0x08, 0x8E);
idt_set_gate(28, (uint32_t)isr28, 0x08, 0x8E);
idt_set_gate(29, (uint32_t)isr29, 0x08, 0x8E);
idt_set_gate(30, (uint32_t)isr30, 0x08, 0x8E);
idt_set_gate(31, (uint32_t)isr31, 0x08, 0x8E);

// Set up IRQ handlers (IRQ 0-15 map to ISR 32-47)
idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E);
idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E);
idt_set_gate(34, (uint32_t)irq2, 0x08, 0x8E);
idt_set_gate(35, (uint32_t)irq3, 0x08, 0x8E);
idt_set_gate(36, (uint32_t)irq4, 0x08, 0x8E);
idt_set_gate(37, (uint32_t)irq5, 0x08, 0x8E);
idt_set_gate(38, (uint32_t)irq6, 0x08, 0x8E);
idt_set_gate(39, (uint32_t)irq7, 0x08, 0x8E);
idt_set_gate(40, (uint32_t)irq8, 0x08, 0x8E);
idt_set_gate(41, (uint32_t)irq9, 0x08, 0x8E);
idt_set_gate(42, (uint32_t)irq10, 0x08, 0x8E);
idt_set_gate(43, (uint32_t)irq11, 0x08, 0x8E);
idt_set_gate(44, (uint32_t)irq12, 0x08, 0x8E);
idt_set_gate(45, (uint32_t)irq13, 0x08, 0x8E);
idt_set_gate(46, (uint32_t)irq14, 0x08, 0x8E);
idt_set_gate(47, (uint32_t)irq15, 0x08, 0x8E);

// Load IDTR
idt_flush((uint32_t)&idtp);

console_puts("[IDT] Initialized\n");
}

9 changes: 9 additions & 0 deletions src/arch/x86/idt_flush.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[bits 32]

; IDT flush function
global idt_flush
idt_flush:
mov eax, [esp + 4] ; param idt_ptr address
lidt [eax] ; load into IDTR
ret

Loading