diff --git a/.gitignore b/.gitignore index 62b4218..77d1f65 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.bin *.iso *.img +/boot/ # Cargo Cargo.lock diff --git a/Makefile b/Makefile index 54f6732..c7cbc97 100644 --- a/Makefile +++ b/Makefile @@ -18,11 +18,12 @@ LIB_DIR := lib HAL_DIR := hal SERVERS_DIR := servers KERNEL_DIR := kernel -ISO_DIR := $(BUILD_DIR)/iso_root +LIMINE_DIR := boot/limine # Common Freestanding Flags COMMON_FLAGS := -Wall -Wextra -O2 -ffreestanding -fno-stack-protector \ -fno-pie -no-pie -mno-red-zone -m64 \ + -mno-sse -mno-sse2 -mno-avx \ -I$(INCLUDE_DIR) -I$(LIB_DIR)/libc/include CFLAGS := $(COMMON_FLAGS) -std=c11 @@ -35,18 +36,20 @@ INIT_SERVER := $(BUILD_DIR)/init_server.elf UART_DRIVER := $(BUILD_DIR)/uart_driver.elf SH_SERVER := $(BUILD_DIR)/sh.elf PROCMGR := $(BUILD_DIR)/procmgr.elf +DISK_IMG := $(BUILD_DIR)/opencoreC.img LIBC_OBJS := $(BUILD_DIR)/string.o $(BUILD_DIR)/syscalls.o $(BUILD_DIR)/stdlib.o -.PHONY: all kernel libc hal servers iso run clean help +.PHONY: all kernel libc hal servers image run clean help limine-setup all: kernel libc hal servers @mkdir -p $(BUILD_DIR) @cp $(KERNEL_BIN) $(KERNEL_ELF) @echo "=======================================================" - @echo " [SUCCESS] opencoreC Skeleton Build Complete!" + @echo " [SUCCESS] opencoreC Build Complete!" @echo " Kernel: $(KERNEL_ELF)" - @echo " Servers: $(INIT_SERVER), $(UART_DRIVER), $(SH_SERVER), $(PROCMGR)" + @echo " Servers: $(INIT_SERVER), $(UART_DRIVER), $(SH_SERVER)" + @echo " Run: make image && make run" @echo "=======================================================" kernel: @@ -90,25 +93,60 @@ servers: libc hal @$(LD) -T $(CONFIG_DIR)/user.ld -nostdlib -static -o $(PROCMGR) \ $(BUILD_DIR)/procmgr_main.o $(BUILD_DIR)/ipc.o $(LIBC_OBJS) -# Staging Limine Boot ISO directory -iso: all - @mkdir -p $(ISO_DIR)/boot $(ISO_DIR)/EFI/BOOT - @cp $(KERNEL_ELF) $(ISO_DIR)/boot/ - @cp $(INIT_SERVER) $(ISO_DIR)/boot/ - @cp $(UART_DRIVER) $(ISO_DIR)/boot/ - @cp $(SH_SERVER) $(ISO_DIR)/boot/ - @cp limine.conf $(ISO_DIR)/boot/limine.conf - @cp limine.cfg $(ISO_DIR)/boot/limine.cfg - @echo "[ISO] Staged boot files in $(ISO_DIR)/" +# Download and build the Limine bootloader host tool +limine-setup: + @if [ ! -d "$(LIMINE_DIR)" ]; then \ + echo "[LIMINE] Cloning Limine v8 binary branch..."; \ + git clone https://github.com/limine-bootloader/limine.git --branch=v8.x-binary --depth=1 $(LIMINE_DIR); \ + fi + @echo "[LIMINE] Building host installer tool..." + @$(MAKE) -C $(LIMINE_DIR) limine -run: all - @echo "[QEMU] Launching opencoreC in QEMU (SMP 4 Cores, Serial stdio)..." - @$(QEMU) -M q35 -smp 4 -m 512M \ - -serial stdio \ - -display none \ +# Build a 64 MiB bootable FAT32 disk image with Limine BIOS/UEFI +image: all limine-setup + @echo "[IMAGE] Creating bootable disk image (64 MiB FAT32)..." + @dd if=/dev/zero of=$(DISK_IMG) bs=1M count=64 2>/dev/null + @parted -s $(DISK_IMG) mklabel msdos mkpart primary fat32 2048s 100% set 1 boot on 2>/dev/null + @mformat -F -i $(DISK_IMG)@@1M :: + @mmd -i $(DISK_IMG)@@1M ::/boot ::/EFI ::/EFI/BOOT + @# Kernel and userspace servers + @mcopy -i $(DISK_IMG)@@1M $(KERNEL_ELF) ::/boot/ + @mcopy -i $(DISK_IMG)@@1M $(INIT_SERVER) ::/boot/ + @mcopy -i $(DISK_IMG)@@1M $(UART_DRIVER) ::/boot/ + @mcopy -i $(DISK_IMG)@@1M $(SH_SERVER) ::/boot/ + @# Bootloader config + @mcopy -i $(DISK_IMG)@@1M limine.conf ::/ + @mcopy -i $(DISK_IMG)@@1M limine.cfg ::/ + @mcopy -i $(DISK_IMG)@@1M limine.conf ::/boot/ + @mcopy -i $(DISK_IMG)@@1M limine.cfg ::/boot/ + @# Limine bootloader files + @mcopy -i $(DISK_IMG)@@1M $(LIMINE_DIR)/limine-bios.sys ::/ + @mcopy -i $(DISK_IMG)@@1M $(LIMINE_DIR)/limine-bios.sys ::/boot/ + @mcopy -i $(DISK_IMG)@@1M $(LIMINE_DIR)/BOOTX64.EFI ::/EFI/BOOT/ + @# Install Limine Stage 1 into MBR + @$(LIMINE_DIR)/limine bios-install $(DISK_IMG) 2>&1 | grep -v "^Physical\|^Installing\|^Stage\|^Reminder" + @echo "[IMAGE] Done: $(DISK_IMG) (BIOS + UEFI bootable)" + +# Launch QEMU with serial terminal (keyboard works via -serial stdio) +run: image + @echo "[QEMU] Launching opencoreC (SMP 4 cores, Serial terminal)..." + @echo "[QEMU] Type 'help' for commands. Ctrl+A X to exit QEMU." + @echo "-------------------------------------------------------" + @$(QEMU) \ + -M q35 \ + -smp 4 \ + -m 512M \ + -serial mon:stdio \ -no-reboot \ - -no-shutdown \ - -kernel $(KERNEL_ELF) + -display none \ + -drive file=$(DISK_IMG),format=raw + +# Run without rebuilding image (faster iteration) +qemu: $(DISK_IMG) + @echo "[QEMU] Quick launch from existing image..." + @echo "[QEMU] Type 'help'. Ctrl+A X to exit." + @$(QEMU) -M q35 -smp 4 -m 512M -serial mon:stdio -no-reboot -display none \ + -drive file=$(DISK_IMG),format=raw clean: @echo "[CLEAN] Removing build directory and cargo target..." @@ -116,7 +154,16 @@ clean: help: @echo "opencoreC Microkernel Build System" + @echo "" @echo "Targets:" - @echo " make all - Build kernel, HAL, libc, and all user servers" - @echo " make run - Launch QEMU with SMP 4 Cores & Serial output" - @echo " make clean - Clean build artifacts" + @echo " make all - Build kernel, HAL, libc, and all user servers" + @echo " make image - Create bootable disk image (build/opencoreC.img)" + @echo " make run - Build image and launch in QEMU (interactive serial)" + @echo " make qemu - Quick relaunch without rebuild" + @echo " make limine-setup - Download/build Limine bootloader" + @echo " make clean - Remove all build artifacts" + @echo "" + @echo "QEMU Controls:" + @echo " Type commands in the terminal (serial I/O)" + @echo " Ctrl+A then X -> exit QEMU" + @echo " Ctrl+A then C -> QEMU monitor" diff --git a/config/linker.ld b/config/linker.ld index 3927ab1..5e2bd08 100644 --- a/config/linker.ld +++ b/config/linker.ld @@ -25,15 +25,25 @@ SECTIONS *(.rodata .rodata.*) } :rodata - /* Limine requests section */ - .limine_requests : { - KEEP(*(.limine_requests_start)) - KEEP(*(.limine_requests)) - KEEP(*(.limine_requests_end)) - } :rodata - . = ALIGN(CONSTANT(MAXPAGESIZE)); + /* Limine requests: must be in a WRITABLE segment so bootloader can fill responses */ + .limine_base_revision : { + KEEP(*(.limine_base_revision)) + } :data + + .limine_requests_start : { + KEEP(*(.limine_requests_start)) + } :data + + .limine_requests : { + KEEP(*(.limine_requests)) + } :data + + .limine_requests_end : { + KEEP(*(.limine_requests_end)) + } :data + .data : { *(.data .data.*) } :data diff --git a/kernel/asm/context.S b/kernel/asm/context.S index cc37547..7389001 100644 --- a/kernel/asm/context.S +++ b/kernel/asm/context.S @@ -1,14 +1,15 @@ -/* opencoreC - Thread Context Switch */ +/* opencoreC - Thread Context Switch & Entry Trampoline */ .global switch_to +.global thread_entry_trampoline .section .text /* - * void switch_to(uint64_t **prev_rsp_ptr, uint64_t *next_rsp); - * RDI = pointer to current thread's stored RSP - * RSI = target thread's RSP + * switch_to(uint64_t *prev_rsp_ptr, uint64_t next_rsp) + * RDI = ¤t_thread.context.rsp (save current RSP here) + * RSI = next thread's saved RSP */ switch_to: - /* Push callee-saved registers of outgoing thread */ + /* Save callee-saved registers of outgoing thread */ push rbp push rbx push r12 @@ -16,13 +17,13 @@ switch_to: push r14 push r15 - /* Save current stack pointer */ + /* Save RSP of outgoing thread */ mov [rdi], rsp - /* Load target stack pointer */ + /* Load RSP of incoming thread */ mov rsp, rsi - /* Pop callee-saved registers of incoming thread */ + /* Restore callee-saved registers of incoming thread */ pop r15 pop r14 pop r13 @@ -31,3 +32,45 @@ switch_to: pop rbp ret + +/* + * thread_entry_trampoline: + * Jumped to via 'ret' in switch_to when a newly created thread runs for the first time. + * When create_user_thread created the stack: + * R12 was populated with user_rsp + * R13 was populated with entry_rip + */ +thread_entry_trampoline: + /* Set user data segments (0x18 | 3 = 0x1B) */ + mov ax, 0x1B + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + /* Build hardware IRETQ frame on kernel stack */ + push 0x1B /* User SS */ + push r12 /* User RSP */ + push 0x3202 /* RFLAGS (IF=1, IOPL=3 for hardware drivers) */ + push 0x23 /* User CS (0x20 | 3 = 0x23) */ + push r13 /* User RIP */ + + /* Clear all general-purpose registers */ + xor rax, rax + xor rbx, rbx + xor rcx, rcx + xor rdx, rdx + xor rsi, rsi + xor rdi, rdi + xor rbp, rbp + xor r8, r8 + xor r9, r9 + xor r10, r10 + xor r11, r11 + xor r12, r12 + xor r13, r13 + xor r14, r14 + xor r15, r15 + + /* Transition to Ring 3 */ + iretq diff --git a/kernel/asm/syscall_entry.S b/kernel/asm/syscall_entry.S index b19d067..d9c8233 100644 --- a/kernel/asm/syscall_entry.S +++ b/kernel/asm/syscall_entry.S @@ -2,25 +2,46 @@ .global syscall_entry .extern kernel_syscall_dispatcher +/* Per-CPU scratch: user RSP saved here on syscall entry */ .section .bss -.align 16 -user_scratch_rsp: - .quad 0 -kernel_temp_stack: - .skip 4096 -kernel_temp_stack_top: +.align 8 +.global cpu0_scratch_user_rsp +cpu0_scratch_user_rsp: .quad 0 .section .text +/* + * syscall_entry: + * On entry (SYSCALL from Ring 3): + * RCX = user RIP + * R11 = user RFLAGS + * RAX = syscall number + * RDI,RSI,RDX,R10,R8,R9 = args + * RSP = user RSP + * + * Strategy: + * 1. Save user RSP into scratch area + * 2. Load kernel RSP from TSS.RSP0 via gdt.rs TSS symbol + * 3. Build SyscallRegisters on kernel stack + * 4. Dispatch to Rust handler + * 5. Restore & sysretq + */ syscall_entry: - /* Save user stack pointer */ - mov [rip + user_scratch_rsp], rsp - lea rsp, [rip + kernel_temp_stack_top] + /* Save user RSP */ + mov [rip + cpu0_scratch_user_rsp], rsp - /* Save user RIP (in RCX) and user RFLAGS (in R11) */ + /* Load kernel stack from TSS.rsp0. + * TSS is exported as 'TSS' from gdt.rs. + * Layout: _reserved1(u32=4 bytes), rsp0(u64=8 bytes) + * So TSS.rsp0 is at offset 4 from TSS start. + */ + lea rsp, [rip + TSS] + mov rsp, [rsp + 4] + + /* Push user RIP and RFLAGS */ push rcx push r11 - /* Save argument and volatile registers to construct SyscallRegisters */ + /* Push syscall arguments as SyscallRegisters */ push rax push rdi push rsi @@ -29,11 +50,10 @@ syscall_entry: push r9 push r10 - /* Pass pointer to SyscallRegisters struct in RDI */ mov rdi, rsp call kernel_syscall_dispatcher - /* Restore registers (rax, rdx, r8 may have return values from dispatcher) */ + /* Restore args */ pop r10 pop r9 pop r8 @@ -42,12 +62,11 @@ syscall_entry: pop rdi pop rax - /* Restore user RIP and RFLAGS */ + /* Restore user context */ pop r11 pop rcx - /* Restore user stack */ - mov rsp, [rip + user_scratch_rsp] + /* Restore user RSP */ + mov rsp, [rip + cpu0_scratch_user_rsp] - /* Return to Ring 3 */ sysretq diff --git a/kernel/asm/timer_isr.S b/kernel/asm/timer_isr.S index ab6c929..d1930e1 100644 --- a/kernel/asm/timer_isr.S +++ b/kernel/asm/timer_isr.S @@ -4,7 +4,7 @@ .section .text timer_isr_entry: - /* 1. Save all general-purpose registers to form a complete interrupt context frame */ + /* 1. Save all general-purpose registers to form complete interrupt context frame */ push rax push rbx push rcx @@ -21,14 +21,10 @@ timer_isr_entry: push r14 push r15 - /* 2. Pass current saved stack pointer in RDI */ - mov rdi, rsp + /* 2. Call handler in Rust (sends EOI and invokes sched.preempt_tick) */ call timer_interrupt_handler - /* 3. Switch stack pointer to returned value (RAX contains next thread's RSP) */ - mov rsp, rax - - /* 4. Restore all general-purpose registers */ + /* 3. Restore all general-purpose registers */ pop r15 pop r14 pop r13 @@ -45,5 +41,5 @@ timer_isr_entry: pop rbx pop rax - /* 5. Return from interrupt (IRETQ restores RIP, CS, RFLAGS, RSP, SS) */ + /* 4. Return from interrupt (IRETQ restores RIP, CS, RFLAGS, RSP, SS) */ iretq diff --git a/kernel/src/arch/gdt.rs b/kernel/src/arch/gdt.rs index c4a1f3c..e64ebe8 100644 --- a/kernel/src/arch/gdt.rs +++ b/kernel/src/arch/gdt.rs @@ -37,6 +37,7 @@ struct GdtDescriptor { base: u64, } +#[no_mangle] pub static mut TSS: TaskStateSegment = TaskStateSegment::new(); // 8 entries: Null, KCode, KData, UData, UCode, TSS Low, TSS High, Null diff --git a/kernel/src/arch/idt.rs b/kernel/src/arch/idt.rs index 386e320..7f331c9 100644 --- a/kernel/src/arch/idt.rs +++ b/kernel/src/arch/idt.rs @@ -55,37 +55,93 @@ extern "C" { fn keyboard_isr_entry(); } -// Global assembly ISR stub for default exception handling +// Global assembly ISR stub with vector ID pushed as separate stubs (0-31) core::arch::global_asm!( + // With error code: CPU already pushed error_code, we push vector on top + ".macro ISR_ERR_CODE num", + "isr_stub_\\num:", + "push \\num", // push vector number (error_code already on stack below) + "jmp default_isr_common", + ".endm", + // Without error code: push dummy 0, then vector + ".macro ISR_NO_ERR num", + "isr_stub_\\num:", + "push 0", // dummy error_code + "push \\num", // push vector number + "jmp default_isr_common", + ".endm", + + "ISR_NO_ERR 0", "ISR_NO_ERR 1", "ISR_NO_ERR 2", "ISR_NO_ERR 3", + "ISR_NO_ERR 4", "ISR_NO_ERR 5", "ISR_NO_ERR 6", "ISR_NO_ERR 7", + "ISR_ERR_CODE 8","ISR_NO_ERR 9", "ISR_ERR_CODE 10","ISR_ERR_CODE 11", + "ISR_ERR_CODE 12","ISR_ERR_CODE 13","ISR_ERR_CODE 14","ISR_NO_ERR 15", + "ISR_NO_ERR 16","ISR_ERR_CODE 17","ISR_NO_ERR 18","ISR_NO_ERR 19", + "ISR_NO_ERR 20","ISR_ERR_CODE 21","ISR_NO_ERR 22","ISR_NO_ERR 23", + "ISR_NO_ERR 24","ISR_NO_ERR 25","ISR_NO_ERR 26","ISR_NO_ERR 27", + "ISR_NO_ERR 28","ISR_NO_ERR 29","ISR_ERR_CODE 30","ISR_NO_ERR 31", + ".global default_isr_stub", "default_isr_stub:", - "push rax", - "push rcx", - "push rdx", - "push rsi", - "push rdi", - "push r8", - "push r9", - "push r10", - "push r11", + "push 0", // dummy error code + "push 255", // dummy vector + "jmp default_isr_common", + + ".global default_isr_common", + "default_isr_common:", + // Stack at this point (top to bottom): + // RSP+0: vector + // RSP+8: error_code + // RSP+16: RIP (pushed by CPU) + // RSP+24: CS + // RSP+32: RFLAGS + // RSP+40: RSP (if ring change) + // RSP+48: SS (if ring change) + "push rax", "push rcx", "push rdx", + "push rsi", "push rdi", + "push r8", "push r9", "push r10", "push r11", "mov rdi, rsp", "call raw_exception_handler", - "pop r11", - "pop r10", - "pop r9", - "pop r8", - "pop rdi", - "pop rsi", - "pop rdx", - "pop rcx", - "pop rax", + "pop r11", "pop r10", "pop r9", "pop r8", + "pop rdi", "pop rsi", + "pop rdx", "pop rcx", "pop rax", + // skip vector + error_code from stack + "add rsp, 16", "iretq" ); + pub unsafe fn init() { - let stub_addr = default_isr_stub as *const () as usize as u64; let idt_ptr = core::ptr::addr_of_mut!(IDT) as *mut IdtEntry; - for i in 0..256 { + + // Install per-vector stubs for exceptions 0-31 + extern "C" { + fn isr_stub_0(); fn isr_stub_1(); fn isr_stub_2(); fn isr_stub_3(); + fn isr_stub_4(); fn isr_stub_5(); fn isr_stub_6(); fn isr_stub_7(); + fn isr_stub_8(); fn isr_stub_9(); fn isr_stub_10(); fn isr_stub_11(); + fn isr_stub_12(); fn isr_stub_13(); fn isr_stub_14(); fn isr_stub_15(); + fn isr_stub_16(); fn isr_stub_17(); fn isr_stub_18(); fn isr_stub_19(); + fn isr_stub_20(); fn isr_stub_21(); fn isr_stub_22(); fn isr_stub_23(); + fn isr_stub_24(); fn isr_stub_25(); fn isr_stub_26(); fn isr_stub_27(); + fn isr_stub_28(); fn isr_stub_29(); fn isr_stub_30(); fn isr_stub_31(); + } + + let exception_stubs: [u64; 32] = [ + isr_stub_0 as u64, isr_stub_1 as u64, isr_stub_2 as u64, isr_stub_3 as u64, + isr_stub_4 as u64, isr_stub_5 as u64, isr_stub_6 as u64, isr_stub_7 as u64, + isr_stub_8 as u64, isr_stub_9 as u64, isr_stub_10 as u64, isr_stub_11 as u64, + isr_stub_12 as u64, isr_stub_13 as u64, isr_stub_14 as u64, isr_stub_15 as u64, + isr_stub_16 as u64, isr_stub_17 as u64, isr_stub_18 as u64, isr_stub_19 as u64, + isr_stub_20 as u64, isr_stub_21 as u64, isr_stub_22 as u64, isr_stub_23 as u64, + isr_stub_24 as u64, isr_stub_25 as u64, isr_stub_26 as u64, isr_stub_27 as u64, + isr_stub_28 as u64, isr_stub_29 as u64, isr_stub_30 as u64, isr_stub_31 as u64, + ]; + for (i, &stub_addr) in exception_stubs.iter().enumerate() { + (*idt_ptr.add(i)).set_handler(stub_addr, 0, 0); + } + + // Fill 32..255 with default stub + let stub_addr = default_isr_stub as *const () as usize as u64; + for i in 32..256usize { (*idt_ptr.add(i)).set_handler(stub_addr, 0, 0); } @@ -105,16 +161,57 @@ pub unsafe fn init() { core::arch::asm!("lidt [{0}]", in(reg) &descriptor, options(preserves_flags)); } -#[no_mangle] -pub extern "C" fn raw_exception_handler(stack_ptr: *const u64) { - kprintln!("[CPU EXCEPTION] Trap triggered at stack frame: {:p}", stack_ptr); +#[repr(C)] +pub struct ExceptionFrame { + // Stack layout when raw_exception_handler is called (low addr = top of stack): + // Pushed by default_isr_common: r11,r10,r9,r8,rdi,rsi,rdx,rcx,rax (in that push order) + pub r11: u64, pub r10: u64, pub r9: u64, pub r8: u64, + pub rdi: u64, pub rsi: u64, pub rdx: u64, pub rcx: u64, pub rax: u64, + // Pushed by per-vector stub (in push order: vector first on top) + pub vector: u64, + pub error_code: u64, + // Pushed by CPU hardware: + pub rip: u64, + pub cs: u64, + pub rflags: u64, + pub user_rsp: u64, // only present on ring transition + pub ss: u64, } #[no_mangle] -pub extern "C" fn timer_interrupt_handler(saved_rsp: u64) -> u64 { +pub extern "C" fn raw_exception_handler(frame: *const ExceptionFrame) { + unsafe { + let v = (*frame).vector; + let ec = (*frame).error_code; + let rip = (*frame).rip; + let cs = (*frame).cs; + let rflags = (*frame).rflags; + let names = [ + "#DE", "#DB", "NMI", "#BP", "#OF", "#BR", "#UD", "#NM", + "#DF", "CSO", "#TS", "#NP", "#SS", "#GP", "#PF", "RES", + "#MF", "#AC", "#MC", "#XM", "#VE", "#CP", "??" , "??" , + "??" , "??" , "??" , "??" , "??" , "??" , "#SX", "??" , + ]; + let name = if v < 32 { names[v as usize] } else { "IRQ" }; + kprintln!("\n[EXCEPTION] Vec={} ({}) ErrCode={:#x} RIP={:#018x} CS={:#x} RFLAGS={:#x}", + v, name, ec, rip, cs, rflags); + if v == 14 { + let cr2: u64; + core::arch::asm!("mov {}, cr2", out(reg) cr2, options(nostack, preserves_flags)); + kprintln!(" #PF CR2 (faulting address): {:#018x}", cr2); + } + if v < 32 { + kprintln!(" [HALT] Unrecoverable exception - CPU halted."); + loop { core::arch::asm!("cli; hlt", options(nostack, preserves_flags)); } + } + } +} + +#[no_mangle] +pub extern "C" fn timer_interrupt_handler() { unsafe { send_eoi(); let sched = &mut *core::ptr::addr_of_mut!(SCHEDULER); - sched.preempt(saved_rsp) + sched.preempt_tick(); } } diff --git a/kernel/src/arch/mod.rs b/kernel/src/arch/mod.rs index 1962606..98493a1 100644 --- a/kernel/src/arch/mod.rs +++ b/kernel/src/arch/mod.rs @@ -4,8 +4,28 @@ pub mod syscall; pub mod smp; pub unsafe fn init() { + enable_sse(); gdt::init(); idt::init(); syscall::init(); smp::init(); } + +/// Enable SSE/SSE2/XSAVE for user-space SIMD operations. +/// Required to prevent #GP when user programs use XMM registers or compiler-generated SSE code. +unsafe fn enable_sse() { + // Clear CR0.EM (bit 2) - disable x87 FPU emulation + // Set CR0.MP (bit 1) - monitor coprocessor (allow WAIT) + let mut cr0: u64; + core::arch::asm!("mov {0}, cr0", out(reg) cr0, options(nostack, preserves_flags)); + cr0 &= !(1u64 << 2); // Clear EM + cr0 |= 1u64 << 1; // Set MP + core::arch::asm!("mov cr0, {0}", in(reg) cr0, options(nostack, preserves_flags)); + + // Set CR4.OSFXSR (bit 9) - OS supports FXSAVE/FXRSTOR + // Set CR4.OSXMMEXCPT (bit 10) - OS handles SIMD FP exceptions + let mut cr4: u64; + core::arch::asm!("mov {0}, cr4", out(reg) cr4, options(nostack, preserves_flags)); + cr4 |= (1u64 << 9) | (1u64 << 10); + core::arch::asm!("mov cr4, {0}", in(reg) cr4, options(nostack, preserves_flags)); +} diff --git a/kernel/src/limine_requests.rs b/kernel/src/limine_requests.rs index 5f24456..ea36869 100644 --- a/kernel/src/limine_requests.rs +++ b/kernel/src/limine_requests.rs @@ -1,20 +1,17 @@ //! Limine Bootloader Protocol Requests & Types for opencoreC Microkernel -//! Implements Base Revision 3 specification. +//! Implements Limine v8 Base Revision 0 specification. -#[repr(C)] -pub struct LimineBaseRevision { - pub id: [u64; 4], - pub revision: u64, -} -unsafe impl Sync for LimineBaseRevision {} +#[repr(transparent)] +pub struct SyncPtr(pub *const ()); +unsafe impl Sync for SyncPtr {} -#[repr(C)] +#[repr(C, align(8))] pub struct LimineHhdmResponse { pub revision: u64, pub offset: u64, } -#[repr(C)] +#[repr(C, align(8))] pub struct LimineHhdmRequest { pub id: [u64; 4], pub revision: u64, @@ -35,21 +32,21 @@ pub enum LimineMemoryType { Framebuffer = 7, } -#[repr(C)] +#[repr(C, align(8))] pub struct LimineMemmapEntry { pub base: u64, pub length: u64, pub typ: u64, } -#[repr(C)] +#[repr(C, align(8))] pub struct LimineMemmapResponse { pub revision: u64, pub entry_count: u64, pub entries: *const *const LimineMemmapEntry, } -#[repr(C)] +#[repr(C, align(8))] pub struct LimineMemmapRequest { pub id: [u64; 4], pub revision: u64, @@ -57,14 +54,14 @@ pub struct LimineMemmapRequest { } unsafe impl Sync for LimineMemmapRequest {} -#[repr(C)] +#[repr(C, align(8))] pub struct LimineKernelAddressResponse { pub revision: u64, pub physical_base: u64, pub virtual_base: u64, } -#[repr(C)] +#[repr(C, align(8))] pub struct LimineKernelAddressRequest { pub id: [u64; 4], pub revision: u64, @@ -72,7 +69,7 @@ pub struct LimineKernelAddressRequest { } unsafe impl Sync for LimineKernelAddressRequest {} -#[repr(C)] +#[repr(C, align(8))] pub struct LimineFile { pub revision: u64, pub address: *const u8, @@ -90,14 +87,14 @@ pub struct LimineFile { pub part_uuid: [u8; 16], } -#[repr(C)] +#[repr(C, align(8))] pub struct LimineModuleResponse { pub revision: u64, pub module_count: u64, pub modules: *const *const LimineFile, } -#[repr(C)] +#[repr(C, align(8))] pub struct LimineModuleRequest { pub id: [u64; 4], pub revision: u64, @@ -107,7 +104,7 @@ pub struct LimineModuleRequest { } unsafe impl Sync for LimineModuleRequest {} -#[repr(C)] +#[repr(C, align(8))] pub struct LimineSmpInfo { pub processor_id: u32, pub lapic_id: u32, @@ -116,7 +113,7 @@ pub struct LimineSmpInfo { pub extra_argument: u64, } -#[repr(C)] +#[repr(C, align(8))] pub struct LimineSmpResponse { pub revision: u64, pub flags: u32, @@ -125,7 +122,7 @@ pub struct LimineSmpResponse { pub cpus: *const *const LimineSmpInfo, } -#[repr(C)] +#[repr(C, align(8))] pub struct LimineSmpRequest { pub id: [u64; 4], pub revision: u64, @@ -134,57 +131,90 @@ pub struct LimineSmpRequest { } unsafe impl Sync for LimineSmpRequest {} -/* Constants for Limine Request Identifiers */ -#[used] -#[link_section = ".limine_requests"] -pub static BASE_REVISION: LimineBaseRevision = LimineBaseRevision { - id: [0xf9562b2d5c95a6c8, 0x6a59638edd43477b, 0, 3], - revision: 3, -}; +// ============================================================================= +// Limine Protocol Markers & Requests (all in writable sections) +// ============================================================================= +/// Base Revision tag - bootloader sets [2] to 0 when supported #[used] +#[no_mangle] +#[link_section = ".limine_base_revision"] +pub static mut limine_base_revision: [u64; 3] = [ + 0xf9562b2d5c95a6c8, + 0x6a7b384944536bdc, + 0, // Base revision 0 - widest compatibility +]; + +/// Start sentinel marker for Limine request scan +#[used] +#[no_mangle] +#[link_section = ".limine_requests_start"] +pub static limine_requests_start_marker: [u64; 4] = [ + 0xf6b8f4b39de7d1ae, 0xfab91a6940fcb9cf, + 0x785c6ed015d3e316, 0x181e920a7852b9d9, +]; + +/// HHDM Request (Higher-Half Direct Map offset) +#[used] +#[no_mangle] #[link_section = ".limine_requests"] -pub static HHDM_REQUEST: LimineHhdmRequest = LimineHhdmRequest { - id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, 0x48d150213051ecda, 0x21e2d0708170e71e], +pub static mut HHDM_REQUEST: LimineHhdmRequest = LimineHhdmRequest { + id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, 0x48dcf1cb8ad2b852, 0x63984e959a98244b], revision: 0, response: core::ptr::null(), }; +/// Memory Map Request #[used] +#[no_mangle] #[link_section = ".limine_requests"] -pub static MEMMAP_REQUEST: LimineMemmapRequest = LimineMemmapRequest { - id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, 0x67cf3d9d378a14e6, 0xe310ac092f613308], +pub static mut MEMMAP_REQUEST: LimineMemmapRequest = LimineMemmapRequest { + id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, 0x67cf3d9d378a806f, 0xe304acdfc50c3c62], revision: 0, response: core::ptr::null(), }; +/// Kernel Virtual/Physical Address Request #[used] +#[no_mangle] #[link_section = ".limine_requests"] -pub static KERNEL_ADDRESS_REQUEST: LimineKernelAddressRequest = LimineKernelAddressRequest { +pub static mut KERNEL_ADDRESS_REQUEST: LimineKernelAddressRequest = LimineKernelAddressRequest { id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, 0x71ba76863cc55f63, 0xb2644a48c516a487], revision: 0, response: core::ptr::null(), }; +/// Boot Module Request #[used] +#[no_mangle] #[link_section = ".limine_requests"] -pub static MODULE_REQUEST: LimineModuleRequest = LimineModuleRequest { - id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, 0x3e7e2797022b3699, 0xa69dc94525d978b1], +pub static mut MODULE_REQUEST: LimineModuleRequest = LimineModuleRequest { + id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, 0x3e7e279702be32af, 0xca1c4f3bd1280cee], revision: 0, response: core::ptr::null(), internal_module_count: 0, internal_modules: core::ptr::null(), }; +/// SMP Multicore Request #[used] +#[no_mangle] #[link_section = ".limine_requests"] -pub static SMP_REQUEST: LimineSmpRequest = LimineSmpRequest { - id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, 0x95a105da0e6d6305, 0xa5951e16b0f99bc7], +pub static mut SMP_REQUEST: LimineSmpRequest = LimineSmpRequest { + id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, 0x95a67b819a1b857e, 0xa0b61b723b6a73e0], revision: 0, response: core::ptr::null(), flags: 0, }; +/// End sentinel marker +#[used] +#[no_mangle] +#[link_section = ".limine_requests_end"] +pub static limine_requests_end_marker: [u64; 2] = [ + 0xadc0e0531bb10d03, 0x9572709f31764c62, +]; + /// Get HHDM direct map virtual offset pub fn get_hhdm_offset() -> Option { unsafe { diff --git a/kernel/src/main.rs b/kernel/src/main.rs index ed98101..8b8eeb1 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -18,25 +18,28 @@ core::arch::global_asm!(include_str!("../asm/context.S")); core::arch::global_asm!(include_str!("../asm/syscall_entry.S")); core::arch::global_asm!(include_str!("../asm/ring3_enter.S")); core::arch::global_asm!(include_str!("../asm/timer_isr.S")); +core::arch::global_asm!(include_str!("../asm/keyboard_isr.S")); #[no_mangle] pub extern "C" fn _start() -> ! { - // 1. Validate Limine Base Revision - if BASE_REVISION.revision != 3 { - loop { core::hint::spin_loop(); } - } - - // 2. Early Serial Initialization + // 1. Early Serial Initialization drivers::serial::SerialPort::init(); kprintln!("\n======================================================="); kprintln!(" opencoreC Microkernel v0.1.0 (x86_64)"); kprintln!("======================================================="); + // 2. Validate Limine Base Revision + unsafe { + if limine_base_revision[2] != 0 { + kprintln!("[WARN] Limine Base Revision 3 not supported by bootloader."); + } + } + // 3. Obtain Limine responses let hhdm_offset = get_hhdm_offset().expect("Limine HHDM response missing"); - let memmap_resp = MEMMAP_REQUEST.response; - let kernel_addr_resp = KERNEL_ADDRESS_REQUEST.response; + let memmap_resp = unsafe { MEMMAP_REQUEST.response }; + let kernel_addr_resp = unsafe { KERNEL_ADDRESS_REQUEST.response }; kprintln!("[BOOT] Limine HHDM Base Virtual Address: {:#018x}", hhdm_offset); if !kernel_addr_resp.is_null() { @@ -47,9 +50,14 @@ pub extern "C" fn _start() -> ! { ); } } + unsafe { + kprintln!("[DBG] MEMMAP resp ptr: {:#018x}", MEMMAP_REQUEST.response as u64); + kprintln!("[DBG] MODULE resp ptr: {:#018x}", MODULE_REQUEST.response as u64); + kprintln!("[DBG] SMP resp ptr: {:#018x}", SMP_REQUEST.response as u64); + } - // 4. Initialize Architecture (GDT, IDT, Syscall MSRs) - kprintln!("[BOOT] Initializing GDT, IDT, and Fast-Path Syscall MSRs..."); + // 4. Initialize Architecture (GDT, IDT, Syscall MSRs, SMP) + kprintln!("[BOOT] Initializing GDT, IDT, Fast-Path Syscall MSRs & SMP..."); unsafe { arch::init(); } @@ -65,6 +73,7 @@ pub extern "C" fn _start() -> ! { kprintln!("[BOOT] Configuring PIT 8254 timer for Preemptive Multi-Tasking..."); unsafe { drivers::timer::init(100); + drivers::keyboard::init(); } // 7. Initialize IPC & Capability System @@ -88,6 +97,7 @@ pub extern "C" fn _start() -> ! { let name = match i { 0 => "init_server", 1 => "uart_driver", + 2 => "sh", _ => "user_server", }; @@ -118,8 +128,7 @@ pub extern "C" fn _start() -> ! { } } - kprintln!("[KERNEL] Entering idle loop.\n"); - + // Idle loop: wait for interrupts and let threads execute loop { unsafe { core::arch::asm!("sti; hlt"); diff --git a/kernel/src/mm/mod.rs b/kernel/src/mm/mod.rs index 5eaf88e..a216cbc 100644 --- a/kernel/src/mm/mod.rs +++ b/kernel/src/mm/mod.rs @@ -6,9 +6,12 @@ use crate::kprintln; pub unsafe fn init(memmap: *const LimineMemmapResponse) { let hhdm = get_hhdm_offset().expect("Limine HHDM is required"); + kprintln!("[MM] HHDM OK: {:#018x}, memmap ptr: {:#018x}", hhdm, memmap as u64); let pfa_ptr = core::ptr::addr_of_mut!(pfa::PFA); - let vmm_ptr = core::ptr::addr_of_mut!(vmm::VMM); + kprintln!("[MM] Calling PFA init..."); (*pfa_ptr).init(memmap, hhdm); + kprintln!("[MM] PFA init complete. Calling VMM init..."); + let vmm_ptr = core::ptr::addr_of_mut!(vmm::VMM); (*vmm_ptr).init(hhdm); kprintln!("[MM] Memory Management Subsystem Initialized."); } diff --git a/kernel/src/mm/pfa.rs b/kernel/src/mm/pfa.rs index 2d823b3..611ad7b 100644 --- a/kernel/src/mm/pfa.rs +++ b/kernel/src/mm/pfa.rs @@ -29,28 +29,33 @@ impl FrameAllocator { pub unsafe fn init(&mut self, memmap: *const LimineMemmapResponse, hhdm: u64) { self.hhdm_offset = hhdm; if memmap.is_null() { + kprintln!("[PFA] ERROR: memmap is null!"); return; } let entry_count = (*memmap).entry_count as usize; - let entries = (*memmap).entries; + let entries_ptr = (*memmap).entries; + kprintln!("[PFA] entry_count={}, entries_ptr={:#018x}", entry_count, entries_ptr as u64); let mut max_paddr: u64 = 0; for i in 0..entry_count { - let entry = *entries.add(i); + let entry = *entries_ptr.add(i); let end = (*entry).base + (*entry).length; + kprintln!("[PFA] entry[{}]: base={:#x} len={:#x} type={}", i, (*entry).base, (*entry).length, (*entry).typ); if end > max_paddr { max_paddr = end; } } + kprintln!("[PFA] max_paddr={:#x}", max_paddr); self.total_frames = (max_paddr as usize) / PAGE_SIZE; self.bitmap_size_bytes = (self.total_frames + 7) / 8; + kprintln!("[PFA] total_frames={}, bitmap_size={} bytes", self.total_frames, self.bitmap_size_bytes); // Find a usable region large enough to place the bitmap let mut bitmap_paddr: u64 = 0; for i in 0..entry_count { - let entry = *entries.add(i); + let entry = *entries_ptr.add(i); if (*entry).typ == LimineMemoryType::Usable as u64 && (*entry).length >= self.bitmap_size_bytes as u64 { @@ -63,13 +68,15 @@ impl FrameAllocator { kprintln!("[FATAL] Could not find memory for PFA bitmap!"); return; } + kprintln!("[PFA] Bitmap at phys={:#x}, virt={:#x}", bitmap_paddr, bitmap_paddr + hhdm); self.bitmap = (bitmap_paddr + hhdm) as *mut u8; core::ptr::write_bytes(self.bitmap, 0xFF, self.bitmap_size_bytes); + kprintln!("[PFA] Bitmap zeroed OK"); self.free_frames = 0; for i in 0..entry_count { - let entry = *entries.add(i); + let entry = *entries_ptr.add(i); if (*entry).typ == LimineMemoryType::Usable as u64 { let start_frame = ((*entry).base as usize) / PAGE_SIZE; let count = ((*entry).length as usize) / PAGE_SIZE; diff --git a/kernel/src/sched/mod.rs b/kernel/src/sched/mod.rs index 0db78f9..2329dff 100644 --- a/kernel/src/sched/mod.rs +++ b/kernel/src/sched/mod.rs @@ -9,6 +9,7 @@ use crate::kprintln; extern "C" { fn switch_to(prev_rsp: *mut u64, next_rsp: u64); + fn thread_entry_trampoline(); } const DEFAULT_QUANTUM_TICKS: u32 = 5; // 5 ticks @ 100Hz = 50ms time slice @@ -62,31 +63,21 @@ impl Scheduler { self.threads[i].user_rsp = user_rsp; self.threads[i].user_rip = entry_rip; - // Build initial interrupt frame for timer_isr_entry / iretq return: - // [kstack - 5]: SS (0x1B) - // [kstack - 4]: RSP (user_rsp) - // [kstack - 3]: RFLAGS (0x202) - // [kstack - 2]: CS (0x23) - // [kstack - 1]: RIP (entry_rip) - // [kstack - 20..-6]: 15 GPRs (rax, rbx, rcx, rdx, rsi, rdi, rbp, r8..r15) - unsafe { - let kstack = kernel_stack_top as *mut u64; - let frame_ptr = kstack.sub(20); + // Build initial kernel stack frame for new thread: + // switch_to will pop r15, r14, r13, r12, rbx, rbp, then ret. + // We configure r13 = entry_rip, r12 = user_rsp, ret = thread_entry_trampoline. + unsafe { + let k = kernel_stack_top as *mut u64; + *k.sub(1) = thread_entry_trampoline as u64; // ret address + *k.sub(2) = 0; // rbp + *k.sub(3) = 0; // rbx + *k.sub(4) = user_rsp; // r12 -> user_rsp + *k.sub(5) = entry_rip; // r13 -> entry_rip + *k.sub(6) = 0; // r14 + *k.sub(7) = 0; // r15 - // GPRs (all 0) - for j in 0..15 { - *frame_ptr.add(j) = 0; - } - - // IRETQ Frame - *frame_ptr.add(15) = entry_rip; // RIP - *frame_ptr.add(16) = 0x23; // CS (User Code) - *frame_ptr.add(17) = 0x0202; // RFLAGS (IF enabled) - *frame_ptr.add(18) = user_rsp; // RSP (User Stack) - *frame_ptr.add(19) = 0x1B; // SS (User Data) - - self.threads[i].context.rsp = frame_ptr as u64; - } + self.threads[i].context.rsp = k.sub(7) as u64; + } kprintln!("[SCHED] Created Preemptible Thread [TID {}] '{}' (RIP: {:#x}, RSP: {:#x})", tid, name, entry_rip, user_rsp); @@ -131,61 +122,17 @@ impl Scheduler { } /// Preemptive tick called directly from timer ISR handler - pub fn preempt(&mut self, saved_rsp: u64) -> u64 { + pub fn preempt_tick(&mut self) { self.ticks += 1; if self.quantum_remaining > 0 { self.quantum_remaining -= 1; - if self.quantum_remaining > 0 { - return saved_rsp; // Keep running current thread - } } - // Time slice quantum expired -> Preempt current thread! - self.quantum_remaining = DEFAULT_QUANTUM_TICKS; - - let prev_idx = self.current_tid; - self.threads[prev_idx].context.rsp = saved_rsp; - if self.threads[prev_idx].state == ThreadState::Running { - self.threads[prev_idx].state = ThreadState::Ready; + if self.quantum_remaining == 0 { + self.quantum_remaining = DEFAULT_QUANTUM_TICKS; + self.schedule(); } - - // Find next Ready thread (Round-Robin) - let mut next_idx = (prev_idx + 1) % MAX_THREADS; - let mut found = false; - for _ in 0..MAX_THREADS { - if self.threads[next_idx].state == ThreadState::Ready { - found = true; - break; - } - next_idx = (next_idx + 1) % MAX_THREADS; - } - - if !found { - // Restore previous thread if it's still runnable - if self.threads[prev_idx].state == ThreadState::Ready { - self.threads[prev_idx].state = ThreadState::Running; - } - return saved_rsp; - } - - self.current_tid = next_idx; - self.threads[next_idx].state = ThreadState::Running; - - let next_pml4 = self.threads[next_idx].pml4_paddr; - let next_kstack = self.threads[next_idx].kernel_stack_top; - let next_rsp = self.threads[next_idx].context.rsp; - - unsafe { - if next_pml4 != 0 { - (*core::ptr::addr_of_mut!(VMM)).load_cr3(next_pml4); - } - if next_kstack != 0 { - set_kernel_stack(next_kstack); - } - } - - next_rsp } pub fn schedule(&mut self) { diff --git a/limine.cfg b/limine.cfg index 6f5406d..442a2f6 100644 --- a/limine.cfg +++ b/limine.cfg @@ -1,5 +1,5 @@ # Limine Bootloader Configuration (Legacy Alias) -TIMEOUT=3 +TIMEOUT=0 :opencoreC Microkernel (x86_64) PROTOCOL=limine diff --git a/limine.conf b/limine.conf index 7855ef7..48c3da7 100644 --- a/limine.conf +++ b/limine.conf @@ -1,5 +1,5 @@ # Limine Bootloader Configuration for opencoreC Microkernel -timeout: 3 +timeout: 0 default_entry: 1 /opencoreC Microkernel (x86_64) diff --git a/servers/sh/main.cpp b/servers/sh/main.cpp index ef8db27..949149c 100644 --- a/servers/sh/main.cpp +++ b/servers/sh/main.cpp @@ -202,6 +202,11 @@ static void handle_command(char *cmd) { } extern "C" void _start() { + // Let root servers finish early boot logging before taking over the terminal + for (int i = 0; i < 6; i++) { + sys_yield(); + } + sys_log_debug("\033[2J\033[H", 7); puts("==============================================================================="); puts(" opencoreC High-Performance Microkernel Operating System (x86_64) ");