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
6 changes: 4 additions & 2 deletions docker/qemu/run-aarch64-boot-test-native.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ run_single_test() {
-serial file:"$OUTPUT_DIR/serial.txt" &
local QEMU_PID=$!

# Wait for kernel output (20s timeout)
# Wait for USERSPACE shell prompt (20s timeout)
# ONLY accept "breenix>" - the actual userspace shell prompt
# DO NOT accept "Interactive Shell" - that's the KERNEL FALLBACK when userspace FAILS
local BOOT_COMPLETE=false
for i in $(seq 1 10); do
if [ -f "$OUTPUT_DIR/serial.txt" ]; then
if grep -qE "(breenix>|Welcome to Breenix|Interactive Shell)" "$OUTPUT_DIR/serial.txt" 2>/dev/null; then
if grep -q "breenix>" "$OUTPUT_DIR/serial.txt" 2>/dev/null; then
BOOT_COMPLETE=true
break
fi
Expand Down
6 changes: 4 additions & 2 deletions docker/qemu/run-aarch64-boot-test-strict.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ run_single_test() {
-serial file:"$OUTPUT_DIR/serial.txt" &
local QEMU_PID=$!

# Wait for kernel output (18s max, checking every 1.5s)
# Wait for USERSPACE shell prompt (18s max, checking every 1.5s)
# ONLY accept "breenix>" - the actual userspace shell prompt
# DO NOT accept "Interactive Shell" - that's the KERNEL FALLBACK when userspace FAILS
local BOOT_COMPLETE=false
for i in $(seq 1 12); do
if [ -f "$OUTPUT_DIR/serial.txt" ]; then
if grep -qE "(breenix>|Welcome to Breenix|Interactive Shell)" "$OUTPUT_DIR/serial.txt" 2>/dev/null; then
if grep -q "breenix>" "$OUTPUT_DIR/serial.txt" 2>/dev/null; then
BOOT_COMPLETE=true
break
fi
Expand Down
8 changes: 7 additions & 1 deletion kernel/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ fn main() {
// Use our custom linker script for x86_64
// Temporarily disabled to test with bootloader's default
// println!("cargo:rustc-link-arg=-Tkernel/linker.ld");


// Use our custom linker script for ARM64
if target.contains("aarch64") {
let linker_script = kernel_dir.join("src/arch_impl/aarch64/linker.ld");
println!("cargo:rustc-link-arg=-T{}", linker_script.display());
}

// Rerun if the assembly files change
println!("cargo:rerun-if-changed=src/syscall/entry.asm");
println!("cargo:rerun-if-changed=src/interrupts/timer_entry.asm");
Expand Down
4 changes: 2 additions & 2 deletions kernel/src/arch_impl/aarch64/linker.ld
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ SECTIONS

/* BSS - zero initialized */
. = ALIGN(4K);
__bss_start = .;
.bss (NOLOAD) : AT(ADDR(.bss) - VMLINUX_OFFSET) {
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
__bss_end = .;
}
__bss_end = .;

/* Stack section - 64KB stack */
. = ALIGN(16);
Expand Down
21 changes: 5 additions & 16 deletions kernel/src/arch_impl/aarch64/syscall_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,16 @@ pub fn is_el0_confirmed() -> bool {
/// The frame must be properly aligned and contain saved register state.
#[no_mangle]
pub extern "C" fn rust_syscall_handler_aarch64(frame: &mut Aarch64ExceptionFrame) {
// Increment preempt count FIRST (prevents scheduling during syscall)
// CRITICAL: No logging before this point - timer interrupt + logger lock = deadlock
Aarch64PerCpu::preempt_disable();

// Check if this is from EL0 (userspace) by examining SPSR
let from_el0 = (frame.spsr & 0xF) == 0; // M[3:0] = 0 means EL0

// Emit EL0_CONFIRMED marker on FIRST EL0 syscall only
if from_el0 && !EL0_CONFIRMED.swap(true, Ordering::SeqCst) {
log::info!(
"EL0_CONFIRMED: First syscall received from EL0 (SPSR={:#x})",
frame.spsr
);
crate::serial_println!(
"EL0_CONFIRMED: First syscall received from EL0 (SPSR={:#x})",
frame.spsr
);
}

// Increment preempt count on syscall entry
Aarch64PerCpu::preempt_disable();

// Verify this came from userspace (security check)
if !from_el0 {
log::warn!("Syscall from kernel mode (EL1) - this shouldn't happen!");
// Don't log here - just return error
frame.set_return_value(u64::MAX); // Error
Aarch64PerCpu::preempt_enable();
return;
Expand Down
15 changes: 4 additions & 11 deletions kernel/src/ipc/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,8 @@ impl Default for FdTable {

impl Clone for FdTable {
fn clone(&self) -> Self {
// Log what we're cloning
log::debug!("FdTable::clone() - cloning fd_table with entries:");
for i in 0..10 {
if let Some(fd_entry) = self.fds[i].as_ref() {
log::debug!(" fd[{}] = {:?}", i, fd_entry.kind);
}
}

// CRITICAL: No logging here - this runs during fork() with potential timer interrupts
// Logging can cause deadlock if timer fires while holding logger lock
let cloned_fds = alloc::boxed::Box::new((*self.fds).clone());

// Increment reference counts for all cloned fds that need it
Expand All @@ -255,10 +249,9 @@ impl Clone for FdTable {
}
FdKind::PtyMaster(pty_num) => {
// Increment PTY master reference count for the clone
// No logging - this runs during fork()
if let Some(pair) = crate::tty::pty::get(*pty_num) {
let old_count = pair.master_refcount.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
log::debug!("FdTable::clone() - PTY master {} refcount {} -> {}",
pty_num, old_count, old_count + 1);
pair.master_refcount.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
}
}
FdKind::TcpConnection(conn_id) => {
Expand Down
5 changes: 3 additions & 2 deletions kernel/src/memory/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ use crate::memory::arch_stub::{OffsetPageTable, VirtAddr};
#[cfg(target_arch = "x86_64")]
pub const HEAP_START: u64 = 0x_4444_4444_0000;
#[cfg(target_arch = "aarch64")]
// ARM64 heap uses the direct-mapped region from boot.S.
// ARM64 heap uses the direct-mapped region from boot.S (TTBR1 high-half).
// The heap MUST be in TTBR1 because TTBR0 gets switched to process page tables.
//
// boot.S maps TTBR1 L1[1] = physical 0x4000_0000..0x7FFF_FFFF to virtual 0xFFFF_0000_4000_0000..
// Frame allocator uses: physical 0x4200_0000 to 0x5000_0000
// Heap must be placed AFTER the frame allocator to avoid collision!
// Physical 0x5000_0000 = virtual 0xFFFF_0000_5000_0000
pub const HEAP_START: u64 = crate::arch_impl::aarch64::constants::HHDM_BASE + 0x5000_0000;

/// Heap size of 4 MiB.
Expand Down
30 changes: 5 additions & 25 deletions kernel/src/memory/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,8 @@ pub fn allocate_stack_with_privilege(
// Calculate number of pages needed for the stack (guard page is implicit)
let stack_pages = size / 4096;

log::debug!(
"ARM64 allocate_stack: size={:#x} ({} stack pages + 1 guard)",
size,
stack_pages
);
// CRITICAL: No logging in stack allocation path - timer interrupt + logger lock = deadlock
// This function is called during process creation which may have interrupts enabled

// Allocate physical frames for the stack
// We track all frames and verify they're contiguous
Expand All @@ -340,18 +337,9 @@ pub fn allocate_stack_with_privilege(

if i == 0 {
first_frame_phys = Some(phys);
log::debug!("ARM64 stack: first frame at phys {:#x}", phys);
} else if let Some(prev) = prev_frame_phys {
// Verify frames are contiguous
if phys != prev + 4096 {
log::warn!(
"ARM64 stack: non-contiguous frames: prev={:#x}, curr={:#x}",
prev, phys
);
// For now, just use the memory anyway - it won't be truly contiguous
// but for simple stacks this should work
}
}
// Note: We don't log non-contiguous frames here - just accept them
// For simple stacks this works fine even if frames aren't contiguous
prev_frame_phys = Some(phys);

// Zero the frame via HHDM
Expand All @@ -368,17 +356,9 @@ pub fn allocate_stack_with_privilege(
// Layout: [guard page][stack pages...]
// Stack top is at the END of the last allocated frame
let allocation_start = VirtAddr::new(HHDM_BASE + stack_phys - 4096); // Guard page (unallocated)
let stack_start = VirtAddr::new(HHDM_BASE + stack_phys);
let stack_top = VirtAddr::new(HHDM_BASE + last_frame_phys + 4096);

log::debug!(
"ARM64 stack allocated: guard={:#x}, stack={:#x}-{:#x} (phys {:#x}-{:#x})",
allocation_start.as_u64(),
stack_start.as_u64(),
stack_top.as_u64(),
stack_phys,
last_frame_phys + 4096
);
// No logging here - see comment at function start

Ok(GuardedStack {
allocation_start,
Expand Down
12 changes: 3 additions & 9 deletions kernel/src/syscall/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,19 +148,13 @@ pub fn is_ring3_confirmed() -> bool {
/// See CLAUDE.md "Interrupt and Syscall Development - CRITICAL PATH REQUIREMENTS"
#[no_mangle]
pub extern "C" fn rust_syscall_handler(frame: &mut SyscallFrame) {
// CRITICAL MARKER: Emit RING3_CONFIRMED marker on FIRST Ring 3 syscall only
// This proves userspace executed and triggered INT 0x80
if (frame.cs & 3) == 3 && !RING3_CONFIRMED.swap(true, Ordering::SeqCst) {
log::info!("🎯 RING3_CONFIRMED: First syscall received from Ring 3 (CS={:#x}, RPL=3)", frame.cs);
crate::serial_println!("🎯 RING3_CONFIRMED: First syscall received from Ring 3 (CS={:#x}, RPL=3)", frame.cs);
}

// Increment preempt count on syscall entry (prevents scheduling during syscall)
// Increment preempt count FIRST (prevents scheduling during syscall)
// CRITICAL: No logging before this point - timer interrupt + logger lock = deadlock
crate::per_cpu::preempt_disable();

// Verify this came from userspace (security check)
if !frame.is_from_userspace() {
log::warn!("Syscall from kernel mode - this shouldn't happen!");
// Don't log here - just return error
frame.set_return_value(u64::MAX); // Error
crate::per_cpu::preempt_enable();
return;
Expand Down
Loading