Add CMake, Ninja, and Conan support with kernel modules (GDT, interrupts)

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-28 20:03:46 +00:00
parent 025e58f391
commit 5c8cf64442
17 changed files with 1170 additions and 6 deletions

79
kernel/CMakeLists.txt Normal file
View File

@@ -0,0 +1,79 @@
# MetalOS Kernel CMakeLists
# Include directories
include_directories(include)
# Source files
set(KERNEL_C_SOURCES
src/main.c
src/gdt.c
src/interrupts.c
)
set(KERNEL_ASM_SOURCES
src/gdt_flush.asm
src/interrupts_asm.asm
)
# Kernel-specific C compiler flags
set(KERNEL_CFLAGS
-mcmodel=large
-O2
)
# Create kernel C object files
add_library(kernel_c_objs OBJECT
${KERNEL_C_SOURCES}
)
target_compile_options(kernel_c_objs PRIVATE ${KERNEL_CFLAGS})
target_include_directories(kernel_c_objs PRIVATE include)
# Create kernel ASM object files separately
# We need to avoid CMake adding C flags to NASM
foreach(asm_file ${KERNEL_ASM_SOURCES})
get_filename_component(asm_name ${asm_file} NAME_WE)
set(asm_obj ${CMAKE_CURRENT_BINARY_DIR}/${asm_name}.o)
add_custom_command(
OUTPUT ${asm_obj}
COMMAND ${CMAKE_ASM_NASM_COMPILER}
-f elf64
-I ${CMAKE_CURRENT_SOURCE_DIR}/include
-o ${asm_obj}
${CMAKE_CURRENT_SOURCE_DIR}/${asm_file}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${asm_file}
COMMENT "Assembling ${asm_file}"
VERBATIM
)
list(APPEND KERNEL_ASM_OBJS ${asm_obj})
endforeach()
# Custom target for ASM objects
add_custom_target(kernel_asm_objs
DEPENDS ${KERNEL_ASM_OBJS}
)
# Link kernel binary
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/metalos.bin
COMMAND ${CMAKE_LINKER}
-nostdlib
-T ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld
$<TARGET_OBJECTS:kernel_c_objs>
${KERNEL_ASM_OBJS}
-o ${CMAKE_CURRENT_BINARY_DIR}/metalos.bin
DEPENDS kernel_c_objs kernel_asm_objs ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld
COMMENT "Linking kernel binary"
COMMAND_EXPAND_LISTS
)
add_custom_target(kernel ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/metalos.bin
)
# Install kernel binary
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/metalos.bin
DESTINATION bin
)

View File

@@ -0,0 +1,25 @@
#ifndef METALOS_KERNEL_GDT_H
#define METALOS_KERNEL_GDT_H
#include <stdint.h>
// GDT Entry structure
typedef struct {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
} __attribute__((packed)) gdt_entry_t;
// GDT Pointer structure
typedef struct {
uint16_t limit;
uint64_t base;
} __attribute__((packed)) gdt_ptr_t;
// Initialize the Global Descriptor Table
void gdt_init(void);
#endif // METALOS_KERNEL_GDT_H

View File

@@ -0,0 +1,37 @@
#ifndef METALOS_KERNEL_INTERRUPTS_H
#define METALOS_KERNEL_INTERRUPTS_H
#include <stdint.h>
// IDT Entry structure
typedef struct {
uint16_t offset_low; // Offset bits 0-15
uint16_t selector; // Code segment selector
uint8_t ist; // Interrupt Stack Table offset
uint8_t type_attr; // Type and attributes
uint16_t offset_mid; // Offset bits 16-31
uint32_t offset_high; // Offset bits 32-63
uint32_t zero; // Reserved
} __attribute__((packed)) idt_entry_t;
// IDT Pointer structure
typedef struct {
uint16_t limit;
uint64_t base;
} __attribute__((packed)) idt_ptr_t;
// CPU registers state (for interrupt handlers)
typedef struct {
uint64_t r15, r14, r13, r12, r11, r10, r9, r8;
uint64_t rbp, rdi, rsi, rdx, rcx, rbx, rax;
uint64_t int_no, err_code;
uint64_t rip, cs, rflags, rsp, ss;
} __attribute__((packed)) registers_t;
// Initialize Interrupt Descriptor Table
void idt_init(void);
// Generic interrupt handler (called from assembly)
void interrupt_handler(registers_t* regs);
#endif // METALOS_KERNEL_INTERRUPTS_H

56
kernel/src/gdt.c Normal file
View File

@@ -0,0 +1,56 @@
/*
* MetalOS Kernel - Global Descriptor Table (GDT)
*
* Minimal GDT setup for x86_64 long mode
* Only what's needed for our single-app OS
*/
#include "kernel/gdt.h"
// GDT entries (minimal for x86_64)
// In long mode, most segmentation is ignored, but we still need a valid GDT
static gdt_entry_t gdt[5];
static gdt_ptr_t gdt_ptr;
// Set a GDT entry
static void gdt_set_gate(int num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran) {
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 |= gran & 0xF0;
gdt[num].access = access;
}
// Load GDT (assembly)
extern void gdt_flush(uint64_t);
// Initialize GDT
void gdt_init(void) {
gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1;
gdt_ptr.base = (uint64_t)&gdt;
// Null descriptor
gdt_set_gate(0, 0, 0, 0, 0);
// Kernel code segment (64-bit)
// Access: Present, Ring 0, Code, Executable, Readable
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xA0);
// Kernel data segment (64-bit)
// Access: Present, Ring 0, Data, Writable
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xC0);
// User code segment (64-bit)
// Access: Present, Ring 3, Code, Executable, Readable
gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xA0);
// User data segment (64-bit)
// Access: Present, Ring 3, Data, Writable
gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xC0);
// Flush GDT
gdt_flush((uint64_t)&gdt_ptr);
}

26
kernel/src/gdt_flush.asm Normal file
View File

@@ -0,0 +1,26 @@
; GDT flush assembly routine
; Load new GDT and update segment registers
global gdt_flush
extern gdt_ptr
section .text
bits 64
gdt_flush:
lgdt [rdi] ; Load GDT pointer (first argument in rdi)
; Reload segments
mov ax, 0x10 ; Kernel data segment offset
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; Far return to reload CS
pop rdi ; Get return address
mov rax, 0x08 ; Kernel code segment offset
push rax
push rdi
retfq ; Far return

160
kernel/src/interrupts.c Normal file
View File

@@ -0,0 +1,160 @@
/*
* MetalOS Kernel - Interrupt Handling
*
* Minimal IDT and interrupt handlers
* Only essential interrupts for QT6 app
*/
#include "kernel/interrupts.h"
// IDT entries (256 interrupts in x86_64)
static idt_entry_t idt[256];
static idt_ptr_t idt_ptr;
// External ISR handlers (defined in interrupts_asm.asm)
extern void isr0(void);
extern void isr1(void);
extern void isr2(void);
extern void isr3(void);
extern void isr4(void);
extern void isr5(void);
extern void isr6(void);
extern void isr7(void);
extern void isr8(void);
extern void isr9(void);
extern void isr10(void);
extern void isr11(void);
extern void isr12(void);
extern void isr13(void);
extern void isr14(void);
extern void isr15(void);
extern void isr16(void);
extern void isr17(void);
extern void isr18(void);
extern void isr19(void);
extern void isr20(void);
extern void isr21(void);
extern void isr22(void);
extern void isr23(void);
extern void isr24(void);
extern void isr25(void);
extern void isr26(void);
extern void isr27(void);
extern void isr28(void);
extern void isr29(void);
extern void isr30(void);
extern void isr31(void);
// IRQ handlers
extern void irq0(void);
extern void irq1(void);
// Set an IDT entry
static void idt_set_gate(uint8_t num, uint64_t handler, uint16_t selector, uint8_t flags) {
idt[num].offset_low = handler & 0xFFFF;
idt[num].offset_mid = (handler >> 16) & 0xFFFF;
idt[num].offset_high = (handler >> 32) & 0xFFFFFFFF;
idt[num].selector = selector;
idt[num].ist = 0;
idt[num].type_attr = flags;
idt[num].zero = 0;
}
// Remap PIC (Programmable Interrupt Controller)
static void pic_remap(void) {
// ICW1: Initialize PIC
__asm__ volatile("outb %0, $0x20" : : "a"((uint8_t)0x11)); // Master PIC
__asm__ volatile("outb %0, $0xA0" : : "a"((uint8_t)0x11)); // Slave PIC
// ICW2: Set interrupt vector offsets
__asm__ volatile("outb %0, $0x21" : : "a"((uint8_t)0x20)); // Master offset to 0x20
__asm__ volatile("outb %0, $0xA1" : : "a"((uint8_t)0x28)); // Slave offset to 0x28
// ICW3: Set up cascade
__asm__ volatile("outb %0, $0x21" : : "a"((uint8_t)0x04)); // Tell master about slave
__asm__ volatile("outb %0, $0xA1" : : "a"((uint8_t)0x02)); // Tell slave its cascade
// ICW4: Set mode
__asm__ volatile("outb %0, $0x21" : : "a"((uint8_t)0x01));
__asm__ volatile("outb %0, $0xA1" : : "a"((uint8_t)0x01));
// Mask all interrupts initially
__asm__ volatile("outb %0, $0x21" : : "a"((uint8_t)0xFF));
__asm__ volatile("outb %0, $0xA1" : : "a"((uint8_t)0xFF));
}
// Initialize IDT
void idt_init(void) {
idt_ptr.limit = (sizeof(idt_entry_t) * 256) - 1;
idt_ptr.base = (uint64_t)&idt;
// Clear IDT
for (int i = 0; i < 256; i++) {
idt_set_gate(i, 0, 0, 0);
}
// Install exception handlers (ISRs 0-31)
idt_set_gate(0, (uint64_t)isr0, 0x08, 0x8E);
idt_set_gate(1, (uint64_t)isr1, 0x08, 0x8E);
idt_set_gate(2, (uint64_t)isr2, 0x08, 0x8E);
idt_set_gate(3, (uint64_t)isr3, 0x08, 0x8E);
idt_set_gate(4, (uint64_t)isr4, 0x08, 0x8E);
idt_set_gate(5, (uint64_t)isr5, 0x08, 0x8E);
idt_set_gate(6, (uint64_t)isr6, 0x08, 0x8E);
idt_set_gate(7, (uint64_t)isr7, 0x08, 0x8E);
idt_set_gate(8, (uint64_t)isr8, 0x08, 0x8E);
idt_set_gate(9, (uint64_t)isr9, 0x08, 0x8E);
idt_set_gate(10, (uint64_t)isr10, 0x08, 0x8E);
idt_set_gate(11, (uint64_t)isr11, 0x08, 0x8E);
idt_set_gate(12, (uint64_t)isr12, 0x08, 0x8E);
idt_set_gate(13, (uint64_t)isr13, 0x08, 0x8E);
idt_set_gate(14, (uint64_t)isr14, 0x08, 0x8E);
idt_set_gate(15, (uint64_t)isr15, 0x08, 0x8E);
idt_set_gate(16, (uint64_t)isr16, 0x08, 0x8E);
idt_set_gate(17, (uint64_t)isr17, 0x08, 0x8E);
idt_set_gate(18, (uint64_t)isr18, 0x08, 0x8E);
idt_set_gate(19, (uint64_t)isr19, 0x08, 0x8E);
idt_set_gate(20, (uint64_t)isr20, 0x08, 0x8E);
idt_set_gate(21, (uint64_t)isr21, 0x08, 0x8E);
idt_set_gate(22, (uint64_t)isr22, 0x08, 0x8E);
idt_set_gate(23, (uint64_t)isr23, 0x08, 0x8E);
idt_set_gate(24, (uint64_t)isr24, 0x08, 0x8E);
idt_set_gate(25, (uint64_t)isr25, 0x08, 0x8E);
idt_set_gate(26, (uint64_t)isr26, 0x08, 0x8E);
idt_set_gate(27, (uint64_t)isr27, 0x08, 0x8E);
idt_set_gate(28, (uint64_t)isr28, 0x08, 0x8E);
idt_set_gate(29, (uint64_t)isr29, 0x08, 0x8E);
idt_set_gate(30, (uint64_t)isr30, 0x08, 0x8E);
idt_set_gate(31, (uint64_t)isr31, 0x08, 0x8E);
// Remap PIC
pic_remap();
// Install IRQ handlers (IRQs 0-15 mapped to 32-47)
idt_set_gate(32, (uint64_t)irq0, 0x08, 0x8E); // Timer
idt_set_gate(33, (uint64_t)irq1, 0x08, 0x8E); // Keyboard
// Load IDT
__asm__ volatile("lidt %0" : : "m"(idt_ptr));
// Enable interrupts
__asm__ volatile("sti");
}
// Generic interrupt handler
void interrupt_handler(registers_t* regs) {
// Handle interrupt based on interrupt number
(void)regs; // Suppress unused warning for now
// TODO: Dispatch to specific handlers based on regs->int_no
// Send EOI (End of Interrupt) to PIC if this was an IRQ
if (regs->int_no >= 32 && regs->int_no < 48) {
if (regs->int_no >= 40) {
// Slave PIC
__asm__ volatile("outb %0, $0xA0" : : "a"((uint8_t)0x20));
}
// Master PIC
__asm__ volatile("outb %0, $0x20" : : "a"((uint8_t)0x20));
}
}

View File

@@ -0,0 +1,120 @@
; Interrupt Service Routines (ISRs) for x86_64
; Assembly stubs that save state and call C handler
global isr0, isr1, isr2, isr3, isr4, isr5, isr6, isr7
global isr8, isr9, isr10, isr11, isr12, isr13, isr14, isr15
global isr16, isr17, isr18, isr19, isr20, isr21, isr22, isr23
global isr24, isr25, isr26, isr27, isr28, isr29, isr30, isr31
global irq0, irq1
extern interrupt_handler
section .text
bits 64
; Macro for ISRs without error code
%macro ISR_NOERRCODE 1
isr%1:
push qword 0 ; Dummy error code
push qword %1 ; Interrupt number
jmp isr_common_stub
%endmacro
; Macro for ISRs with error code
%macro ISR_ERRCODE 1
isr%1:
push qword %1 ; Interrupt number
jmp isr_common_stub
%endmacro
; Macro for IRQs
%macro IRQ 2
irq%1:
push qword 0 ; Dummy error code
push qword %2 ; Interrupt number
jmp isr_common_stub
%endmacro
; CPU Exceptions (0-31)
ISR_NOERRCODE 0 ; Divide by zero
ISR_NOERRCODE 1 ; Debug
ISR_NOERRCODE 2 ; Non-maskable interrupt
ISR_NOERRCODE 3 ; Breakpoint
ISR_NOERRCODE 4 ; Overflow
ISR_NOERRCODE 5 ; Bound range exceeded
ISR_NOERRCODE 6 ; Invalid opcode
ISR_NOERRCODE 7 ; Device not available
ISR_ERRCODE 8 ; Double fault
ISR_NOERRCODE 9 ; Coprocessor segment overrun
ISR_ERRCODE 10 ; Invalid TSS
ISR_ERRCODE 11 ; Segment not present
ISR_ERRCODE 12 ; Stack-segment fault
ISR_ERRCODE 13 ; General protection fault
ISR_ERRCODE 14 ; Page fault
ISR_NOERRCODE 15 ; Reserved
ISR_NOERRCODE 16 ; x87 floating-point exception
ISR_ERRCODE 17 ; Alignment check
ISR_NOERRCODE 18 ; Machine check
ISR_NOERRCODE 19 ; SIMD floating-point exception
ISR_NOERRCODE 20 ; Virtualization exception
ISR_NOERRCODE 21 ; Reserved
ISR_NOERRCODE 22 ; Reserved
ISR_NOERRCODE 23 ; Reserved
ISR_NOERRCODE 24 ; Reserved
ISR_NOERRCODE 25 ; Reserved
ISR_NOERRCODE 26 ; Reserved
ISR_NOERRCODE 27 ; Reserved
ISR_NOERRCODE 28 ; Reserved
ISR_NOERRCODE 29 ; Reserved
ISR_ERRCODE 30 ; Security exception
ISR_NOERRCODE 31 ; Reserved
; IRQs (32-47)
IRQ 0, 32 ; Timer
IRQ 1, 33 ; Keyboard
; Common ISR stub - saves state and calls C handler
isr_common_stub:
; Save all general purpose registers
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
push rbp
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
; Call C handler with pointer to register state
mov rdi, rsp
call interrupt_handler
; Restore all registers
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
; Clean up error code and interrupt number
add rsp, 16
; Return from interrupt
iretq

View File

@@ -7,6 +7,8 @@
*/
#include "kernel/kernel.h"
#include "kernel/gdt.h"
#include "kernel/interrupts.h"
/*
* Kernel main entry point
@@ -19,13 +21,13 @@ void kernel_main(BootInfo* boot_info) {
// Suppress unused parameter warning
(void)boot_info;
// TODO: Set up minimal page tables (identity mapped or simple offset)
// Initialize GDT (Global Descriptor Table)
gdt_init();
// TODO: Set up IDT with only interrupts we need:
// - Keyboard/mouse (USB or PS/2)
// - Timer (for QT event loop)
// - GPU (if needed)
// That's it! Maybe 5 interrupt handlers total.
// Initialize IDT (Interrupt Descriptor Table)
idt_init();
// TODO: Set up minimal page tables (identity mapped or simple offset)
// TODO: Simple memory allocator (bump allocator is fine)