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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions arch/aarch64/src/asm/boot.S
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// TEAM_422: AArch64 boot assembly with early MMU setup
// TEAM_427: Investigation of boot failures - see .teams/TEAM_427_aarch64_boot_investigation.md
// Status: Partial fix - kernel prints boot messages but crashes during memory::init()
// with L0 translation fault at FAR 0xffff800040082000
//
// Boot sequence:
// 1. Save boot registers (x0-x3) from bootloader
Expand Down Expand Up @@ -59,6 +62,13 @@ _start:
// Disable interrupts
msr daifset, #0xf

// TEAM_427: Enable FP/SIMD (required for Rust code) FIRST
// Use x9 as a scratch register to preserve x0-x3 boot registers
// Set CPACR_EL1.FPEN to 0b11 (bits 21:20) to enable EL1/EL0 access
mov x9, #(3 << 20)
msr cpacr_el1, x9
isb

// Save boot registers to physical-address symbols
// x0 = DTB address (on QEMU virt), x1-x3 = bootloader-specific
adrp x4, BOOT_REGS_PHYS
Expand All @@ -77,8 +87,10 @@ _start:
// But wait - at this point we're using the linker symbol directly
// and the literal pool is at physical address, containing the virtual address.
// We need to calculate the physical address.
movz x5, #0x8000, lsl #48 // 0xFFFF800000000000 high bits
movk x5, #0xFFFF, lsl #48
// TEAM_427: Build 0xFFFF_8000_0000_0000 correctly
// movz sets bits 63:48 to 0xFFFF, movk sets bits 47:32 to 0x8000
movz x5, #0xFFFF, lsl #48 // x5 = 0xFFFF_0000_0000_0000
movk x5, #0x8000, lsl #32 // x5 = 0xFFFF_8000_0000_0000
sub x4, x4, x5 // Convert VA to PA
mov sp, x4

Expand Down Expand Up @@ -184,7 +196,29 @@ _start:
// Load TTBR1_EL1 (higher-half map) - x10 still holds __boot_l0_ttbr1
msr ttbr1_el1, x10

// Ensure all table writes are visible before enabling MMU
// TEAM_428: Ensure page table writes are visible to MMU
// CRITICAL FIX: Must clean+invalidate the ENTIRE page table region,
// not just the entries we wrote. The zeroing loop may have left
// stale data in the cache that could corrupt the MMU's view.
//
// Page tables span 16KB (4 x 4KB tables) from __boot_l0_ttbr1 to
// __boot_page_tables_end. That's 256 cache lines (64 bytes each).
dsb sy

// Clean and invalidate entire page table region (16KB)
// x10 = __boot_l0_ttbr1 (start of page tables)
mov x3, x10 // Start address
mov x4, #256 // 256 cache lines = 16KB
.Lflush_pt_cache:
dc civac, x3 // Clean+invalidate cache line at x3
add x3, x3, #64 // Next cache line (64 bytes)
subs x4, x4, #1 // Decrement counter
b.ne .Lflush_pt_cache // Loop until done
dsb sy
isb

// Invalidate TLB before enabling MMU
tlbi vmalle1
dsb sy
isb

Expand All @@ -198,6 +232,13 @@ _start:
dsb sy
isb

// TEAM_427: Set up exception vectors BEFORE jumping to Rust
// This ensures any early exceptions are properly caught
.extern vectors
ldr x4, =vectors
msr vbar_el1, x4
isb

// === Jump to higher-half Rust entry point ===
// Now that MMU is on, we can use virtual addresses
ldr x4, =rust_main
Expand Down
60 changes: 48 additions & 12 deletions levitate/src/boot/dtb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use super::{BootInfo, BootProtocol, FirmwareInfo, MemoryKind, MemoryRegion};
use los_hal::aarch64::fdt::{self, Fdt};
use los_hal::mmu::{KERNEL_PHYS_END, KERNEL_PHYS_START};

/// TEAM_282: Parse DTB into BootInfo.
///
Expand All @@ -28,13 +29,9 @@ pub unsafe fn parse(dtb_ptr: usize) -> BootInfo {
let dtb_slice = unsafe { core::slice::from_raw_parts(dtb_ptr as *const u8, 1024 * 1024) };

if let Ok(fdt_obj) = Fdt::new(dtb_slice) {
// Extract memory regions using HAL helper
// TEAM_428: Split memory regions around kernel to avoid overwriting boot page tables
fdt::for_each_memory_region(&fdt_obj, |region| {
let _ = boot_info.memory_map.push(MemoryRegion::new(
region.start,
region.end - region.start,
MemoryKind::Usable,
));
add_memory_region_with_kernel_split(&mut boot_info, region.start, region.end);
});

// Try to find initramfs
Expand All @@ -61,13 +58,9 @@ pub fn parse_from_slice(dtb_slice: &[u8], dtb_phys: usize) -> BootInfo {
boot_info.firmware = FirmwareInfo::DeviceTree { dtb: dtb_phys };

if let Ok(fdt_obj) = Fdt::new(dtb_slice) {
// Extract memory regions using HAL helper
// TEAM_428: Split memory regions around kernel to avoid overwriting boot page tables
fdt::for_each_memory_region(&fdt_obj, |region| {
let _ = boot_info.memory_map.push(MemoryRegion::new(
region.start,
region.end - region.start,
MemoryKind::Usable,
));
add_memory_region_with_kernel_split(&mut boot_info, region.start, region.end);
});

// Try to find initramfs
Expand All @@ -84,3 +77,46 @@ pub fn parse_from_slice(dtb_slice: &[u8], dtb_phys: usize) -> BootInfo {

boot_info
}

/// TEAM_428: Add a memory region to boot_info, splitting around the kernel physical region.
///
/// This prevents the page array allocation from overlapping with the kernel and
/// boot page tables, which would cause an L0 translation fault when memory::init()
/// zeros the page array.
fn add_memory_region_with_kernel_split(boot_info: &mut BootInfo, start: usize, end: usize) {
// Check if this region overlaps with kernel
if end <= KERNEL_PHYS_START || start >= KERNEL_PHYS_END {
// No overlap - add as usable
let _ = boot_info.memory_map.push(MemoryRegion::new(
start,
end - start,
MemoryKind::Usable,
));
} else {
// Region overlaps with kernel - split into parts
// Part before kernel
if start < KERNEL_PHYS_START {
let _ = boot_info.memory_map.push(MemoryRegion::new(
start,
KERNEL_PHYS_START - start,
MemoryKind::Usable,
));
}
// Kernel region itself (reserved)
let kernel_start = start.max(KERNEL_PHYS_START);
let kernel_end = end.min(KERNEL_PHYS_END);
let _ = boot_info.memory_map.push(MemoryRegion::new(
kernel_start,
kernel_end - kernel_start,
MemoryKind::Kernel,
));
// Part after kernel
if end > KERNEL_PHYS_END {
let _ = boot_info.memory_map.push(MemoryRegion::new(
KERNEL_PHYS_END,
end - KERNEL_PHYS_END,
MemoryKind::Usable,
));
}
}
}
67 changes: 67 additions & 0 deletions levitate/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,15 @@ fn init_display() {
/// Initialize VirtIO devices (block, network, input).
fn init_devices() {
crate::virtio::init();

// TEAM_429: Register VirtIO keyboard as secondary console input
// This allows the shell to receive keyboard input from VirtIO devices
fn virtio_keyboard_input() -> Option<char> {
// Poll VirtIO input devices first to get fresh events
crate::input::poll();
crate::input::read_char()
}
los_hal::console::set_secondary_input(virtio_keyboard_input);
}

/// Initialize userspace: load and spawn init from initramfs.
Expand Down Expand Up @@ -418,10 +427,68 @@ fn init_userspace() -> bool {
*global_archive = Some(sb);
}

// Register process hooks for syscalls
register_process_hooks();

// Spawn init from initramfs
spawn_init()
}

/// Register process hooks for syscall layer.
///
/// These hooks allow the syscall layer (los_syscall) to access kernel-specific
/// functionality like initramfs and spawn_from_elf without circular dependencies.
fn register_process_hooks() {
use core::sync::atomic::Ordering;
use los_syscall::process::{
RESOLVE_EXECUTABLE_HOOK, SPAWN_FROM_ELF_HOOK, SPAWN_FROM_ELF_WITH_ARGS_HOOK,
};

// Resolver: reads ELF data from initramfs by path
fn resolve_executable(path: &str) -> Result<alloc::vec::Vec<u8>, u32> {
const ENOENT: u32 = 2; // No such file or directory

let archive_lock = crate::fs::INITRAMFS.lock();
let Some(sb) = archive_lock.as_ref() else {
return Err(ENOENT);
};

// Strip leading slashes for initramfs lookup
let name = path.trim_start_matches('/');

sb.archive
.iter()
.find(|e| e.name == name)
.map(|e| e.data.to_vec())
.ok_or(ENOENT)
}

// Spawn hook: creates a UserTask from ELF data
fn spawn_from_elf_hook(
elf_data: &[u8],
fd_table: crate::task::fd_table::SharedFdTable,
) -> Result<crate::task::user::UserTask, crate::process::SpawnError> {
crate::process::spawn_from_elf(elf_data, fd_table)
}

// Spawn with args hook: creates a UserTask with argv/envp
fn spawn_from_elf_with_args_hook(
elf_data: &[u8],
_argv: &[&str],
_envp: &[&str],
fd_table: crate::task::fd_table::SharedFdTable,
) -> Result<crate::task::user::UserTask, crate::process::SpawnError> {
// TODO: Pass argv/envp to spawn_from_elf
crate::process::spawn_from_elf(elf_data, fd_table)
}

RESOLVE_EXECUTABLE_HOOK.store(resolve_executable as *mut (), Ordering::Release);
SPAWN_FROM_ELF_HOOK.store(spawn_from_elf_hook as *mut (), Ordering::Release);
SPAWN_FROM_ELF_WITH_ARGS_HOOK.store(spawn_from_elf_with_args_hook as *mut (), Ordering::Release);

log::trace!("[BOOT] Process hooks registered");
}

/// Spawn the init process from initramfs.
fn spawn_init() -> bool {
log::info!("[BOOT] spawning init task...");
Expand Down
15 changes: 11 additions & 4 deletions levitate/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,19 @@ mod aarch64_handlers {
crate::arch::cpu::halt();
}

/// Handle IRQ dispatch
/// Handle IRQ dispatch - delegates to HAL registered handlers
#[unsafe(no_mangle)]
pub extern "C" fn handle_irq_dispatch(irq: u32) -> bool {
// TODO: Implement proper IRQ handling
log::trace!("IRQ {}", irq);
false
#[cfg(target_arch = "aarch64")]
{
los_hal::aarch64::gic::dispatch(irq)
}
#[cfg(target_arch = "x86_64")]
{
// x86_64 handles dispatch in HAL exceptions.rs directly
let _ = irq;
false
}
}

/// Check and deliver signals before returning to userspace
Expand Down
22 changes: 22 additions & 0 deletions lib/hal/src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ type SecondaryOutputFn = fn(&str);
static SECONDARY_OUTPUT: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
static SECONDARY_OUTPUT_ENABLED: AtomicBool = AtomicBool::new(false);

// TEAM_429: Secondary input callback for VirtIO keyboard
type SecondaryInputFn = fn() -> Option<char>;
static SECONDARY_INPUT: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
static SECONDARY_INPUT_ENABLED: AtomicBool = AtomicBool::new(false);

static RX_BUFFER: IrqSafeLock<RingBuffer<u8, 1024>> = IrqSafeLock::new(RingBuffer::new(0));

/// TEAM_244: Flag set when Ctrl+C (0x03) is received via serial interrupt
Expand Down Expand Up @@ -65,6 +70,17 @@ pub fn poll_for_ctrl_c() -> bool {
}

pub fn read_byte() -> Option<u8> {
// TEAM_429: Check secondary input (VirtIO keyboard) first
if SECONDARY_INPUT_ENABLED.load(Ordering::Acquire) {
let ptr = SECONDARY_INPUT.load(Ordering::Acquire);
if !ptr.is_null() {
let callback: SecondaryInputFn = unsafe { core::mem::transmute(ptr) };
if let Some(c) = callback() {
return Some(c as u8);
}
}
}

if let Some(byte) = RX_BUFFER.lock().pop() {
return Some(byte);
}
Expand All @@ -76,6 +92,12 @@ pub fn set_secondary_output(callback: SecondaryOutputFn) {
SECONDARY_OUTPUT_ENABLED.store(true, Ordering::SeqCst);
}

/// TEAM_429: Register secondary input source (e.g., VirtIO keyboard)
pub fn set_secondary_input(callback: SecondaryInputFn) {
SECONDARY_INPUT.store(callback as *mut (), Ordering::SeqCst);
SECONDARY_INPUT_ENABLED.store(true, Ordering::SeqCst);
}

#[allow(dead_code)]
pub fn disable_secondary_output() {
SECONDARY_OUTPUT_ENABLED.store(false, Ordering::SeqCst);
Expand Down
26 changes: 26 additions & 0 deletions lib/hal/src/x86_64/cpu/gdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,32 @@ pub unsafe fn init() {
};

crate::x86_64::cpu::lgdt(&ptr);

// TEAM_429: Reload segment registers after loading new GDT
// Limine leaves us with its segment selectors, which may not match our GDT.
// CS must be reloaded via far return, other segments via mov.
core::arch::asm!(
// Reload CS via far return
"push {kernel_code}", // Push new CS (0x08)
"lea rax, [rip + 2f]", // Get return address
"push rax", // Push return address
"retfq", // Far return to reload CS
"2:",
// Reload data segment registers with kernel data (0x10)
"mov ax, {kernel_data}",
"mov ds, ax",
"mov es, ax",
"mov ss, ax",
// FS and GS are used for TLS, set to 0 initially
"xor ax, ax",
"mov fs, ax",
"mov gs, ax",
kernel_code = const KERNEL_CODE as u64,
kernel_data = const KERNEL_DATA,
out("rax") _,
options(nostack)
);

crate::x86_64::cpu::ltr(TSS_SELECTOR);
}
}
Expand Down
22 changes: 22 additions & 0 deletions lib/hal/src/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,28 @@ pub fn init() {
idt::init();
exceptions::init();

// TEAM_429: Enable SSE/FPU for userspace
// Limine may have SSE enabled, but we ensure it explicitly after GDT reload.
// CR0: Clear EM (bit 2), set MP (bit 1)
// CR4: Set OSFXSR (bit 9), OSXMMEXCPT (bit 10)
unsafe {
core::arch::asm!(
// Modify CR0: clear EM (bit 2), set MP (bit 1)
"mov rax, cr0",
"and ax, 0xFFFB", // Clear EM (bit 2)
"or ax, 0x2", // Set MP (bit 1)
"mov cr0, rax",
// Modify CR4: set OSFXSR (bit 9), OSXMMEXCPT (bit 10)
"mov rax, cr4",
"or ax, 0x600", // Set bits 9 and 10
"mov cr4, rax",
// Initialize FPU
"fninit",
out("rax") _,
options(nostack)
);
}

// 3. Initialize legacy 8259 PIC
// TEAM_319: APIC MMIO (0xFEE00000) is outside HHDM range, use legacy PIC instead.
// This remaps IRQ0-7 to vectors 32-39 and unmasks IRQ0 (timer).
Expand Down
2 changes: 2 additions & 0 deletions syscall/src/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub use identity::{
pub use lifecycle::{
sys_exec, sys_exit, sys_exit_group, sys_get_foreground, sys_getpid, sys_getppid,
sys_set_foreground, sys_spawn, sys_spawn_args, sys_waitpid, sys_yield,
// TEAM_429: Hooks for kernel integration
RESOLVE_EXECUTABLE_HOOK, SPAWN_FROM_ELF_HOOK, SPAWN_FROM_ELF_WITH_ARGS_HOOK,
};
pub use resources::{sys_getrusage, sys_prlimit64};
pub use thread::{sys_clone, sys_set_tid_address};
Expand Down
Loading