feat(boot+shell): add bootable disk image, unified scheduler and interactive user-space shell

- Implement Limine v8 boot protocol requests in dedicated PT_LOAD RW segment
- Configure IOPL=3 and SSE in arch setup for Ring 3 drivers and libc
- Unify scheduler context switching via switch_to and preempt_tick
- Support dual-source keyboard/serial input in sys_key_read
- Add automated 'make image' and 'make run' targets for instant QEMU launch
- Complete interactive UNIX-like shell (osh) with built-in commands & ramdisk
This commit is contained in:
RarDog
2026-09-02 19:47:57 +03:00
parent ca3555bd09
commit eed05c1eca
17 changed files with 452 additions and 217 deletions
+1
View File
@@ -7,6 +7,7 @@
*.bin
*.iso
*.img
/boot/
# Cargo
Cargo.lock
+71 -24
View File
@@ -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"
+17 -7
View File
@@ -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
+51 -8
View File
@@ -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 = &current_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
+36 -17
View File
@@ -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
+4 -8
View File
@@ -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
+1
View File
@@ -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
+123 -26
View File
@@ -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();
}
}
+20
View File
@@ -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));
}
+66 -36
View File
@@ -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<u64> {
unsafe {
+21 -12
View File
@@ -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");
+4 -1
View File
@@ -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.");
}
+11 -4
View File
@@ -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;
+19 -72
View File
@@ -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) {
+1 -1
View File
@@ -1,5 +1,5 @@
# Limine Bootloader Configuration (Legacy Alias)
TIMEOUT=3
TIMEOUT=0
:opencoreC Microkernel (x86_64)
PROTOCOL=limine
+1 -1
View File
@@ -1,5 +1,5 @@
# Limine Bootloader Configuration for opencoreC Microkernel
timeout: 3
timeout: 0
default_entry: 1
/opencoreC Microkernel (x86_64)
+5
View File
@@ -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) ");