Ring 3 multitasking

This commit is contained in:
boreddevnl 2026-02-25 19:09:32 +01:00
parent cc950974e8
commit ca997072ce
49 changed files with 70045 additions and 41 deletions

BIN
.DS_Store vendored

Binary file not shown.

View file

@ -70,6 +70,16 @@ $(BUILD_DIR)/cli_apps/%.o: $(SRC_DIR)/cli_apps/%.c | $(BUILD_DIR) limine-setup
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.asm | $(BUILD_DIR) $(BUILD_DIR)/%.o: $(SRC_DIR)/%.asm | $(BUILD_DIR)
$(NASM) $(NASMFLAGS) $< -o $@ $(NASM) $(NASMFLAGS) $< -o $@
# Assemble test files specifically if they get missed
$(BUILD_DIR)/test_syscall.o: $(SRC_DIR)/test_syscall.asm | $(BUILD_DIR)
$(NASM) $(NASMFLAGS) $< -o $@
$(BUILD_DIR)/user_test.o: $(SRC_DIR)/user_test.asm | $(BUILD_DIR)
$(NASM) $(NASMFLAGS) $< -o $@
$(BUILD_DIR)/process_asm.o: $(SRC_DIR)/process_asm.asm | $(BUILD_DIR)
$(NASM) $(NASMFLAGS) $< -o $@
# Link Kernel # Link Kernel
$(KERNEL_ELF): $(OBJ_FILES) $(KERNEL_ELF): $(OBJ_FILES)
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES) $(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
@ -116,8 +126,9 @@ clean:
rm -rf $(BUILD_DIR) $(ISO_DIR) $(ISO_IMAGE) rm -rf $(BUILD_DIR) $(ISO_DIR) $(ISO_IMAGE)
run: $(ISO_IMAGE) run: $(ISO_IMAGE)
qemu-system-x86_64 -m 2G -serial stdio -cdrom $(ISO_IMAGE) -boot d \ qemu-system-x86_64 -m 2G -serial stdio -cdrom $< -boot d \
-audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \ -audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \
-netdev user,id=net0,hostfwd=udp::12345-:12345 -device e1000,netdev=net0 \ -netdev user,id=net0,hostfwd=udp::12345-:12345 -device e1000,netdev=net0 \
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \ -vga std -global VGA.xres=1920 -global VGA.yres=1080 \
-drive file=disk.img,format=raw -drive file=disk.img,format=raw \
-no-reboot -d int -D qemu-debug.log

1
addr2line.log Normal file
View file

@ -0,0 +1 @@
??:0

50772
boredos.dump Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

18286
qemu-debug.log Normal file

File diff suppressed because it is too large Load diff

105
src/kernel/gdt.c Normal file
View file

@ -0,0 +1,105 @@
#include "gdt.h"
#include <stdint.h>
#include <stddef.h>
#include "memory_manager.h"
static void *gdt_memset(void *s, int c, size_t n) {
uint8_t *p = (uint8_t *)s;
while (n--) {
*p++ = (uint8_t)c;
}
return s;
}
#define GDT_ENTRIES 7
struct gdt_entry gdt[GDT_ENTRIES];
struct gdt_ptr gdtr;
struct tss_entry tss;
extern void gdt_flush(uint64_t);
extern void tss_flush(void);
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;
}
static void gdt_set_tss_gate(int num, uint64_t base, uint32_t limit, uint8_t access, uint8_t gran) {
// A TSS entry in x86_64 is 16 bytes (takes up 2 adjacent GDT entries)
struct {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
uint32_t base_upper;
uint32_t reserved;
} __attribute__((packed)) *tss_desc = (void*)&gdt[num];
tss_desc->base_low = (base & 0xFFFF);
tss_desc->base_middle = (base >> 16) & 0xFF;
tss_desc->base_high = (base >> 24) & 0xFF;
tss_desc->base_upper = (base >> 32);
tss_desc->limit_low = (limit & 0xFFFF);
tss_desc->granularity = ((limit >> 16) & 0x0F);
tss_desc->granularity |= (gran & 0xF0);
tss_desc->access = access;
tss_desc->reserved = 0;
}
void tss_set_stack(uint64_t kernel_stack) {
tss.rsp0 = kernel_stack;
}
void gdt_init(void) {
gdtr.limit = (sizeof(struct gdt_entry) * GDT_ENTRIES) - 1;
gdtr.base = (uint64_t)&gdt;
// NULL segment
gdt_set_gate(0, 0, 0, 0, 0);
// Kernel Code segment (Ring 0, 64-bit)
// 0x9A: Present(1), Ring(0), System(1), Executable(1), DirConforming(0), Readable(1), Accessed(0)
// 0xAF: Long Mode (64-bit) (L=1, DB=0)
gdt_set_gate(1, 0, 0, 0x9A, 0xAF);
// Kernel Data segment (Ring 0)
// 0x92: Present(1), Ring(0), System(1), Executable(0), DirExpandDown(0), Writable(1), Accessed(0)
gdt_set_gate(2, 0, 0, 0x92, 0xAF);
// User Data segment (Ring 3)
// 0xF2: Present(1), Ring(3), System(1), Executable(0), DirExpandDown(0), Writable(1), Accessed(0)
gdt_set_gate(3, 0, 0, 0xF2, 0xAF);
// User Code segment (Ring 3, 64-bit)
// 0xFA: Present(1), Ring(3), System(1), Executable(1), DirConforming(0), Readable(1), Accessed(0)
// 0xAF: Long Mode (64-bit)
gdt_set_gate(4, 0, 0, 0xFA, 0xAF);
// TSS segment (takes entries 5 and 6 technically because it's 16 bytes)
gdt_memset(&tss, 0, sizeof(struct tss_entry));
tss.iopb_offset = sizeof(struct tss_entry);
// Allocate a default Ring 0 interrupt stack in case an interrupt fires early or
// the scheduler hasn't set one up yet for a task.
void* initial_tss_stack = kmalloc_aligned(4096, 4096);
if (initial_tss_stack) {
tss.rsp0 = (uint64_t)initial_tss_stack + 4096;
}
gdt_set_tss_gate(5, (uint64_t)&tss, sizeof(struct tss_entry) - 1, 0x89, 0x00);
gdt_flush((uint64_t)&gdtr);
tss_flush();
}

48
src/kernel/gdt.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef GDT_H
#define GDT_H
#include <stdint.h>
// Segment Selectors
#define KERNEL_CS 0x08
#define KERNEL_DS 0x10
#define USER_CS 0x1B // 0x18 | 3 (RPL 3)
#define USER_DS 0x23 // 0x20 | 3 (RPL 3)
#define TSS_SEG 0x28
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
} __attribute__((packed));
struct tss_entry {
uint32_t reserved0;
uint64_t rsp0;
uint64_t rsp1;
uint64_t rsp2;
uint64_t reserved1;
uint64_t ist1;
uint64_t ist2;
uint64_t ist3;
uint64_t ist4;
uint64_t ist5;
uint64_t ist6;
uint64_t ist7;
uint64_t reserved2;
uint16_t reserved3;
uint16_t iopb_offset;
} __attribute__((packed));
struct gdt_ptr {
uint16_t limit;
uint64_t base;
} __attribute__((packed));
void gdt_init(void);
void tss_set_stack(uint64_t kernel_stack);
#endif

28
src/kernel/gdt_asm.asm Normal file
View file

@ -0,0 +1,28 @@
global gdt_flush
global tss_flush
section .text
gdt_flush:
lgdt [rdi] ; Load GDT from the pointer passed in RDI
mov ax, 0x10 ; 0x10 is our offset in the GDT to our data segment
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; Far jump to update CS
push 0x08 ; 0x08 is our offset to the code segment
lea rax, [rel .flush]
push rax
retfq
.flush:
ret
tss_flush:
mov ax, 0x28 ; 0x28 is our offset in the GDT to the TSS
ltr ax
ret

View file

@ -1,6 +1,34 @@
#include "idt.h" #include "idt.h"
#include "io.h" #include "io.h"
extern void serial_write(const char *str);
// Simple hex printer for debugging exceptions
static void print_hex(uint64_t val) {
const char* digits = "0123456789ABCDEF";
char buf[17];
buf[16] = '\0';
for (int i = 15; i >= 0; i--) {
buf[i] = digits[val & 0xF];
val >>= 4;
}
serial_write("0x");
serial_write(buf);
}
void exception_handler_c(uint64_t vector, uint64_t err_code, uint64_t rip, uint64_t cr2) {
serial_write("\n*** EXCEPTION ***\nVector: ");
print_hex(vector);
serial_write("\nError Code: ");
print_hex(err_code);
serial_write("\nRIP: ");
print_hex(rip);
serial_write("\nCR2: ");
print_hex(cr2);
serial_write("\nCPU HALTED.\n");
while(1) { asm volatile("cli; hlt"); }
}
#define IDT_ENTRIES 256 #define IDT_ENTRIES 256
struct idt_entry { struct idt_entry {
@ -89,11 +117,18 @@ void idt_register_interrupts(void) {
idt_set_gate(32, isr0_wrapper, cs, 0x8E); // Timer (IRQ 0) idt_set_gate(32, isr0_wrapper, cs, 0x8E); // Timer (IRQ 0)
idt_set_gate(33, isr1_wrapper, cs, 0x8E); // Keyboard (IRQ 1) idt_set_gate(33, isr1_wrapper, cs, 0x8E); // Keyboard (IRQ 1)
idt_set_gate(44, isr12_wrapper, cs, 0x8E); // Mouse (IRQ 12) idt_set_gate(44, isr12_wrapper, cs, 0x8E); // Mouse (IRQ 12)
// Exceptions
extern void isr8_wrapper(void);
extern void isr14_wrapper(void);
idt_set_gate(8, isr8_wrapper, cs, 0x8E); // Double Fault
idt_set_gate(14, isr14_wrapper, cs, 0x8E); // Page Fault
} }
void idt_load(void) { void idt_load(void) {
idtr.base = (uint64_t)&idt; idtr.base = (uint64_t)&idt;
idtr.limit = sizeof(struct idt_entry) * IDT_ENTRIES - 1; idtr.limit = sizeof(struct idt_entry) * IDT_ENTRIES - 1;
asm volatile ("lidt %0" : : "m"(idtr)); asm volatile ("lidt %0" : : "m"(idtr));
asm volatile ("sti"); // Do not sti here! The OS must decide when to enable interrupts
// after all subsystems (WM, PS/2) are initialized!
} }

View file

@ -1,10 +1,13 @@
section .text section .text
global isr0_wrapper global isr0_wrapper
global isr1_wrapper global isr1_wrapper
global isr8_wrapper
global isr12_wrapper global isr12_wrapper
global isr14_wrapper
extern timer_handler extern timer_handler
extern keyboard_handler extern keyboard_handler
extern mouse_handler extern mouse_handler
extern exception_handler_c
; Helper to send EOI (End of Interrupt) to PIC ; Helper to send EOI (End of Interrupt) to PIC
send_eoi: send_eoi:
@ -16,15 +19,15 @@ send_eoi:
ret ret
%macro ISR_NOERRCODE 1 %macro ISR_NOERRCODE 1
push rdi
push rsi
push rdx
push rcx
push r8
push r9
push rax push rax
push rbx push rbx
push rcx
push rdx
push rbp push rbp
push rdi
push rsi
push r8
push r9
push r10 push r10
push r11 push r11
push r12 push r12
@ -32,23 +35,29 @@ send_eoi:
push r14 push r14
push r15 push r15
; Pass current RSP as 1st argument
mov rdi, rsp
call %1 call %1
; Update RSP with return value (task switch)
mov rsp, rax
pop r15 pop r15
pop r14 pop r14
pop r13 pop r13
pop r12 pop r12
pop r11 pop r11
pop r10 pop r10
pop rbp
pop rbx
pop rax
pop r9 pop r9
pop r8 pop r8
pop rcx
pop rdx
pop rsi pop rsi
pop rdi pop rdi
pop rbp
pop rdx
pop rcx
pop rbx
pop rax
iretq iretq
%endmacro %endmacro
@ -60,3 +69,62 @@ isr1_wrapper:
isr12_wrapper: isr12_wrapper:
ISR_NOERRCODE mouse_handler ISR_NOERRCODE mouse_handler
; Common exception macro
%macro EXCEPTION_ERRCODE 1
isr%1_wrapper:
push %1
jmp exception_common
%endmacro
; Exception 8: Double Fault (has error code)
EXCEPTION_ERRCODE 8
; Exception 14: Page Fault (has error code)
EXCEPTION_ERRCODE 14
exception_common:
; Save registers
push rax
push rcx
push rdx
push rbx
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
; Call C handler: void exception_handler_c(uint64_t vector, uint64_t err_code, uint64_t rip, uint64_t cr2)
; Stack right now: 15 registers (15*8=120 bytes), then vector (8), then err_code (8), then RIP (8), CS (8), RFLAGS (8), RSP (8), SS (8)
mov rdi, [rsp + 120] ; vector
mov rsi, [rsp + 128] ; err_code
mov rdx, [rsp + 136] ; RIP
mov rcx, cr2 ; CR2
call exception_handler_c
; Restore (in case we want to return, but usually we halt)
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbp
pop rbx
pop rdx
pop rcx
pop rax
add rsp, 16 ; drop vector and error code
iretq

View file

@ -3,7 +3,11 @@
#include <stdbool.h> #include <stdbool.h>
#include "limine.h" #include "limine.h"
#include "graphics.h" #include "graphics.h"
#include "gdt.h"
#include "idt.h" #include "idt.h"
#include "paging.h"
#include "syscall.h"
#include "process.h"
#include "ps2.h" #include "ps2.h"
#include "wm.h" #include "wm.h"
#include "io.h" #include "io.h"
@ -47,27 +51,81 @@ static void hcf(void) {
} }
} }
// Simple serial port initialization and output for debugging
static void init_serial() {
outb(0x3F8 + 1, 0x00);
outb(0x3F8 + 3, 0x80);
outb(0x3F8 + 0, 0x03);
outb(0x3F8 + 1, 0x00);
outb(0x3F8 + 3, 0x03);
outb(0x3F8 + 2, 0xC7);
outb(0x3F8 + 4, 0x0B);
}
void serial_write(const char *str) {
while (*str) {
while ((inb(0x3F8 + 5) & 0x20) == 0);
outb(0x3F8, *str++);
}
}
// Kernel Entry Point // Kernel Entry Point
void kmain(void) { void kmain(void) {
platform_init(); init_serial();
// 1. Graphics Init serial_write("\n[DEBUG] Entering kmain...\n");
platform_init();
serial_write("[DEBUG] platform_init OK\n");
// 1. Graphics Init
if (framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1) { if (framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1) {
serial_write("[DEBUG] No framebuffer! Halting.\n");
hcf(); hcf();
} }
struct limine_framebuffer *fb = framebuffer_request.response->framebuffers[0]; struct limine_framebuffer *fb = framebuffer_request.response->framebuffers[0];
graphics_init(fb); graphics_init(fb);
serial_write("[DEBUG] graphics_init OK\n");
// 1.5 GDT & TSS Init
gdt_init();
serial_write("[DEBUG] gdt_init OK\n");
// 1.6 Paging Init
paging_init();
serial_write("[DEBUG] paging_init OK\n");
// 1.7 Syscall Init
syscall_init();
serial_write("[DEBUG] syscall_init OK\n");
// Set up a user page and jump to user space
// 2. Interrupts Init // 2. Interrupts Init
idt_init(); idt_init();
// Register ISRs with correct CS
idt_register_interrupts(); idt_register_interrupts();
// Load IDT and Enable Interrupts
idt_load(); idt_load();
serial_write("[DEBUG] idt_init OK\n");
process_init();
int ENABLE_USER_TEST = 1; // Set to 1 to test User Mode ring 3 jump
if (ENABLE_USER_TEST) {
// Create an isolated PML4 table for this "process"
uint64_t user_pml4_phys = paging_create_user_pml4_phys();
serial_write("[DEBUG] user_pml4 created OK\n");
if (user_pml4_phys) {
// Debug verify we can allocate
void* code_page = kmalloc_aligned(4096, 4096);
if (code_page) {
extern void user_test_function(void);
process_create(user_test_function, true);
serial_write("[DEBUG] User Process 1 Created.\n");
}
}
}
serial_write("[DEBUG] Skipping user mode test, proceeding with normal boot.\n");
// 2.5 Memory Manager Init - Calculate available RAM from Limine // 2.5 Memory Manager Init - Calculate available RAM from Limine
size_t total_usable_memory = 0; size_t total_usable_memory = 0;
if (memmap_request.response != NULL) { if (memmap_request.response != NULL) {
@ -99,6 +157,9 @@ void kmain(void) {
// 4. Window Manager Init (Draws initial desktop) // 4. Window Manager Init (Draws initial desktop)
wm_init(); wm_init();
// Re-enable interrupts since we removed sti from idt_load
asm volatile("sti");
// 5. Main loop - just wait for interrupts // 5. Main loop - just wait for interrupts
// Timer interrupt will drive the redraw system // Timer interrupt will drive the redraw system
while (1) { while (1) {

View file

@ -44,11 +44,22 @@ static uint32_t get_timestamp(void) {
return tick++; return tick++;
} }
// Find free space in memory pool // Find free space in memory pool with alignment
static void* find_free_space(size_t size) { static void* find_free_space_aligned(size_t size, size_t alignment) {
size_t offset = 0; size_t offset = 0;
// Ensure 8-byte minimum alignment for regular malloc if 0 is passed
if (alignment == 0) alignment = 8;
while (offset + size <= memory_pool_size) { while (offset + size <= memory_pool_size) {
// Align offset
if ((uint64_t)((uint8_t*)memory_pool + offset) % alignment != 0) {
size_t diff = alignment - ((uint64_t)((uint8_t*)memory_pool + offset) % alignment);
offset += diff;
}
if (offset + size > memory_pool_size) break;
bool space_free = true; bool space_free = true;
// Check if this range is free // Check if this range is free
@ -63,12 +74,8 @@ static void* find_free_space(size_t size) {
// Check for overlap // Check for overlap
if (check_start < block_end && check_end > block_start) { if (check_start < block_end && check_end > block_start) {
space_free = false; space_free = false;
// Move offset past this block
// Skip past this block offset = (size_t)((uint8_t *)block_end - (uint8_t *)memory_pool);
size_t block_offset = (uintptr_t)block_end - (uintptr_t)memory_pool;
if (block_offset > offset) {
offset = block_offset;
}
break; break;
} }
} }
@ -81,6 +88,10 @@ static void* find_free_space(size_t size) {
return NULL; return NULL;
} }
static void* find_free_space(size_t size) {
return find_free_space_aligned(size, 8);
}
// Calculate fragmentation // Calculate fragmentation
static size_t calculate_fragmentation(void) { static size_t calculate_fragmentation(void) {
if (total_allocated == 0) return 0; if (total_allocated == 0) return 0;
@ -143,7 +154,7 @@ void memory_manager_init(void) {
memory_manager_init_with_size(DEFAULT_POOL_SIZE); memory_manager_init_with_size(DEFAULT_POOL_SIZE);
} }
void* kmalloc(size_t size) { void* kmalloc_aligned(size_t size, size_t alignment) {
if (!initialized) { if (!initialized) {
memory_manager_init(); memory_manager_init();
} }
@ -162,8 +173,8 @@ void* kmalloc(size_t size) {
return NULL; return NULL;
} }
// Find free space // Find free space with alignment
void *ptr = find_free_space(size); void *ptr = find_free_space_aligned(size, alignment);
if (ptr == NULL) { if (ptr == NULL) {
asm volatile("push %0; popfq" : : "r"(rflags)); asm volatile("push %0; popfq" : : "r"(rflags));
return NULL; return NULL;
@ -196,6 +207,10 @@ void* kmalloc(size_t size) {
return ptr; return ptr;
} }
void* kmalloc(size_t size) {
return kmalloc_aligned(size, 8);
}
void kfree(void *ptr) { void kfree(void *ptr) {
if (ptr == NULL || !initialized) { if (ptr == NULL || !initialized) {
return; return;

View file

@ -38,6 +38,7 @@ void memory_manager_init_with_size(size_t pool_size);
// Allocation/Deallocation // Allocation/Deallocation
void* kmalloc(size_t size); void* kmalloc(size_t size);
void* kmalloc_aligned(size_t size, size_t alignment);
void kfree(void *ptr); void kfree(void *ptr);
void* krealloc(void *ptr, size_t new_size); void* krealloc(void *ptr, size_t new_size);

121
src/kernel/paging.c Normal file
View file

@ -0,0 +1,121 @@
#include "paging.h"
#include "memory_manager.h"
#include "platform.h"
#include <stddef.h>
static uint64_t current_pml4_phys = 0;
// Get current CR3 value
static uint64_t read_cr3(void) {
uint64_t cr3;
asm volatile("mov %%cr3, %0" : "=r"(cr3));
return cr3;
}
// Set CR3 value
static void write_cr3(uint64_t cr3) {
asm volatile("mov %0, %%cr3" : : "r"(cr3));
}
// Helper to allocate a page table and clear it
static uint64_t alloc_page_table_phys(void) {
// We allocate a page table in Virtual Memory (HHDM)
void* ptr = kmalloc(PAGE_SIZE * 2);
if (!ptr) return 0;
// Align to PAGE_SIZE virtually
uintptr_t addr = (uintptr_t)ptr;
if (addr % PAGE_SIZE != 0) {
addr = (addr + PAGE_SIZE) & ~(PAGE_SIZE - 1);
}
page_table_t* table = (page_table_t*)addr;
// Clear table
for (int i = 0; i < 512; i++) {
table->entries[i] = 0;
}
// Return the physical address of this table
return v2p((uint64_t)table);
}
void paging_init(void) {
// Limine sets up a highly mapped page table for us.
// CR3 contains the physical address of the PML4.
current_pml4_phys = read_cr3() & PT_ADDR_MASK;
}
uint64_t paging_get_pml4_phys(void) {
return current_pml4_phys;
}
void paging_switch_directory(uint64_t pml4_phys) {
current_pml4_phys = pml4_phys;
write_cr3(pml4_phys);
}
void paging_map_page(uint64_t pml4_phys, uint64_t virtual_addr, uint64_t physical_addr, uint64_t flags) {
if (!pml4_phys) return;
page_table_t* pml4 = (page_table_t*)p2v(pml4_phys);
// Extract indices
uint64_t pml4_index = (virtual_addr >> 39) & 0x1FF;
uint64_t pdpt_index = (virtual_addr >> 30) & 0x1FF;
uint64_t pd_index = (virtual_addr >> 21) & 0x1FF;
uint64_t pt_index = (virtual_addr >> 12) & 0x1FF;
// Check PML4 entry
if (!(pml4->entries[pml4_index] & PT_PRESENT)) {
uint64_t new_table_phys = alloc_page_table_phys();
if (!new_table_phys) return; // Out of memory
pml4->entries[pml4_index] = new_table_phys | PT_PRESENT | PT_RW | PT_USER;
}
// Get PDPT
page_table_t* pdpt = (page_table_t*)p2v(pml4->entries[pml4_index] & PT_ADDR_MASK);
if (!(pdpt->entries[pdpt_index] & PT_PRESENT)) {
uint64_t new_table_phys = alloc_page_table_phys();
if (!new_table_phys) return;
pdpt->entries[pdpt_index] = new_table_phys | PT_PRESENT | PT_RW | PT_USER;
}
// Get PD
page_table_t* pd = (page_table_t*)p2v(pdpt->entries[pdpt_index] & PT_ADDR_MASK);
if (!(pd->entries[pd_index] & PT_PRESENT)) {
uint64_t new_table_phys = alloc_page_table_phys();
if (!new_table_phys) return;
pd->entries[pd_index] = new_table_phys | PT_PRESENT | PT_RW | PT_USER;
}
// Get PT
page_table_t* pt = (page_table_t*)p2v(pd->entries[pd_index] & PT_ADDR_MASK);
// Set entry in PT
pt->entries[pt_index] = (physical_addr & PT_ADDR_MASK) | flags;
// Flush TLB for this address
asm volatile("invlpg (%0)" : : "r"(virtual_addr) : "memory");
}
uint64_t paging_create_user_pml4_phys(void) {
// 1. Allocate a new physical PML4
uint64_t new_pml4_phys = alloc_page_table_phys();
if (!new_pml4_phys) return 0;
page_table_t* new_pml4 = (page_table_t*)p2v(new_pml4_phys);
// 2. Clone the higher-half kernel mappings from the active PML4
// In x86_64, indices 256-511 are the higher half.
uint64_t kernel_pml4_phys = paging_get_pml4_phys();
if (kernel_pml4_phys) {
page_table_t* kernel_pml4 = (page_table_t*)p2v(kernel_pml4_phys);
for (int i = 256; i < 512; i++) {
new_pml4->entries[i] = kernel_pml4->entries[i];
}
}
// The lower half (0-255) is left empty for the user process to use
return new_pml4_phys;
}

42
src/kernel/paging.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef PAGING_H
#define PAGING_H
#include <stdint.h>
#include <stdbool.h>
#define PAGE_SIZE 4096
// Page Table Entry Flags
#define PT_PRESENT (1ull << 0)
#define PT_RW (1ull << 1)
#define PT_USER (1ull << 2)
#define PT_WRITE_THROUGH (1ull << 3)
#define PT_CACHE_DISABLE (1ull << 4)
#define PT_ACCESSED (1ull << 5)
#define PT_DIRTY (1ull << 6)
#define PT_HUGE (1ull << 7)
#define PT_GLOBAL (1ull << 8)
#define PT_NX (1ull << 63)
#define PT_ADDR_MASK 0x000FFFFFFFFFF000ull
typedef struct {
uint64_t entries[512];
} __attribute__((aligned(PAGE_SIZE))) page_table_t;
// Get the current PML4 physical address
uint64_t paging_get_pml4_phys(void);
// Map a physical address to a virtual address
void paging_map_page(uint64_t pml4_phys, uint64_t virtual_addr, uint64_t physical_addr, uint64_t flags);
// Create a new, isolated PML4 for a user process (returns physical address)
uint64_t paging_create_user_pml4_phys(void);
// Switch the active page table (takes physical address)
void paging_switch_directory(uint64_t pml4_phys);
// Initialize paging system (if needed beyond Limine's setup)
void paging_init(void);
#endif // PAGING_H

125
src/kernel/process.c Normal file
View file

@ -0,0 +1,125 @@
#include "process.h"
#include "gdt.h"
#include "idt.h"
#include "paging.h"
#include "io.h"
#include "platform.h"
#include "memory_manager.h"
extern void cmd_write(const char *str);
extern void serial_write(const char *str);
#define MAX_PROCESSES 16
static process_t processes[MAX_PROCESSES];
static int process_count = 0;
static process_t* current_process = NULL;
static uint32_t next_pid = 0;
void process_init(void) {
// Current kernel execution is PID 0
process_t *kernel_proc = &processes[process_count++];
kernel_proc->pid = next_pid++;
kernel_proc->is_user = false;
// We don't have its RSP or PML4 yet, but it's already running.
// The timer interrupt will naturally capture its context on the first tick!
kernel_proc->pml4_phys = paging_get_pml4_phys();
kernel_proc->kernel_stack = 0;
kernel_proc->next = kernel_proc; // Circular linked list
current_process = kernel_proc;
}
void process_create(void* entry_point, bool is_user) {
if (process_count >= MAX_PROCESSES) return;
process_t *new_proc = &processes[process_count++];
new_proc->pid = next_pid++;
new_proc->is_user = is_user;
// 1. Setup Page Table
if (is_user) {
new_proc->pml4_phys = paging_create_user_pml4_phys();
} else {
new_proc->pml4_phys = paging_get_pml4_phys();
}
if (!new_proc->pml4_phys) return;
// 2. Allocate aligned stack
void* stack = kmalloc_aligned(4096, 4096);
void* kernel_stack = kmalloc_aligned(4096, 4096); // Needed for when user interrupts to Ring 0
if (is_user) {
// Map user stack to 0x800000
paging_map_page(new_proc->pml4_phys, 0x800000, v2p((uint64_t)stack), PT_PRESENT | PT_RW | PT_USER);
// Allocate code page aligned and copy code
void* code = kmalloc_aligned(4096, 4096);
for(int i=0; i<128; i++) ((uint8_t*)code)[i] = ((uint8_t*)entry_point)[i];
paging_map_page(new_proc->pml4_phys, 0x400000, v2p((uint64_t)code), PT_PRESENT | PT_RW | PT_USER);
// Build initial stack frame for iretq
// Stack grows down, start at top
uint64_t* stack_ptr = (uint64_t*)((uint64_t)kernel_stack + 4096);
*(--stack_ptr) = 0x1B; // SS (User Data)
*(--stack_ptr) = 0x800000 + 4096; // RSP
*(--stack_ptr) = 0x202; // RFLAGS (IF=1)
*(--stack_ptr) = 0x23; // CS (User Code)
*(--stack_ptr) = 0x400000; // RIP
// Push 15 zeros for general purpose registers (r15 -> rax)
for (int i = 0; i < 15; i++) *(--stack_ptr) = 0;
new_proc->kernel_stack = (uint64_t)kernel_stack + 4096;
new_proc->rsp = (uint64_t)stack_ptr;
} else {
// Kernel thread
uint64_t* stack_ptr = (uint64_t*)((uint64_t)stack + 4096);
*(--stack_ptr) = 0x10; // SS (Kernel Data)
stack_ptr--;
*stack_ptr = (uint64_t)stack_ptr; // RSP
*(--stack_ptr) = 0x202; // RFLAGS
*(--stack_ptr) = 0x08; // CS (Kernel Code)
*(--stack_ptr) = (uint64_t)entry_point; // RIP
for (int i = 0; i < 15; i++) *(--stack_ptr) = 0;
new_proc->kernel_stack = 0;
new_proc->rsp = (uint64_t)stack_ptr;
}
// Add to linked list
new_proc->next = current_process->next;
current_process->next = new_proc;
}
process_t* process_get_current(void) {
return current_process;
}
uint64_t process_schedule(uint64_t current_rsp) {
if (!current_process || !current_process->next || current_process == current_process->next)
return current_rsp;
// serial_write("SCHED\n");
// Save context
current_process->rsp = current_rsp;
// Switch process
current_process = current_process->next;
// Update Kernel Stack for User Mode interrupts
if (current_process->is_user && current_process->kernel_stack) {
tss_set_stack(current_process->kernel_stack);
}
// Switch page table
paging_switch_directory(current_process->pml4_phys);
return current_process->rsp;
}

30
src/kernel/process.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef PROCESS_H
#define PROCESS_H
#include <stdint.h>
#include <stdbool.h>
// Registers saved on the stack by interrupts/exceptions
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;
typedef struct process {
uint32_t pid;
uint64_t rsp; // Current stack pointer representing context
uint64_t pml4_phys; // Physical address of the page table
uint64_t kernel_stack; // Ring 0 stack pointer for user mode switches
bool is_user;
struct process *next;
} process_t;
void process_init(void);
void process_create(void* entry_point, bool is_user);
process_t* process_get_current(void);
uint64_t process_schedule(uint64_t current_rsp);
#endif

View file

@ -0,0 +1,36 @@
global process_jump_usermode
section .text
; void process_jump_usermode(uint64_t entry_point, uint64_t user_stack)
; System V AMD64 ABI:
; RDI = entry_point
; RSI = user_stack
process_jump_usermode:
cli
; Load user data segment (0x23)
mov ax, 0x23
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Build the IRETQ stack frame
; 1. SS (User Data Segment)
push 0x23
; 2. RSP (User Stack)
push rsi
; 3. RFLAGS (Enable Interrupts: IF = 0x200 | Reserved bit 1 = 0x2 -> 0x202)
push 0x202
; 4. CS (User Code Segment)
push 0x1B
; 5. RIP (Entry Point)
push rdi
; Jump to Ring 3!
iretq

View file

@ -8,10 +8,16 @@ extern void serial_print(const char *s);
extern void serial_print_hex(uint64_t n); extern void serial_print_hex(uint64_t n);
// --- Timer Handler --- // --- Timer Handler ---
void timer_handler(void) { uint64_t timer_handler(uint64_t rsp) {
wm_timer_tick(); wm_timer_tick();
network_process_frames(); network_process_frames();
extern uint64_t process_schedule(uint64_t current_rsp);
outb(0x20, 0x20); // EOI to Master PIC outb(0x20, 0x20); // EOI to Master PIC
rsp = process_schedule(rsp);
return rsp;
} }
// --- Keyboard --- // --- Keyboard ---
@ -35,13 +41,13 @@ static char scancode_map_shift[128] = {
0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}; };
void keyboard_handler(void) { uint64_t keyboard_handler(uint64_t rsp) {
uint8_t scancode = inb(0x60); uint8_t scancode = inb(0x60);
if (scancode == 0xE0) { if (scancode == 0xE0) {
extended_scancode = true; extended_scancode = true;
outb(0x20, 0x20); outb(0x20, 0x20);
return; return rsp;
} }
if (scancode == 0x2A || scancode == 0x36) { // Shift Down if (scancode == 0x2A || scancode == 0x36) { // Shift Down
@ -71,6 +77,7 @@ void keyboard_handler(void) {
} }
outb(0x20, 0x20); // EOI outb(0x20, 0x20); // EOI
return rsp;
} }
// --- Mouse --- // --- Mouse ---
@ -128,12 +135,12 @@ void mouse_init(void) {
mouse_read(); mouse_read();
} }
void mouse_handler(void) { uint64_t mouse_handler(uint64_t rsp) {
uint8_t status = inb(0x64); uint8_t status = inb(0x64);
if (!(status & 0x20)) { if (!(status & 0x20)) {
outb(0x20, 0x20); outb(0x20, 0x20);
outb(0xA0, 0x20); outb(0xA0, 0x20);
return; return rsp; // Return rsp here as well
} }
uint8_t b = inb(0x60); uint8_t b = inb(0x60);
@ -162,6 +169,7 @@ void mouse_handler(void) {
outb(0x20, 0x20); outb(0x20, 0x20);
outb(0xA0, 0x20); // Slave EOI outb(0xA0, 0x20); // Slave EOI
return rsp;
} }
void ps2_init(void) { void ps2_init(void) {

View file

@ -1,8 +1,11 @@
#ifndef PS2_H #ifndef PS2_H
#define PS2_H #define PS2_H
#include <stdint.h>
void ps2_init(void); void ps2_init(void);
void keyboard_handler(void); uint64_t timer_handler(uint64_t rsp);
void mouse_handler(void); uint64_t keyboard_handler(uint64_t rsp);
uint64_t mouse_handler(uint64_t rsp);
#endif #endif

View file

@ -33,9 +33,6 @@ void rtc_get_datetime(int *year, int *month, int *day, int *hour, int *minute, i
*month = get_rtc_register(0x08); *month = get_rtc_register(0x08);
*year = get_rtc_register(0x09); *year = get_rtc_register(0x09);
// Note: Century register is not standard, but often 0x32 if ACPI table says so.
// For now, assume 20xx
do { do {
last_second = *second; last_second = *second;
last_minute = *minute; last_minute = *minute;

55
src/kernel/syscall.c Normal file
View file

@ -0,0 +1,55 @@
#include "syscall.h"
#include "gdt.h"
#include "memory_manager.h"
// Read MSR
static inline uint64_t rdmsr(uint32_t msr) {
uint32_t low, high;
asm volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(msr));
return ((uint64_t)high << 32) | low;
}
// Write MSR
static inline void wrmsr(uint32_t msr, uint64_t value) {
uint32_t low = value & 0xFFFFFFFF;
uint32_t high = value >> 32;
asm volatile("wrmsr" : : "c"(msr), "a"(low), "d"(high));
}
// Implemented in assembly
extern void syscall_entry(void);
extern uint64_t kernel_syscall_stack;
void syscall_init(void) {
void* stack = kmalloc(16384);
kernel_syscall_stack = (uint64_t)stack + 16384;
uint64_t efer = rdmsr(MSR_EFER);
efer |= 1; // SCE bit is bit 0
wrmsr(MSR_EFER, efer);
// STAR MSR setup:
// Bits 32-47: Syscall CS and SS. CS = STAR[47:32], SS = STAR[47:32] + 8 (Kernel CS = 0x08)
// Bits 48-63: Sysret CS and SS. CS = STAR[63:48] + 16, SS = STAR[63:48] + 8.
// User Data must be Base+8, User Code must be Base+16.
// Our GDT: User Data = 0x1B, User Code = 0x23.
// Therefore Base = 0x13.
uint64_t star = ((uint64_t)0x08 << 32) | ((uint64_t)0x13 << 48);
wrmsr(MSR_STAR, star);
wrmsr(MSR_LSTAR, (uint64_t)syscall_entry);
// Mask Interrupts on SYSCALL (Clear IF bit in RFLAGS during syscall execution)
wrmsr(MSR_FMASK, 0x200);
}
void syscall_handler_c(uint64_t syscall_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5) {
extern void cmd_write(const char *str);
extern void serial_write(const char *str);
if (syscall_num == 1) { // SYS_WRITE
// arg2 is the buffer based on our user_test logic
cmd_write((const char*)arg2);
serial_write((const char*)arg2);
}
}

20
src/kernel/syscall.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef SYSCALL_H
#define SYSCALL_H
#include <stdint.h>
// MSRs used for syscalls in x86_64
#define MSR_EFER 0xC0000080
#define MSR_STAR 0xC0000081
#define MSR_LSTAR 0xC0000082
#define MSR_COMPAT_STAR 0xC0000083
#define MSR_FMASK 0xC0000084
// Syscall Numbers
#define SYS_WRITE 1
#define SYS_EXIT 60
void syscall_init(void);
void syscall_handler_c(uint64_t syscall_num, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5);
#endif // SYSCALL_H

98
src/kernel/syscalls.asm Normal file
View file

@ -0,0 +1,98 @@
global syscall_entry
extern syscall_handler_c
section .text
; Syscall ABI:
; RDI = syscall_num
; RSI = arg1
; RDX = arg2
; R10 = arg3
; R8 = arg4
; R9 = arg5
syscall_entry:
; We arrived here from Ring 3 via `syscall`.
; RAX = syscall_num
; RDI, RSI, RDX, R10, R8, R9 = args
; RCX = User RIP
; R11 = User RFLAGS
; Current RSP = User RSP
; 1. Save User RSP
mov [rel user_rsp_scratch], rsp
; 2. Switch to Kernel Stack
mov rsp, [rel kernel_syscall_stack]
; 3. Save preserved registers (System V ABI)
push rbx
push rbp
push r12
push r13
push r14
push r15
; We also need to save RCX (RIP) and R11 (RFLAGS) because C functions might clobber them
push rcx
push r11
; Syscall convention: argument 4 is passed in R10, but C expects it in RCX
mov rcx, r10
; The syscall number is in RAX, let's put it in RDI (arg 0 for C)
; But wait, the ABI expects arg1 in RDI!
; Let's change our C handler signature or adapt here.
; C handler: void syscall_handler_c(uint64_t syscall_num, uint64_t arg1, ...)
; So:
; syscall_num -> RDI
; arg1 (was RDI) -> RSI
; arg2 (was RSI) -> RDX
; arg3 (was RDX) -> RCX
; arg4 (was R10) -> R8
; arg5 (was R8) -> R9
; arg6 (was R9) -> stack (if needed, but we have 6 regs)
; This shuffling is messy. Let's just push everything and call a struct-based handler,
; or carefully shuffle.
; For now, let's just make sure RAX goes to RDI, RDI to RSI, RSI to RDX, RDX to RCX, R10 to R8.
; Shuffling for SYS V C ABI:
; R9 is arg6 -> no room in registers, need to push to stack (but our handler takes 6 args total)
mov r9, r8 ; arg5
mov r8, r10 ; arg4
mov rcx, rdx ; arg3
mov rdx, rsi ; arg2
mov rsi, rdi ; arg1
mov rdi, rax ; syscall_num
; 4. Call C handler
call syscall_handler_c
; 5. Restore RCX and R11
pop r11
pop rcx
; 6. Restore preserved registers
pop r15
pop r14
pop r13
pop r12
pop rbp
pop rbx
; 7. Restore User RSP
mov rsp, [rel user_rsp_scratch]
; 8. Return to User Mode
; NASM syntax for 64-bit sysret requires the o64 prefix
; Force IF=1 (bit 9) in R11 (restored to RFLAGS) to ensure interrupts stay enabled!
or r11, 0x200
o64 sysret
section .bss
global kernel_syscall_stack
global user_rsp_scratch
kernel_syscall_stack: resq 1
user_rsp_scratch: resq 1

View file

@ -0,0 +1,16 @@
global test_syscall
section .text
test_syscall:
; syscall number in RDI
mov rdi, 1
; string pointer in RSI
lea rsi, [rel test_msg]
; The SYSCALL instruction
syscall
ret
section .rodata
test_msg: db "Hello from Syscall!", 10, 0

22
src/kernel/user_test.asm Normal file
View file

@ -0,0 +1,22 @@
global user_test_function
section .text
user_test_function:
; Syscall convention
.loop:
; Invoke SYS_WRITE (Syscall #1)
mov rdi, 1 ; arg1: fd = 1 (stdout)
lea rsi, [rel msg] ; arg2: buffer (RIP-relative)
mov rdx, 15 ; arg3: length
mov eax, 1 ; syscall_num = 1 (SYS_WRITE)
syscall
; Some delay loop
mov rcx, 100000000
.delay:
dec rcx
jnz .delay
jmp .loop
msg: db "Hello syscall!", 10