From c81dea17bfceab42d142576924e824ae9b324ffb Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Tue, 27 Jan 2026 09:17:10 -0500 Subject: [PATCH 1/2] arm64: unify kernel heap allocator and fix userspace entry Bring ARM64 to parity with x86_64 for kernel heap allocation and fix the TTBR0 page table switch needed for userspace execution. Heap allocator changes: - Remove 256KB static bump allocator from main_aarch64.rs - Use unified heap allocator from kernel/src/memory/heap.rs - ARM64 heap placed in direct-mapped region (0xFFFF_0000_4800_0000) - Skip page mapping on ARM64 since boot.S provides direct map TTBR0/userspace fixes: - Switch TTBR0 to process page table before ERET to userspace - Add TTBR0 switching helpers in context_switch.rs - Fix ARM64 user stack mapping (map into TTBR0, not TTBR1) Supporting changes: - Fix Docker scripts to use correct kernel binary path - Update coreutils for ARM64 argv handling - Add ARM64 syscall stubs for better compatibility init_shell now boots successfully on ARM64 QEMU. Co-Authored-By: Claude Opus 4.5 --- docker/qemu/run-aarch64-interactive.sh | 27 +- docker/qemu/run-aarch64-test.sh | 4 +- docker/qemu/run-aarch64-userspace.sh | 73 ++---- kernel/build.rs | 8 +- kernel/src/arch_impl/aarch64/context.rs | 1 + .../src/arch_impl/aarch64/context_switch.rs | 55 ++++ kernel/src/arch_impl/aarch64/linker.ld | 15 +- kernel/src/arch_impl/aarch64/mmu.rs | 2 +- kernel/src/fs/ext2/mod.rs | 13 +- kernel/src/main.rs | 2 +- kernel/src/main_aarch64.rs | 71 ++---- kernel/src/memory/heap.rs | 97 ++++---- kernel/src/memory/mod.rs | 15 +- kernel/src/memory/process_memory.rs | 235 +++++++++++------- kernel/src/memory/stack.rs | 8 + kernel/src/process/manager.rs | 10 + kernel/src/syscall/fifo.rs | 14 ++ kernel/src/syscall/fs.rs | 85 ++++++- kernel/src/syscall/io.rs | 185 +------------- kernel/src/syscall/ioctl.rs | 3 + kernel/src/syscall/pipe.rs | 11 + kernel/src/syscall/pty.rs | 31 +++ kernel/src/syscall/socket.rs | 44 +++- kernel/src/syscall/wait.rs | 3 +- libs/libbreenix/Cargo.toml | 1 + libs/libbreenix/src/argv.rs | 30 ++- libs/libbreenix/src/lib.rs | 2 + libs/libbreenix/src/runtime.rs | 41 +++ scripts/create_ext2_disk.sh | 17 +- userspace/bin/coreutils/cat.rs | 28 +-- userspace/bin/coreutils/cp.rs | 24 +- userspace/bin/coreutils/echo.rs | 8 +- userspace/bin/coreutils/false.rs | 4 +- userspace/bin/coreutils/head.rs | 26 +- userspace/bin/coreutils/ls.rs | 23 +- userspace/bin/coreutils/mkdir.rs | 21 +- userspace/bin/coreutils/mv.rs | 24 +- userspace/bin/coreutils/rm.rs | 20 +- userspace/bin/coreutils/rmdir.rs | 20 +- userspace/bin/coreutils/tail.rs | 26 +- userspace/bin/coreutils/true.rs | 4 +- userspace/bin/coreutils/wc.rs | 24 +- userspace/bin/coreutils/which.rs | 30 +-- userspace/tests/Cargo.toml | 3 + userspace/tests/build-aarch64.sh | 27 +- userspace/tests/build.sh | 47 +++- 46 files changed, 803 insertions(+), 659 deletions(-) create mode 100644 libs/libbreenix/src/runtime.rs diff --git a/docker/qemu/run-aarch64-interactive.sh b/docker/qemu/run-aarch64-interactive.sh index b4514220..379ac6bb 100755 --- a/docker/qemu/run-aarch64-interactive.sh +++ b/docker/qemu/run-aarch64-interactive.sh @@ -11,21 +11,25 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BREENIX_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # Build the kernel if needed -KERNEL="$BREENIX_ROOT/target/aarch64-unknown-none/release/kernel" +KERNEL="$BREENIX_ROOT/target/aarch64-unknown-none/release/kernel-aarch64" if [ ! -f "$KERNEL" ]; then echo "Building ARM64 kernel..." - cd "$BREENIX_ROOT/kernel" - cargo build --release --target aarch64-unknown-none - cd "$BREENIX_ROOT" + cargo build --release --target aarch64-unknown-none -p kernel --bin kernel-aarch64 fi if [ ! -f "$KERNEL" ]; then echo "Error: ARM64 kernel not found at $KERNEL" echo "Try building with:" - echo " cd kernel && cargo build --release --target aarch64-unknown-none" + echo " cargo build --release --target aarch64-unknown-none -p kernel --bin kernel-aarch64" exit 1 fi +# Check for ext2 disk image +EXT2_DISK="$BREENIX_ROOT/target/ext2-aarch64.img" +if [ -f "$EXT2_DISK" ]; then + echo "Found ext2 disk: $EXT2_DISK" +fi + # Build Docker image if needed IMAGE_NAME="breenix-qemu-aarch64" if ! docker image inspect "$IMAGE_NAME" &>/dev/null; then @@ -57,10 +61,20 @@ echo "" # Run QEMU with VNC display in Docker # Port 5901 to avoid conflict with x86_64 on 5900 + +# Build disk options +DISK_VOLUME="" +DISK_OPTS="-device virtio-blk-device,drive=hd0 -drive if=none,id=hd0,format=raw,file=/dev/null" +if [ -f "$EXT2_DISK" ]; then + DISK_VOLUME="-v $EXT2_DISK:/breenix/ext2.img:ro" + DISK_OPTS="-device virtio-blk-device,drive=ext2disk -drive if=none,id=ext2disk,format=raw,readonly=on,file=/breenix/ext2.img" +fi + docker run --rm \ -p 5901:5900 \ -v "$KERNEL:/breenix/kernel:ro" \ -v "$OUTPUT_DIR:/output" \ + $DISK_VOLUME \ "$IMAGE_NAME" \ qemu-system-aarch64 \ -M virt \ @@ -70,8 +84,7 @@ docker run --rm \ -device virtio-gpu-device \ -vnc :0 \ -device virtio-keyboard-device \ - -device virtio-blk-device,drive=hd0 \ - -drive if=none,id=hd0,format=raw,file=/dev/null \ + $DISK_OPTS \ -device virtio-net-device,netdev=net0 \ -netdev user,id=net0 \ -serial file:/output/serial.txt \ diff --git a/docker/qemu/run-aarch64-test.sh b/docker/qemu/run-aarch64-test.sh index 73b68900..2d9afbc6 100755 --- a/docker/qemu/run-aarch64-test.sh +++ b/docker/qemu/run-aarch64-test.sh @@ -8,10 +8,10 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BREENIX_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # Find the ARM64 kernel -KERNEL="$BREENIX_ROOT/target/aarch64-unknown-none/release/kernel-aarch64" +KERNEL="$BREENIX_ROOT/target/aarch64-breenix/release/kernel-aarch64" if [ ! -f "$KERNEL" ]; then echo "Error: No ARM64 kernel found. Build with:" - echo " cargo build --release --target aarch64-unknown-none -p kernel --features aarch64-qemu --bin kernel-aarch64" + echo " cargo build --release --target aarch64-breenix.json -p kernel --features aarch64-qemu --bin kernel-aarch64" exit 1 fi diff --git a/docker/qemu/run-aarch64-userspace.sh b/docker/qemu/run-aarch64-userspace.sh index 8d90a196..1545286e 100755 --- a/docker/qemu/run-aarch64-userspace.sh +++ b/docker/qemu/run-aarch64-userspace.sh @@ -8,63 +8,44 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BREENIX_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # Find the ARM64 kernel -KERNEL="$BREENIX_ROOT/target/aarch64-unknown-none/release/kernel-aarch64" +KERNEL="$BREENIX_ROOT/target/aarch64-breenix/release/kernel-aarch64" if [ ! -f "$KERNEL" ]; then echo "Error: No ARM64 kernel found. Build with:" - echo " cargo build --release --target aarch64-unknown-none -p kernel --features aarch64-qemu --bin kernel-aarch64" + echo " cargo build --release --target aarch64-breenix.json -p kernel --features aarch64-qemu --bin kernel-aarch64" exit 1 fi -# Find or create ARM64 test disk -TEST_DISK="$BREENIX_ROOT/target/aarch64_test_binaries.img" -if [ ! -f "$TEST_DISK" ]; then - echo "Creating ARM64 test disk image..." - - # Create a disk image with userspace binaries - # Using a simple raw format - kernel will need to parse this - TEMP_DIR=$(mktemp -d) - - # Copy ARM64 binaries to temp dir - if [ -d "$BREENIX_ROOT/userspace/tests/aarch64" ]; then - cp "$BREENIX_ROOT/userspace/tests/aarch64/"*.elf "$TEMP_DIR/" 2>/dev/null || true - fi - - # Create a simple FAT disk image - # 4MB should be plenty for test binaries - dd if=/dev/zero of="$TEST_DISK" bs=1M count=4 - - # Format as FAT16 - if command -v mkfs.fat &>/dev/null; then - mkfs.fat -F 16 "$TEST_DISK" - # Mount and copy files - MOUNT_DIR=$(mktemp -d) - if mount -o loop "$TEST_DISK" "$MOUNT_DIR" 2>/dev/null; then - cp "$TEMP_DIR"/*.elf "$MOUNT_DIR/" 2>/dev/null || true - umount "$MOUNT_DIR" - else - echo "Note: Could not mount disk image to copy files" - echo " (This is expected on macOS - using mtools instead)" - fi - rmdir "$MOUNT_DIR" +# Find or create ARM64 ext2 disk +EXT2_DISK="$BREENIX_ROOT/target/ext2-aarch64.img" +EXT2_SIZE_BYTES=$((8 * 1024 * 1024)) +EXT2_SIZE_ACTUAL=0 +if [ -f "$EXT2_DISK" ]; then + if stat -f%z "$EXT2_DISK" >/dev/null 2>&1; then + EXT2_SIZE_ACTUAL=$(stat -f%z "$EXT2_DISK") + else + EXT2_SIZE_ACTUAL=$(stat -c %s "$EXT2_DISK") fi +fi - # On macOS, use mtools if available - if command -v mtools &>/dev/null || [ -f /opt/homebrew/bin/mformat ]; then - # Try to use mtools - mformat -i "$TEST_DISK" -F :: 2>/dev/null || true - for f in "$TEMP_DIR"/*.elf; do - [ -f "$f" ] && mcopy -i "$TEST_DISK" "$f" :: 2>/dev/null || true - done +if [ ! -f "$EXT2_DISK" ] || [ "$EXT2_SIZE_ACTUAL" -ne "$EXT2_SIZE_BYTES" ]; then + if [ -f "$EXT2_DISK" ]; then + echo "Recreating ARM64 ext2 disk (size mismatch: $EXT2_SIZE_ACTUAL bytes)" + rm -f "$EXT2_DISK" + else + echo "Creating ARM64 ext2 disk image..." fi - rm -rf "$TEMP_DIR" + "$BREENIX_ROOT/scripts/create_ext2_disk.sh" --arch aarch64 --size 8 - echo "Created: $TEST_DISK" + if [ ! -f "$EXT2_DISK" ]; then + echo "Error: Failed to create ext2 disk image at $EXT2_DISK" + exit 1 + fi fi echo "Running ARM64 kernel with userspace..." echo "Kernel: $KERNEL" -echo "Test disk: $TEST_DISK" +echo "Ext2 disk: $EXT2_DISK" # Create output directory OUTPUT_DIR="/tmp/breenix_aarch64_1" @@ -86,7 +67,7 @@ echo "Starting QEMU ARM64 with VirtIO devices..." # etc. docker run --rm \ -v "$KERNEL:/breenix/kernel:ro" \ - -v "$TEST_DISK:/breenix/test_disk.img:ro" \ + -v "$EXT2_DISK:/breenix/ext2.img:ro" \ -v "$OUTPUT_DIR:/output" \ breenix-qemu-aarch64 \ qemu-system-aarch64 \ @@ -94,8 +75,8 @@ docker run --rm \ -cpu cortex-a72 \ -m 512 \ -kernel /breenix/kernel \ - -drive if=none,id=hd0,format=raw,readonly=on,file=/breenix/test_disk.img \ - -device virtio-blk-device,drive=hd0 \ + -drive if=none,id=ext2disk,format=raw,readonly=on,file=/breenix/ext2.img \ + -device virtio-blk-device,drive=ext2disk \ -display none \ -no-reboot \ -serial file:/output/serial.txt \ diff --git a/kernel/build.rs b/kernel/build.rs index ca42f21a..68ba760a 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -59,12 +59,6 @@ fn main() { println!("cargo:rustc-link-arg={}/breakpoint_entry.o", out_dir); } - // For aarch64, use our custom linker script - if target.contains("aarch64") { - // Use ARM64-specific linker script - println!("cargo:rustc-link-arg=-T{}/src/arch_impl/aarch64/linker.ld", manifest_dir); - } - // Use our custom linker script for x86_64 // Temporarily disabled to test with bootloader's default // println!("cargo:rustc-link-arg=-Tkernel/linker.ld"); @@ -130,4 +124,4 @@ fn main() { } else { println!("cargo:warning=Userspace test directory not found at {:?}", userspace_test_dir); } -} \ No newline at end of file +} diff --git a/kernel/src/arch_impl/aarch64/context.rs b/kernel/src/arch_impl/aarch64/context.rs index 2f076436..c3f8bb79 100644 --- a/kernel/src/arch_impl/aarch64/context.rs +++ b/kernel/src/arch_impl/aarch64/context.rs @@ -34,6 +34,7 @@ pub use crate::task::thread::CpuContext; // 120 elr_el1 (exception return address) // 128 spsr_el1 (saved program status) core::arch::global_asm!(r#" +.section .text .global switch_context .type switch_context, @function switch_context: diff --git a/kernel/src/arch_impl/aarch64/context_switch.rs b/kernel/src/arch_impl/aarch64/context_switch.rs index 324673b5..eea611d0 100644 --- a/kernel/src/arch_impl/aarch64/context_switch.rs +++ b/kernel/src/arch_impl/aarch64/context_switch.rs @@ -345,6 +345,10 @@ fn setup_kernel_thread_return_arm64(thread_id: u64, frame: &mut Aarch64Exception /// Restore userspace context for a thread. fn restore_userspace_context_arm64(thread_id: u64, frame: &mut Aarch64ExceptionFrame) { + crate::serial_println!( + "ARM64: enter restore_userspace_context_arm64 thread={}", + thread_id + ); log::trace!("restore_userspace_context_arm64: thread {}", thread_id); // Check if this thread has ever run @@ -398,6 +402,9 @@ fn restore_userspace_context_arm64(thread_id: u64, frame: &mut Aarch64ExceptionF } }); + // Set TTBR0 target for this thread's process address space + set_next_ttbr0_for_thread(thread_id); + // Switch TTBR0 if needed for different address space switch_ttbr0_if_needed(thread_id); } @@ -462,6 +469,9 @@ fn setup_first_userspace_entry_arm64(thread_id: u64, frame: &mut Aarch64Exceptio ); }); + // Set TTBR0 target for this thread's process address space + set_next_ttbr0_for_thread(thread_id); + // Switch TTBR0 for this thread's address space switch_ttbr0_if_needed(thread_id); @@ -492,6 +502,12 @@ fn switch_ttbr0_if_needed(thread_id: u64) { } if current_ttbr0 != next_ttbr0 { + crate::serial_println!( + "ARM64: TTBR0 switch thread={} {:#x} -> {:#x}", + thread_id, + current_ttbr0, + next_ttbr0 + ); log::trace!( "TTBR0 switch: {:#x} -> {:#x} for thread {}", current_ttbr0, @@ -529,6 +545,45 @@ fn switch_ttbr0_if_needed(thread_id: u64) { } } +/// Determine and set the next TTBR0 value for a userspace thread. +fn set_next_ttbr0_for_thread(thread_id: u64) { + let next_ttbr0 = { + let manager_guard = crate::process::manager(); + if let Some(ref manager) = *manager_guard { + if let Some((_pid, process)) = manager.find_process_by_thread(thread_id) { + process + .page_table + .as_ref() + .map(|pt| pt.level_4_frame().start_address().as_u64()) + } else { + None + } + } else { + None + } + }; + + if let Some(ttbr0) = next_ttbr0 { + crate::serial_println!( + "ARM64: set_next_ttbr0_for_thread thread={} ttbr0={:#x}", + thread_id, + ttbr0 + ); + unsafe { + Aarch64PerCpu::set_next_cr3(ttbr0); + } + } else { + crate::serial_println!( + "ARM64: set_next_ttbr0_for_thread thread={} ttbr0=NONE", + thread_id + ); + log::error!( + "ARM64: Failed to determine TTBR0 for thread {} (process/page table missing)", + thread_id + ); + } +} + /// ARM64 idle loop - wait for interrupts. /// /// This function runs when no other threads are ready. It uses WFI diff --git a/kernel/src/arch_impl/aarch64/linker.ld b/kernel/src/arch_impl/aarch64/linker.ld index 803f15b3..62d2125f 100644 --- a/kernel/src/arch_impl/aarch64/linker.ld +++ b/kernel/src/arch_impl/aarch64/linker.ld @@ -54,22 +54,26 @@ SECTIONS } /* Main code section */ - .text : ALIGN(4K) AT(ADDR(.text) - VMLINUX_OFFSET) { + . = ALIGN(4K); + .text : AT(ADDR(.text) - VMLINUX_OFFSET) { *(.text .text.*) } /* Read-only data */ - .rodata : ALIGN(4K) AT(ADDR(.rodata) - VMLINUX_OFFSET) { + . = ALIGN(4K); + .rodata : AT(ADDR(.rodata) - VMLINUX_OFFSET) { *(.rodata .rodata.*) } /* Initialized data */ - .data : ALIGN(4K) AT(ADDR(.data) - VMLINUX_OFFSET) { + . = ALIGN(4K); + .data : AT(ADDR(.data) - VMLINUX_OFFSET) { *(.data .data.*) } /* BSS - zero initialized */ - .bss (NOLOAD) : ALIGN(4K) AT(ADDR(.bss) - VMLINUX_OFFSET) { + . = ALIGN(4K); + .bss (NOLOAD) : AT(ADDR(.bss) - VMLINUX_OFFSET) { __bss_start = .; *(.bss .bss.*) *(COMMON) @@ -77,7 +81,8 @@ SECTIONS } /* Stack section - 64KB stack */ - .bss.stack (NOLOAD) : ALIGN(16) AT(ADDR(.bss.stack) - VMLINUX_OFFSET) { + . = ALIGN(16); + .bss.stack (NOLOAD) : AT(ADDR(.bss.stack) - VMLINUX_OFFSET) { __stack_bottom = .; . = . + 65536; /* Reserve 64KB for stack */ __stack_top = .; diff --git a/kernel/src/arch_impl/aarch64/mmu.rs b/kernel/src/arch_impl/aarch64/mmu.rs index 35298697..85c8f261 100644 --- a/kernel/src/arch_impl/aarch64/mmu.rs +++ b/kernel/src/arch_impl/aarch64/mmu.rs @@ -119,7 +119,7 @@ fn l2_block_desc_user(base: u64, attr: u64) -> u64 { /// - 0x4100_0000 - 0x8000_0000: User (2MB blocks, AP=1, EL0 exec only due to implicit PXN) pub fn init() { // If MMU is already enabled, do not reprogram page tables. - let mut sctlr: u64 = 0; + let mut sctlr: u64; unsafe { core::arch::asm!("mrs {0}, sctlr_el1", out(reg) sctlr, options(nomem, nostack)); } diff --git a/kernel/src/fs/ext2/mod.rs b/kernel/src/fs/ext2/mod.rs index 7c4ba033..d885e5c2 100644 --- a/kernel/src/fs/ext2/mod.rs +++ b/kernel/src/fs/ext2/mod.rs @@ -1280,19 +1280,18 @@ static ROOT_EXT2: Mutex> = Mutex::new(None); /// Initialize the root ext2 filesystem /// -/// Mounts the ext2 disk (VirtIO block device index 2) as the root filesystem. +/// Mounts the ext2 disk as the root filesystem. /// Device layout: -/// - Device 0: UEFI boot disk -/// - Device 1: Test binaries disk -/// - Device 2: ext2 filesystem disk (testdata/ext2.img) +/// - x86_64: Device 0 UEFI boot disk, device 1 test binaries disk, device 2 ext2 disk +/// - ARM64: Device 0 ext2 disk /// /// This should be called during kernel initialization after VirtIO /// block device initialization. pub fn init_root_fs() -> Result<(), &'static str> { - // Get the ext2 disk (device index 2) - // Device 0 is the UEFI boot disk, device 1 is the test binaries disk + // Try x86_64 layout first (device index 2), then ARM64 layout (device index 0). let device = VirtioBlockWrapper::new(2) - .ok_or("No ext2 block device available (expected at device index 2)")?; + .or_else(|| VirtioBlockWrapper::new(0)) + .ok_or("No ext2 block device available (expected at device index 2 or 0)")?; let device = Arc::new(device); // Register with VFS mount system diff --git a/kernel/src/main.rs b/kernel/src/main.rs index ab5011c2..7b329320 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -2954,5 +2954,5 @@ fn test_threading() { // Non-x86_64 note: // When building for non-x86_64 (e.g., aarch64), all the code above is gated out. // The lang items (panic_handler, global_allocator, alloc_error_handler) are -// provided by main_aarch64.rs for the entire crate. +// provided by kernel::memory::heap for the entire crate. // ============================================================================= diff --git a/kernel/src/main_aarch64.rs b/kernel/src/main_aarch64.rs index eff7ed9e..a3993820 100644 --- a/kernel/src/main_aarch64.rs +++ b/kernel/src/main_aarch64.rs @@ -21,8 +21,6 @@ extern crate rlibc; // Provides memcpy, memset, etc. #[cfg(target_arch = "aarch64")] use core::panic::PanicInfo; -#[cfg(target_arch = "aarch64")] -use core::alloc::{GlobalAlloc, Layout}; // Import the kernel library macros and modules #[cfg(target_arch = "aarch64")] @@ -32,6 +30,7 @@ extern crate kernel; #[cfg(target_arch = "aarch64")] fn run_userspace_from_ext2(path: &str) -> Result { use alloc::string::String; + use core::arch::asm; use kernel::arch_impl::aarch64::context::return_to_userspace; let fs_guard = kernel::fs::ext2::root_fs(); @@ -60,7 +59,7 @@ fn run_userspace_from_ext2(path: &str) -> Result Result Result *mut u8 { - let align = layout.align(); - let size = layout.size(); - - // Use raw pointers to avoid references to mutable statics - let heap_ptr = &raw mut HEAP; - let heap_pos_ptr = &raw mut HEAP_POS; - - // Align the current position - let current_pos = *heap_pos_ptr; - let aligned_pos = (current_pos + align - 1) & !(align - 1); - let new_pos = aligned_pos + size; - - if new_pos > (*heap_ptr).len() { - // Out of memory - core::ptr::null_mut() - } else { - *heap_pos_ptr = new_pos; - (*heap_ptr).as_mut_ptr().add(aligned_pos) - } - } - - unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { - // Bump allocator doesn't support deallocation + unsafe { + asm!("msr ttbr0_el1, {0}", "isb", in(reg) ttbr0_phys, options(nostack, preserves_flags)); } + unsafe { return_to_userspace(entry_point, user_stack_top); } } -#[cfg(target_arch = "aarch64")] -#[global_allocator] -static ALLOCATOR: BumpAllocator = BumpAllocator; - -#[cfg(target_arch = "aarch64")] -#[alloc_error_handler] -fn alloc_error_handler(layout: Layout) -> ! { - panic!("allocation error: {:?}", layout) -} #[cfg(target_arch = "aarch64")] use kernel::serial; @@ -193,6 +149,7 @@ pub extern "C" fn kernel_main() -> ! { // Kernel stacks are at 0x51000000..0x52000000 (16MB) serial_println!("[boot] Initializing memory management..."); kernel::memory::frame_allocator::init_aarch64(0x4200_0000, 0x5000_0000); + kernel::memory::init_aarch64_heap(); kernel::memory::kernel_stack::init(); serial_println!("[boot] Memory management ready"); diff --git a/kernel/src/memory/heap.rs b/kernel/src/memory/heap.rs index 6b76175f..6613ab47 100644 --- a/kernel/src/memory/heap.rs +++ b/kernel/src/memory/heap.rs @@ -1,11 +1,21 @@ -#[cfg(target_arch = "x86_64")] use spin::Mutex; #[cfg(target_arch = "x86_64")] use x86_64::structures::paging::{Mapper, OffsetPageTable, Page, PageTableFlags, Size4KiB}; #[cfg(target_arch = "x86_64")] use x86_64::VirtAddr; +#[cfg(not(target_arch = "x86_64"))] +use crate::memory::arch_stub::{ + Mapper, OffsetPageTable, Page, PageTableFlags, Size4KiB, 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. +// boot.S maps TTBR1 L1[1] = physical 0x4000_0000..0x7FFF_FFFF to virtual 0xFFFF_0000_4000_0000.. +// We place heap at physical 0x4800_0000 (virtual 0xFFFF_0000_4800_0000) to avoid +// collision with frame allocator starting at 0x4200_0000. +pub const HEAP_START: u64 = crate::arch_impl::aarch64::constants::HHDM_BASE + 0x4800_0000; /// Heap size of 4 MiB. /// @@ -32,8 +42,7 @@ pub const HEAP_START: u64 = 0x_4444_4444_0000; /// are freed, so memory accumulates across the entire test run. pub const HEAP_SIZE: u64 = 32 * 1024 * 1024; -/// A simple bump allocator (x86_64 only - ARM64 uses allocator in main_aarch64.rs) -#[cfg(target_arch = "x86_64")] +/// A simple bump allocator struct BumpAllocator { heap_start: u64, heap_end: u64, @@ -41,7 +50,6 @@ struct BumpAllocator { allocations: usize, } -#[cfg(target_arch = "x86_64")] impl BumpAllocator { /// Creates a new bump allocator pub const fn new() -> Self { @@ -61,11 +69,9 @@ impl BumpAllocator { } } -/// Wrapper for the global allocator (x86_64 only) -#[cfg(target_arch = "x86_64")] +/// Wrapper for the global allocator pub struct GlobalAllocator(Mutex); -#[cfg(target_arch = "x86_64")] unsafe impl core::alloc::GlobalAlloc for GlobalAllocator { unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 { let mut allocator = self.0.lock(); @@ -97,48 +103,58 @@ unsafe impl core::alloc::GlobalAlloc for GlobalAllocator { } /// Global allocator instance -/// Only defined for x86_64 - ARM64 defines its own allocator in main_aarch64.rs -#[cfg(target_arch = "x86_64")] +/// Defined for all architectures #[global_allocator] static ALLOCATOR: GlobalAllocator = GlobalAllocator(Mutex::new(BumpAllocator::new())); /// Initialize the heap allocator -/// Only for x86_64 - ARM64 uses a simple bump allocator in main_aarch64.rs -#[cfg(target_arch = "x86_64")] pub fn init(mapper: &OffsetPageTable<'static>) -> Result<(), &'static str> { let heap_start = VirtAddr::new(HEAP_START); let heap_end = heap_start + HEAP_SIZE; - // Map heap pages - let heap_start_page = Page::::containing_address(heap_start); - let heap_end_page = Page::::containing_address(heap_end - 1u64); - - log::info!( - "Mapping heap pages from {:?} to {:?}", - heap_start_page, - heap_end_page - ); - - for page in Page::range_inclusive(heap_start_page, heap_end_page) { - let frame = crate::memory::frame_allocator::allocate_frame().ok_or("out of memory")?; - let frame_phys = frame.start_address().as_u64(); - - // Log the first few frame allocations for debugging - if frame_phys > 0xFFFF_FFFF { - log::error!("HEAP: Allocated frame {:#x} > 4GB - DMA will fail!", frame_phys); + // On x86_64, we need to map heap pages. On ARM64, boot.S sets up a direct map + // so HEAP_START is already backed by physical memory. + #[cfg(target_arch = "x86_64")] + { + let heap_start_page = Page::::containing_address(heap_start); + let heap_end_page = Page::::containing_address(heap_end - 1u64); + + log::info!( + "Mapping heap pages from {:?} to {:?}", + heap_start_page, + heap_end_page + ); + + for page in Page::range_inclusive(heap_start_page, heap_end_page) { + let frame = crate::memory::frame_allocator::allocate_frame().ok_or("out of memory")?; + + let frame_phys = frame.start_address().as_u64(); + if frame_phys > 0xFFFF_FFFF { + log::error!("HEAP: Allocated frame {:#x} > 4GB - DMA will fail!", frame_phys); + } + + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + + unsafe { + let locked_mapper = mapper as *const _ as *mut OffsetPageTable<'static>; + let mut frame_allocator = crate::memory::frame_allocator::GlobalFrameAllocator; + + (*locked_mapper) + .map_to(page, frame, flags, &mut frame_allocator) + .map_err(|_| "failed to map heap page")? + .flush(); + } } + } - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; - - unsafe { - let locked_mapper = mapper as *const _ as *mut OffsetPageTable<'static>; - let mut frame_allocator = crate::memory::frame_allocator::GlobalFrameAllocator; - - (*locked_mapper) - .map_to(page, frame, flags, &mut frame_allocator) - .map_err(|_| "failed to map heap page")? - .flush(); - } + #[cfg(target_arch = "aarch64")] + { + // ARM64: Direct map from boot.S covers heap region, no page mapping needed + let _ = (mapper, heap_end); // suppress unused warnings + log::info!( + "ARM64 heap using direct-mapped region at {:#x}", + HEAP_START + ); } // Initialize the allocator @@ -156,14 +172,11 @@ pub fn init(mapper: &OffsetPageTable<'static>) -> Result<(), &'static str> { } /// Align the given address upwards to the given alignment -#[cfg(target_arch = "x86_64")] fn align_up(addr: u64, align: u64) -> u64 { (addr + align - 1) & !(align - 1) } /// Handle allocation errors -/// Only defined for x86_64 - ARM64 defines its own handler in main_aarch64.rs -#[cfg(target_arch = "x86_64")] #[alloc_error_handler] fn alloc_error_handler(layout: core::alloc::Layout) -> ! { panic!("allocation error: {:?}", layout) diff --git a/kernel/src/memory/mod.rs b/kernel/src/memory/mod.rs index 552cf8f6..31b8b91f 100644 --- a/kernel/src/memory/mod.rs +++ b/kernel/src/memory/mod.rs @@ -36,6 +36,15 @@ pub fn init_physical_memory_offset_aarch64() { log::info!("ARM64 physical memory offset initialized (HHDM at {:#x})", hhdm_base); } +/// Initialize the ARM64 kernel heap (mapped virtual pages + global allocator) +#[cfg(target_arch = "aarch64")] +pub fn init_aarch64_heap() { + log::info!("ARM64: Initializing heap allocator..."); + let phys_offset = physical_memory_offset(); + let mapper = unsafe { paging::init(phys_offset) }; + heap::init(&mapper).expect("ARM64 heap initialization failed"); +} + /// Next available MMIO virtual address #[allow(dead_code)] // Used by map_mmio for device driver MMIO mappings static MMIO_NEXT_ADDR: Mutex = Mutex::new(layout::MMIO_BASE); @@ -101,10 +110,9 @@ pub fn init(physical_memory_offset: VirtAddr, memory_regions: &'static MemoryReg #[cfg(target_arch = "x86_64")] let mapper = unsafe { paging::init(physical_memory_offset) }; #[cfg(not(target_arch = "x86_64"))] - let _mapper = unsafe { paging::init(physical_memory_offset) }; + let mapper = unsafe { paging::init(physical_memory_offset) }; // Initialize heap - // For ARM64, the heap is initialized in main_aarch64.rs with a simple bump allocator #[cfg(target_arch = "x86_64")] { log::info!("Initializing heap allocator..."); @@ -112,7 +120,8 @@ pub fn init(physical_memory_offset: VirtAddr, memory_regions: &'static MemoryReg } #[cfg(target_arch = "aarch64")] { - log::info!("ARM64: Using bump allocator from main_aarch64.rs"); + log::info!("Initializing heap allocator..."); + heap::init(&mapper).expect("heap initialization failed"); } // Initialize stack allocation system diff --git a/kernel/src/memory/process_memory.rs b/kernel/src/memory/process_memory.rs index ce653072..b36997f8 100644 --- a/kernel/src/memory/process_memory.rs +++ b/kernel/src/memory/process_memory.rs @@ -3,6 +3,7 @@ //! This module provides per-process page tables and address space isolation. use crate::memory::frame_allocator::{allocate_frame, GlobalFrameAllocator}; +use crate::memory::layout::USER_STACK_REGION_END; #[cfg(target_arch = "x86_64")] use crate::task::thread::ThreadPrivilege; #[cfg(not(target_arch = "x86_64"))] @@ -19,7 +20,6 @@ use x86_64::{ }; #[cfg(not(target_arch = "x86_64"))] use crate::memory::arch_stub::{ - mapper::TranslateResult, Cr3, Mapper, OffsetPageTable, Page, PageTable, PageTableEntry, PageTableFlags, PhysAddr, PhysFrame, Size4KiB, Translate, VirtAddr, @@ -294,9 +294,9 @@ impl ProcessPageTable { /// - TTBR1_EL1 handles all kernel mappings automatically /// Create a new page table for ARM64 process /// - /// IMPORTANT: Since our kernel is at 0x4000_0000 (uses TTBR0, not TTBR1), - /// we MUST copy kernel mappings to each process page table. Otherwise - /// exception handlers become inaccessible after switching TTBR0. + /// IMPORTANT: The kernel is mapped in the higher half via TTBR1. + /// TTBR0 is reserved for userspace mappings only, so new process page tables + /// must start empty and allow the mapper to create L0 entries on demand. #[cfg(target_arch = "aarch64")] pub fn new() -> Result { log::debug!("ProcessPageTable::new() [ARM64] - Creating userspace page table"); @@ -319,9 +319,7 @@ impl ProcessPageTable { // Get physical memory offset let phys_offset = crate::memory::physical_memory_offset(); - // CRITICAL: Copy kernel L0 entry from current page table - // Since kernel is at 0x40000000 (TTBR0 region), we need kernel mappings - // in every process page table for exception handling to work + // Initialize a fresh TTBR0 L0 table for userspace mappings. let l0_table = unsafe { let virt = phys_offset + l0_frame.start_address().as_u64(); &mut *(virt.as_mut_ptr() as *mut PageTable) @@ -332,26 +330,9 @@ impl ProcessPageTable { l0_table[i].set_unused(); } - // Read current TTBR0 to get the boot page table - let current_ttbr0: u64; - unsafe { - core::arch::asm!("mrs {}, ttbr0_el1", out(reg) current_ttbr0, options(nomem, nostack)); - } - - // Copy L0[0] from boot page table (covers 0x0 - 0x8000_0000_0000) - // This entry points to L1 which contains both kernel and device mappings - let boot_l0_table = unsafe { - let virt = phys_offset + (current_ttbr0 & 0x0000_FFFF_FFFF_F000); - &*(virt.as_ptr() as *const PageTable) - }; - - // Copy the first L0 entry which covers kernel region - l0_table[0] = boot_l0_table[0].clone(); - - log::debug!( - "ARM64: Copied L0[0]={:#x} from boot page table for kernel access", - l0_table[0].addr().as_u64() - ); + // NOTE: Do not copy boot TTBR0 entries (kernel/device mappings). + // Userspace mappings may reside in high TTBR0 regions (e.g. L0[511]), + // and we want the mapper to allocate those tables as needed. // Create mapper for the new page table let mapper = unsafe { @@ -1131,9 +1112,10 @@ impl ProcessPageTable { // CRITICAL WORKAROUND: The OffsetPageTable might be failing during child // page table operations. Let's add extra validation. - // First, ensure we're not trying to map kernel addresses as user pages + // First, ensure we're not trying to map kernel/non-canonical addresses as user pages. + // Use the arch-specific canonical boundary instead of an x86_64 constant. let page_addr = page.start_address().as_u64(); - if page_addr >= 0x800000000000 && flags.contains(PageTableFlags::USER_ACCESSIBLE) { + if page_addr >= USER_STACK_REGION_END && flags.contains(PageTableFlags::USER_ACCESSIBLE) { log::error!( "Attempting to map kernel address {:#x} as user-accessible!", page_addr @@ -1767,78 +1749,161 @@ pub fn map_user_stack_to_process( stack_top.as_u64() ); - // Get access to the kernel page table - let kernel_mapper = unsafe { crate::memory::paging::get_mapper() }; - // Calculate page range to copy let start_page = Page::::containing_address(stack_bottom); let end_page = Page::::containing_address(stack_top - 1u64); let mut mapped_pages = 0; - // Copy each page mapping from kernel to process page table - for page in Page::range_inclusive(start_page, end_page) { - // Look up the mapping in the kernel page table - match kernel_mapper.translate(page.start_address()) { - TranslateResult::Mapped { - frame, - offset, - flags: _, - } => { - let phys_addr = frame.start_address() + offset; - let frame = PhysFrame::containing_address(phys_addr); - - // Map the same physical frame in the process page table - // Use user-accessible permissions for user stack - let flags = PageTableFlags::PRESENT - | PageTableFlags::WRITABLE - | PageTableFlags::USER_ACCESSIBLE; - - // Check if already mapped - if let Some(existing_frame) = - process_page_table.translate_page(page.start_address()) - { - let existing_frame = PhysFrame::containing_address(existing_frame); - if existing_frame == frame { - log::trace!( - "User stack page {:#x} already mapped correctly to frame {:#x}", - page.start_address().as_u64(), - frame.start_address().as_u64() - ); - mapped_pages += 1; - } else { - log::error!("User stack page {:#x} already mapped to different frame: expected {:#x}, found {:#x}", - page.start_address().as_u64(), frame.start_address().as_u64(), existing_frame.start_address().as_u64()); - return Err("User stack page already mapped to different frame"); - } - } else { - // Page not mapped, map it now - match process_page_table.map_page(page, frame, flags) { - Ok(()) => { - mapped_pages += 1; + #[cfg(target_arch = "x86_64")] + { + // Get access to the kernel page table + let kernel_mapper = unsafe { crate::memory::paging::get_mapper() }; + + // Copy each page mapping from kernel to process page table + for page in Page::range_inclusive(start_page, end_page) { + // Look up the mapping in the kernel page table + match kernel_mapper.translate(page.start_address()) { + TranslateResult::Mapped { + frame, + offset, + flags: _, + } => { + let phys_addr = frame.start_address() + offset; + let frame = PhysFrame::containing_address(phys_addr); + + // Map the same physical frame in the process page table + // Use user-accessible permissions for user stack + let flags = PageTableFlags::PRESENT + | PageTableFlags::WRITABLE + | PageTableFlags::USER_ACCESSIBLE; + + // Check if already mapped + if let Some(existing_frame) = + process_page_table.translate_page(page.start_address()) + { + let existing_frame = PhysFrame::containing_address(existing_frame); + if existing_frame == frame { log::trace!( - "Mapped user stack page {:#x} -> frame {:#x}", + "User stack page {:#x} already mapped correctly to frame {:#x}", page.start_address().as_u64(), frame.start_address().as_u64() ); + mapped_pages += 1; + } else { + log::error!("User stack page {:#x} already mapped to different frame: expected {:#x}, found {:#x}", + page.start_address().as_u64(), frame.start_address().as_u64(), existing_frame.start_address().as_u64()); + return Err("User stack page already mapped to different frame"); } - Err(e) => { - log::error!( - "Failed to map user stack page {:#x}: {}", - page.start_address().as_u64(), - e - ); - return Err("Failed to map user stack page"); + } else { + // Page not mapped, map it now + match process_page_table.map_page(page, frame, flags) { + Ok(()) => { + mapped_pages += 1; + log::trace!( + "Mapped user stack page {:#x} -> frame {:#x}", + page.start_address().as_u64(), + frame.start_address().as_u64() + ); + } + Err(e) => { + log::error!( + "Failed to map user stack page {:#x}: {}", + page.start_address().as_u64(), + e + ); + return Err("Failed to map user stack page"); + } } } } + _ => { + log::error!( + "User stack page {:#x} not mapped in kernel page table!", + page.start_address().as_u64() + ); + return Err("User stack page not found in kernel page table"); + } } - _ => { - log::error!( - "User stack page {:#x} not mapped in kernel page table!", - page.start_address().as_u64() + } + } + + #[cfg(not(target_arch = "x86_64"))] + { + // ARM64: user addresses live in TTBR0 and must be mapped directly into + // the process page table, not copied from the kernel (TTBR1) mappings. + crate::serial_println!( + "map_user_stack_to_process [ARM64]: enter mapping {:#x} - {:#x}", + start_page.start_address().as_u64(), + end_page.start_address().as_u64() + ); + let flags = PageTableFlags::PRESENT + | PageTableFlags::WRITABLE + | PageTableFlags::USER_ACCESSIBLE; + + for page in Page::range_inclusive(start_page, end_page) { + crate::serial_println!( + "map_user_stack_to_process [ARM64]: processing page {:#x}", + page.start_address().as_u64() + ); + if let Some(existing_frame) = process_page_table.translate_page(page.start_address()) { + let existing_frame = PhysFrame::::containing_address(existing_frame); + log::trace!( + "User stack page {:#x} already mapped to frame {:#x}", + page.start_address().as_u64(), + existing_frame.start_address().as_u64() + ); + crate::serial_println!( + "map_user_stack_to_process [ARM64]: page already mapped to frame {:#x}", + existing_frame.start_address().as_u64() ); - return Err("User stack page not found in kernel page table"); + mapped_pages += 1; + continue; + } + + let frame = match allocate_frame() { + Some(frame) => { + crate::serial_println!( + "map_user_stack_to_process [ARM64]: allocated frame {:#x}", + frame.start_address().as_u64() + ); + frame + } + None => { + crate::serial_println!( + "map_user_stack_to_process [ARM64]: allocate_frame failed (OOM)" + ); + return Err("Out of memory for user stack"); + } + }; + match process_page_table.map_page(page, frame, flags) { + Ok(()) => { + mapped_pages += 1; + log::trace!( + "Mapped user stack page {:#x} -> frame {:#x}", + page.start_address().as_u64(), + frame.start_address().as_u64() + ); + crate::serial_println!( + "map_user_stack_to_process [ARM64]: map_page ok {:#x} -> {:#x}", + page.start_address().as_u64(), + frame.start_address().as_u64() + ); + } + Err(e) => { + log::error!( + "Failed to map user stack page {:#x}: {}", + page.start_address().as_u64(), + e + ); + crate::serial_println!( + "map_user_stack_to_process [ARM64]: map_page FAILED {:#x} -> {:#x}: {}", + page.start_address().as_u64(), + frame.start_address().as_u64(), + e + ); + return Err("Failed to map user stack page"); + } } } } diff --git a/kernel/src/memory/stack.rs b/kernel/src/memory/stack.rs index df3c11a7..b6509520 100644 --- a/kernel/src/memory/stack.rs +++ b/kernel/src/memory/stack.rs @@ -180,6 +180,14 @@ impl GuardedStack { mapper: &mut OffsetPageTable, privilege: ThreadPrivilege, ) -> Result<(), &'static str> { + #[cfg(target_arch = "aarch64")] + if privilege == ThreadPrivilege::User { + // ARM64 user stacks live in TTBR0; defer physical mapping to the + // process page table instead of the kernel (TTBR1) mappings. + log::debug!("ARM64 user stack: deferring page mapping to process page table"); + return Ok(()); + } + let start_page = Page::::containing_address(start); let end_page = Page::::containing_address(start + size as u64 - 1u64); diff --git a/kernel/src/process/manager.rs b/kernel/src/process/manager.rs index 75b22c67..8b6db81b 100644 --- a/kernel/src/process/manager.rs +++ b/kernel/src/process/manager.rs @@ -368,12 +368,22 @@ impl ProcessManager { crate::serial_println!("manager.create_process [ARM64]: Mapping user stack into process page table"); if let Some(ref mut page_table) = process.page_table { let stack_bottom = VirtAddr::new(stack_top.as_u64() - USER_STACK_SIZE as u64); + crate::serial_println!( + "manager.create_process [ARM64]: map_user_stack_to_process stack_bottom={:#x} stack_top={:#x} size={}", + stack_bottom.as_u64(), + stack_top.as_u64(), + USER_STACK_SIZE + ); crate::memory::process_memory::map_user_stack_to_process( page_table, stack_bottom, stack_top, ) .map_err(|e| { + crate::serial_println!( + "manager.create_process [ARM64]: map_user_stack_to_process FAILED: {}", + e + ); log::error!("ARM64: Failed to map user stack to process page table: {}", e); "Failed to map user stack in process page table" })?; diff --git a/kernel/src/syscall/fifo.rs b/kernel/src/syscall/fifo.rs index a69b6f88..bfa38327 100644 --- a/kernel/src/syscall/fifo.rs +++ b/kernel/src/syscall/fifo.rs @@ -3,7 +3,9 @@ //! Implements: mkfifo (via mknod with S_IFIFO) use super::SyscallResult; +#[cfg(target_arch = "x86_64")] use super::userptr::copy_cstr_from_user; +#[cfg(target_arch = "x86_64")] use crate::ipc::fifo::FIFO_REGISTRY; /// sys_mkfifo - Create a FIFO (named pipe) @@ -14,6 +16,7 @@ use crate::ipc::fifo::FIFO_REGISTRY; /// /// # Returns /// 0 on success, negative errno on failure +#[cfg(target_arch = "x86_64")] pub fn sys_mkfifo(pathname: u64, mode: u32) -> SyscallResult { // Copy path from userspace let raw_path = match copy_cstr_from_user(pathname) { @@ -62,6 +65,7 @@ pub fn sys_mkfifo(pathname: u64, mode: u32) -> SyscallResult { /// /// # Returns /// 0 on success, negative errno on failure +#[cfg(target_arch = "x86_64")] pub fn sys_mknod(pathname: u64, mode: u32, _dev: u64) -> SyscallResult { use super::fs::S_IFMT; use super::fs::S_IFIFO; @@ -78,3 +82,13 @@ pub fn sys_mknod(pathname: u64, mode: u32, _dev: u64) -> SyscallResult { SyscallResult::Err(38) // ENOSYS } } + +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_mkfifo(_pathname: u64, _mode: u32) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} + +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_mknod(_pathname: u64, _mode: u32, _dev: u64) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} diff --git a/kernel/src/syscall/fs.rs b/kernel/src/syscall/fs.rs index 8237d3b4..7552a867 100644 --- a/kernel/src/syscall/fs.rs +++ b/kernel/src/syscall/fs.rs @@ -2,15 +2,15 @@ //! //! Implements: open, lseek, fstat, getdents64 +#[cfg(target_arch = "x86_64")] use crate::ipc::fd::FdKind; +#[cfg(target_arch = "x86_64")] use crate::arch_impl::traits::CpuOps; use super::SyscallResult; // Architecture-specific CPU type for interrupt control #[cfg(target_arch = "x86_64")] type Cpu = crate::arch_impl::x86_64::X86Cpu; -#[cfg(target_arch = "aarch64")] -type Cpu = crate::arch_impl::aarch64::Aarch64Cpu; /// Open flags (POSIX compatible) pub const O_RDONLY: u32 = 0; @@ -34,6 +34,7 @@ pub const O_DIRECTORY: u32 = 0x10000; /// /// Note: We don't instantiate this struct directly; instead we write /// the fields manually to user memory due to the variable-length d_name. +#[cfg(target_arch = "x86_64")] #[repr(C)] #[allow(dead_code)] // Documentation struct - we write fields manually pub struct LinuxDirent64 { @@ -49,51 +50,72 @@ pub struct LinuxDirent64 { } /// Size of the fixed part of LinuxDirent64 (before d_name) +#[cfg(target_arch = "x86_64")] const DIRENT64_HEADER_SIZE: usize = 19; // 8 + 8 + 2 + 1 = 19 bytes // File type constants for d_type field (Linux values) /// Unknown file type +#[cfg(target_arch = "x86_64")] pub const DT_UNKNOWN: u8 = 0; /// FIFO (named pipe) #[allow(dead_code)] // Part of dirent API +#[cfg(target_arch = "x86_64")] pub const DT_FIFO: u8 = 1; /// Character device #[allow(dead_code)] // Part of dirent API +#[cfg(target_arch = "x86_64")] pub const DT_CHR: u8 = 2; /// Directory +#[cfg(target_arch = "x86_64")] pub const DT_DIR: u8 = 4; /// Block device #[allow(dead_code)] // Part of dirent API +#[cfg(target_arch = "x86_64")] pub const DT_BLK: u8 = 6; /// Regular file +#[cfg(target_arch = "x86_64")] pub const DT_REG: u8 = 8; /// Symbolic link #[allow(dead_code)] // Part of dirent API +#[cfg(target_arch = "x86_64")] pub const DT_LNK: u8 = 10; /// Socket #[allow(dead_code)] // Part of dirent API +#[cfg(target_arch = "x86_64")] pub const DT_SOCK: u8 = 12; /// Seek whence values +#[cfg(target_arch = "x86_64")] pub const SEEK_SET: i32 = 0; +#[cfg(target_arch = "x86_64")] pub const SEEK_CUR: i32 = 1; +#[cfg(target_arch = "x86_64")] pub const SEEK_END: i32 = 2; /// File type mode constants (POSIX S_IFMT values) #[allow(dead_code)] // Part of POSIX stat API +#[cfg(target_arch = "x86_64")] pub const S_IFMT: u32 = 0o170000; // File type mask +#[cfg(target_arch = "x86_64")] pub const S_IFSOCK: u32 = 0o140000; // Socket #[allow(dead_code)] // Part of POSIX stat API +#[cfg(target_arch = "x86_64")] pub const S_IFLNK: u32 = 0o120000; // Symbolic link +#[cfg(target_arch = "x86_64")] pub const S_IFREG: u32 = 0o100000; // Regular file #[allow(dead_code)] // Part of POSIX stat API +#[cfg(target_arch = "x86_64")] pub const S_IFBLK: u32 = 0o060000; // Block device #[allow(dead_code)] // Part of POSIX stat API +#[cfg(target_arch = "x86_64")] pub const S_IFDIR: u32 = 0o040000; // Directory +#[cfg(target_arch = "x86_64")] pub const S_IFCHR: u32 = 0o020000; // Character device +#[cfg(target_arch = "x86_64")] pub const S_IFIFO: u32 = 0o010000; // FIFO (pipe) /// stat structure (Linux x86_64 compatible) +#[cfg(target_arch = "x86_64")] #[repr(C)] pub struct Stat { pub st_dev: u64, @@ -116,6 +138,7 @@ pub struct Stat { _reserved: [i64; 3], } +#[cfg(target_arch = "x86_64")] impl Stat { /// Create a zeroed Stat structure pub fn zeroed() -> Self { @@ -151,6 +174,7 @@ impl Stat { /// /// # Returns /// File descriptor on success, negative errno on failure +#[cfg(target_arch = "x86_64")] pub fn sys_open(pathname: u64, flags: u32, mode: u32) -> SyscallResult { use super::errno::{EACCES, EEXIST, EISDIR, EMFILE, ENOENT, ENOSPC, ENOTDIR}; use super::userptr::copy_cstr_from_user; @@ -438,6 +462,11 @@ pub fn sys_open(pathname: u64, flags: u32, mode: u32) -> SyscallResult { } } +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_open(_pathname: u64, _flags: u32, _mode: u32) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} + /// sys_lseek - Reposition file offset /// /// # Arguments @@ -447,6 +476,7 @@ pub fn sys_open(pathname: u64, flags: u32, mode: u32) -> SyscallResult { /// /// # Returns /// New file position on success, negative errno on failure +#[cfg(target_arch = "x86_64")] pub fn sys_lseek(fd: i32, offset: i64, whence: i32) -> SyscallResult { // Get current process let thread_id = match crate::task::scheduler::current_thread_id() { @@ -510,6 +540,11 @@ pub fn sys_lseek(fd: i32, offset: i64, whence: i32) -> SyscallResult { } } +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_lseek(_fd: i32, _offset: i64, _whence: i32) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} + /// sys_fstat - Get file status /// /// # Arguments @@ -518,6 +553,7 @@ pub fn sys_lseek(fd: i32, offset: i64, whence: i32) -> SyscallResult { /// /// # Returns /// 0 on success, negative errno on failure +#[cfg(target_arch = "x86_64")] pub fn sys_fstat(fd: i32, statbuf: u64) -> SyscallResult { use super::errno::{EBADF, EFAULT}; use core::ptr; @@ -702,12 +738,19 @@ pub fn sys_fstat(fd: i32, statbuf: u64) -> SyscallResult { SyscallResult::Ok(0) } +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_fstat(_fd: i32, _statbuf: u64) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} + /// Helper to create device ID from major/minor numbers +#[cfg(target_arch = "x86_64")] fn make_dev(major: u64, minor: u64) -> u64 { (major << 8) | (minor & 0xff) } /// Inode metadata from ext2 filesystem +#[cfg(target_arch = "x86_64")] struct InodeStat { mode: u32, uid: u32, @@ -723,6 +766,7 @@ struct InodeStat { /// Load inode metadata from ext2 filesystem /// /// Returns None if the ext2 filesystem is not available or inode cannot be read. +#[cfg(target_arch = "x86_64")] fn load_ext2_inode_stat(inode_num: u64) -> Option { use crate::fs::ext2; @@ -759,6 +803,7 @@ fn load_ext2_inode_stat(inode_num: u64) -> Option { /// Get file size from ext2 inode /// /// Returns None if the ext2 filesystem is not available or inode cannot be read. +#[cfg(target_arch = "x86_64")] fn get_ext2_file_size(inode_num: u64) -> Option { use crate::fs::ext2; @@ -773,6 +818,7 @@ fn get_ext2_file_size(inode_num: u64) -> Option { } /// Convert ext2 file type to Linux dirent d_type +#[cfg(target_arch = "x86_64")] fn ext2_file_type_to_dt(ext2_type: u8) -> u8 { use crate::fs::ext2::dir; match ext2_type { @@ -788,6 +834,7 @@ fn ext2_file_type_to_dt(ext2_type: u8) -> u8 { } /// Align a value up to the nearest multiple of 8 +#[cfg(target_arch = "x86_64")] fn align_up_8(value: usize) -> usize { (value + 7) & !7 } @@ -805,6 +852,7 @@ fn align_up_8(value: usize) -> usize { /// * On success: Number of bytes written to the buffer /// * On success with no more entries: 0 /// * On error: Negative errno +#[cfg(target_arch = "x86_64")] pub fn sys_getdents64(fd: i32, dirp: u64, count: u64) -> SyscallResult { use super::errno::{EBADF, EFAULT, EINVAL, EIO, ENOTDIR}; use crate::fs::ext2::{self, dir::DirReader}; @@ -990,6 +1038,11 @@ pub fn sys_getdents64(fd: i32, dirp: u64, count: u64) -> SyscallResult { SyscallResult::Ok(bytes_written as u64) } +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_getdents64(_fd: i32, _dirp: u64, _count: u64) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} + /// sys_unlink - Delete a file /// /// Removes a directory entry for the specified pathname. If this is the @@ -1010,7 +1063,6 @@ pub fn sys_unlink(pathname: u64) -> SyscallResult { use super::errno::{EACCES, EIO, EISDIR, ENOENT}; use super::userptr::copy_cstr_from_user; use crate::fs::ext2; - use crate::ipc::fifo::FIFO_REGISTRY; // Copy path from userspace let raw_path = match copy_cstr_from_user(pathname) { @@ -1034,14 +1086,18 @@ pub fn sys_unlink(pathname: u64) -> SyscallResult { log::debug!("sys_unlink: path={:?}", path); // Check if this is a FIFO - if so, remove from registry - if FIFO_REGISTRY.exists(&path) { - match FIFO_REGISTRY.unlink(&path) { - Ok(()) => { - log::info!("sys_unlink: successfully unlinked FIFO {}", path); - return SyscallResult::Ok(0); - } - Err(errno) => { - return SyscallResult::Err(errno as u64); + #[cfg(target_arch = "x86_64")] + { + use crate::ipc::fifo::FIFO_REGISTRY; + if FIFO_REGISTRY.exists(&path) { + match FIFO_REGISTRY.unlink(&path) { + Ok(()) => { + log::info!("sys_unlink: successfully unlinked FIFO {}", path); + return SyscallResult::Ok(0); + } + Err(errno) => { + return SyscallResult::Err(errno as u64); + } } } } @@ -1664,6 +1720,7 @@ pub fn sys_access(pathname: u64, mode: u32) -> SyscallResult { /// /// # Returns /// File descriptor on success, negative errno on failure +#[cfg(target_arch = "x86_64")] fn handle_devfs_open(device_name: &str, _flags: u32) -> SyscallResult { use super::errno::{EMFILE, ENOENT}; use crate::fs::devfs; @@ -1735,6 +1792,7 @@ fn handle_devfs_open(device_name: &str, _flags: u32) -> SyscallResult { /// /// # Returns /// File descriptor on success, negative errno on failure +#[cfg(target_arch = "x86_64")] fn handle_devpts_open(pty_name: &str) -> SyscallResult { use super::errno::{EMFILE, ENOENT}; use crate::fs::devptsfs; @@ -1791,6 +1849,7 @@ fn handle_devpts_open(pty_name: &str) -> SyscallResult { /// Handle opening the /dev/pts directory itself /// /// Returns a directory fd that can be used with getdents64 to list PTY slaves. +#[cfg(target_arch = "x86_64")] fn handle_devpts_directory_open() -> SyscallResult { use super::errno::EMFILE; @@ -1840,6 +1899,7 @@ fn handle_devpts_directory_open() -> SyscallResult { /// /// # Arguments /// * `_flags` - Open flags (O_DIRECTORY expected) +#[cfg(target_arch = "x86_64")] fn handle_devfs_directory_open(_flags: u32) -> SyscallResult { use super::errno::EMFILE; @@ -1886,6 +1946,7 @@ fn handle_devfs_directory_open(_flags: u32) -> SyscallResult { /// Handle getdents64 for the /dev directory /// /// Returns virtual directory entries for all registered devices. +#[cfg(target_arch = "x86_64")] fn handle_devfs_getdents64( fd: i32, dirp: u64, @@ -2034,6 +2095,7 @@ fn handle_devfs_getdents64( /// Handle getdents64 for the /dev/pts directory /// /// Returns virtual directory entries for all active and unlocked PTY slaves. +#[cfg(target_arch = "x86_64")] fn handle_devpts_getdents64( fd: i32, dirp: u64, @@ -2471,6 +2533,7 @@ pub fn get_current_cwd() -> Option { /// /// # Returns /// File descriptor on success, negative errno on failure +#[cfg(target_arch = "x86_64")] fn handle_fifo_open(path: &str, flags: u32) -> SyscallResult { use super::errno::EMFILE; use crate::ipc::fd::{FdKind, FileDescriptor, status_flags}; diff --git a/kernel/src/syscall/io.rs b/kernel/src/syscall/io.rs index 0febe21a..f1c6b81f 100644 --- a/kernel/src/syscall/io.rs +++ b/kernel/src/syscall/io.rs @@ -75,14 +75,9 @@ pub fn sys_write(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { enum WriteOperation { StdIo, Pipe { pipe_buffer: alloc::sync::Arc>, is_nonblocking: bool }, - Fifo { pipe_buffer: alloc::sync::Arc>, is_nonblocking: bool }, UnixStream { socket: alloc::sync::Arc> }, - RegularFile { file: alloc::sync::Arc> }, - TcpConnection { conn_id: crate::net::tcp::ConnectionId }, - Device { device_type: crate::fs::devfs::DeviceType }, Ebadf, Enotconn, - Eisdir, Eopnotsupp, } @@ -110,23 +105,10 @@ pub fn sys_write(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { WriteOperation::Pipe { pipe_buffer: pipe_buffer.clone(), is_nonblocking: (fd_entry.status_flags & crate::ipc::fd::status_flags::O_NONBLOCK) != 0 } } FdKind::PipeRead(_) => WriteOperation::Ebadf, - FdKind::FifoWrite(_path, pipe_buffer) => { - WriteOperation::Fifo { pipe_buffer: pipe_buffer.clone(), is_nonblocking: (fd_entry.status_flags & crate::ipc::fd::status_flags::O_NONBLOCK) != 0 } - } - FdKind::FifoRead(_, _) => WriteOperation::Ebadf, - FdKind::TcpSocket(_) => WriteOperation::Enotconn, - FdKind::TcpListener(_) => WriteOperation::Enotconn, - FdKind::TcpConnection(conn_id) => WriteOperation::TcpConnection { conn_id: *conn_id }, FdKind::UdpSocket(_) => WriteOperation::Eopnotsupp, FdKind::UnixStream(socket) => WriteOperation::UnixStream { socket: socket.clone() }, FdKind::UnixSocket(_) => WriteOperation::Enotconn, FdKind::UnixListener(_) => WriteOperation::Enotconn, - FdKind::RegularFile(file) => WriteOperation::RegularFile { file: file.clone() }, - FdKind::Directory(_) => WriteOperation::Eisdir, - FdKind::Device(device_type) => WriteOperation::Device { device_type: device_type.clone() }, - FdKind::DevfsDirectory { .. } => WriteOperation::Eisdir, - FdKind::DevptsDirectory { .. } => WriteOperation::Eisdir, - FdKind::PtyMaster(_) | FdKind::PtySlave(_) => WriteOperation::Eopnotsupp, } }; @@ -134,7 +116,6 @@ pub fn sys_write(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { WriteOperation::StdIo => write_to_stdio(fd, &buffer), WriteOperation::Ebadf => SyscallResult::Err(9), WriteOperation::Enotconn => SyscallResult::Err(super::errno::ENOTCONN as u64), - WriteOperation::Eisdir => SyscallResult::Err(super::errno::EISDIR as u64), WriteOperation::Eopnotsupp => SyscallResult::Err(95), WriteOperation::Pipe { pipe_buffer, is_nonblocking } => { let mut pipe = pipe_buffer.lock(); @@ -144,14 +125,6 @@ pub fn sys_write(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { Err(e) => SyscallResult::Err(e as u64), } } - WriteOperation::Fifo { pipe_buffer, is_nonblocking } => { - let mut pipe = pipe_buffer.lock(); - match pipe.write(&buffer) { - Ok(n) => SyscallResult::Ok(n as u64), - Err(11) if !is_nonblocking => SyscallResult::Err(11), - Err(e) => SyscallResult::Err(e as u64), - } - } WriteOperation::UnixStream { socket } => { let sock = socket.lock(); match sock.write(&buffer) { @@ -159,65 +132,6 @@ pub fn sys_write(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { Err(e) => SyscallResult::Err(e as u64), } } - WriteOperation::TcpConnection { conn_id } => { - match crate::net::tcp::tcp_send(&conn_id, &buffer) { - Ok(n) => SyscallResult::Ok(n as u64), - Err(e) => { - if e.contains("shutdown") { - SyscallResult::Err(super::errno::EPIPE as u64) - } else { - SyscallResult::Err(super::errno::EIO as u64) - } - } - } - } - WriteOperation::Device { device_type } => { - use crate::fs::devfs::DeviceType; - match device_type { - DeviceType::Null | DeviceType::Zero => SyscallResult::Ok(buffer.len() as u64), - DeviceType::Console | DeviceType::Tty => write_to_stdio(fd, &buffer), - } - } - WriteOperation::RegularFile { file } => { - let (inode_num, position, flags) = { - let file_guard = file.lock(); - (file_guard.inode_num, file_guard.position, file_guard.flags) - }; - - let write_offset = if (flags & crate::syscall::fs::O_APPEND) != 0 { - let root_fs = crate::fs::ext2::root_fs(); - let fs = match root_fs.as_ref() { - Some(fs) => fs, - None => return SyscallResult::Err(super::errno::ENOSYS as u64), - }; - match fs.read_inode(inode_num as u32) { - Ok(inode) => inode.size(), - Err(_) => return SyscallResult::Err(super::errno::EIO as u64), - } - } else { - position - }; - - let mut root_fs = crate::fs::ext2::root_fs(); - let fs = match root_fs.as_mut() { - Some(fs) => fs, - None => return SyscallResult::Err(super::errno::ENOSYS as u64), - }; - - let bytes_written = match fs.write_file_range(inode_num as u32, write_offset, &buffer) { - Ok(n) => n, - Err(_) => return SyscallResult::Err(super::errno::EIO as u64), - }; - - drop(root_fs); - - { - let mut file_guard = file.lock(); - file_guard.position = write_offset + bytes_written as u64; - } - - SyscallResult::Ok(bytes_written as u64) - } } } @@ -271,9 +185,7 @@ pub fn sys_read(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { return SyscallResult::Ok(n as u64); } Err(11) => { - crate::task::scheduler::with_scheduler(|sched| { - sched.block_current_for_stdin_read(); - }); + // ARM64: no scheduler unblock path for stdin yet; wait for data. loop { if crate::ipc::stdin::has_data() { break; @@ -291,20 +203,7 @@ pub fn sys_read(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { FdKind::StdIo(_) => SyscallResult::Err(9), FdKind::PipeRead(pipe_buffer) => { let mut pipe = pipe_buffer.lock(); - let mut buf = vec![0u8; count as usize]; - match pipe.read(&mut buf) { - Ok(n) => { - if copy_to_user_bytes(buf_ptr, &buf[..n]).is_err() { - return SyscallResult::Err(14); - } - SyscallResult::Ok(n as u64) - } - Err(e) => SyscallResult::Err(e as u64), - } - } - FdKind::FifoRead(_path, pipe_buffer) => { - let mut pipe = pipe_buffer.lock(); - let mut buf = vec![0u8; count as usize]; + let mut buf = alloc::vec![0u8; count as usize]; match pipe.read(&mut buf) { Ok(n) => { if copy_to_user_bytes(buf_ptr, &buf[..n]).is_err() { @@ -315,13 +214,13 @@ pub fn sys_read(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { Err(e) => SyscallResult::Err(e as u64), } } - FdKind::PipeWrite(_) | FdKind::FifoWrite(_, _) => SyscallResult::Err(9), - FdKind::UdpSocket(_) | FdKind::TcpSocket(_) | FdKind::UnixSocket(_) | FdKind::UnixListener(_) | FdKind::TcpListener(_) => { + FdKind::PipeWrite(_) => SyscallResult::Err(9), + FdKind::UdpSocket(_) | FdKind::UnixSocket(_) | FdKind::UnixListener(_) => { SyscallResult::Err(super::errno::ENOTCONN as u64) } FdKind::UnixStream(socket) => { - let mut sock = socket.lock(); - let mut buf = vec![0u8; count as usize]; + let sock = socket.lock(); + let mut buf = alloc::vec![0u8; count as usize]; match sock.read(&mut buf) { Ok(n) => { if copy_to_user_bytes(buf_ptr, &buf[..n]).is_err() { @@ -332,67 +231,6 @@ pub fn sys_read(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { Err(e) => SyscallResult::Err(e as u64), } } - FdKind::TcpConnection(conn_id) => { - let mut buf = vec![0u8; count as usize]; - match crate::net::tcp::tcp_recv(conn_id, &mut buf) { - Ok(n) => { - if copy_to_user_bytes(buf_ptr, &buf[..n]).is_err() { - return SyscallResult::Err(14); - } - SyscallResult::Ok(n as u64) - } - Err(e) => SyscallResult::Err(e as u64), - } - } - FdKind::RegularFile(file) => { - let (inode_num, position) = { - let file_guard = file.lock(); - (file_guard.inode_num, file_guard.position) - }; - - let mut root_fs = crate::fs::ext2::root_fs(); - let fs = match root_fs.as_mut() { - Some(fs) => fs, - None => return SyscallResult::Err(super::errno::ENOSYS as u64), - }; - - let mut buf = vec![0u8; count as usize]; - let bytes_read = match fs.read_file_range(inode_num as u32, position, &mut buf) { - Ok(n) => n, - Err(_) => return SyscallResult::Err(super::errno::EIO as u64), - }; - - drop(root_fs); - - { - let mut file_guard = file.lock(); - file_guard.position = position + bytes_read as u64; - } - - if copy_to_user_bytes(buf_ptr, &buf[..bytes_read]).is_err() { - return SyscallResult::Err(14); - } - - SyscallResult::Ok(bytes_read as u64) - } - FdKind::Directory(_) | FdKind::DevfsDirectory { .. } | FdKind::DevptsDirectory { .. } => { - SyscallResult::Err(super::errno::EISDIR as u64) - } - FdKind::Device(device_type) => { - use crate::fs::devfs::DeviceType; - match device_type { - DeviceType::Null => SyscallResult::Ok(0), - DeviceType::Zero => { - let buf = vec![0u8; count as usize]; - if copy_to_user_bytes(buf_ptr, &buf).is_err() { - return SyscallResult::Err(14); - } - SyscallResult::Ok(count) - } - DeviceType::Console | DeviceType::Tty => SyscallResult::Err(9), - } - } - FdKind::PtyMaster(_) | FdKind::PtySlave(_) => SyscallResult::Err(95), } } @@ -484,19 +322,14 @@ pub fn sys_fcntl(fd: u64, cmd: u64, arg: u64) -> SyscallResult { match cmd { F_DUPFD => { - match process.fd_table.dup_min(fd, arg) { + match process.fd_table.dup_at_least(fd, arg, false) { Ok(new_fd) => SyscallResult::Ok(new_fd as u64), Err(e) => SyscallResult::Err(e as u64), } } F_DUPFD_CLOEXEC => { - match process.fd_table.dup_min(fd, arg) { - Ok(new_fd) => { - if let Some(entry) = process.fd_table.get_mut(new_fd) { - entry.flags |= crate::ipc::fd::flags::FD_CLOEXEC; - } - SyscallResult::Ok(new_fd as u64) - } + match process.fd_table.dup_at_least(fd, arg, true) { + Ok(new_fd) => SyscallResult::Ok(new_fd as u64), Err(e) => SyscallResult::Err(e as u64), } } diff --git a/kernel/src/syscall/ioctl.rs b/kernel/src/syscall/ioctl.rs index 65fa2e17..c88211fa 100644 --- a/kernel/src/syscall/ioctl.rs +++ b/kernel/src/syscall/ioctl.rs @@ -49,7 +49,10 @@ pub fn sys_ioctl(fd: u64, request: u64, arg: u64) -> SyscallResult { // First, try to look up the fd in the process's fd table // to check if it's a PTY device if let Some((fd_kind, pid)) = get_fd_kind_and_pid(fd as i32) { + #[cfg(not(target_arch = "x86_64"))] + let _ = pid; match fd_kind { + #[cfg(target_arch = "x86_64")] FdKind::PtyMaster(pty_num) | FdKind::PtySlave(pty_num) => { // Dispatch to PTY ioctl handler let pair = match crate::tty::pty::get(pty_num) { diff --git a/kernel/src/syscall/pipe.rs b/kernel/src/syscall/pipe.rs index 173917f3..0c258ce4 100644 --- a/kernel/src/syscall/pipe.rs +++ b/kernel/src/syscall/pipe.rs @@ -162,35 +162,43 @@ pub fn sys_close(fd: i32) -> SyscallResult { // to remain bound until all references are closed. log::debug!("sys_close: Closed UDP socket fd={}", fd); } + #[cfg(target_arch = "x86_64")] FdKind::RegularFile(_) => { // Regular file cleanup handled by Arc refcount log::debug!("sys_close: Closed regular file fd={}", fd); } + #[cfg(target_arch = "x86_64")] FdKind::Directory(_) => { // Directory cleanup handled by Arc refcount log::debug!("sys_close: Closed directory fd={}", fd); } + #[cfg(target_arch = "x86_64")] FdKind::Device(_) => { // Device files don't need cleanup log::debug!("sys_close: Closed device fd={}", fd); } + #[cfg(target_arch = "x86_64")] FdKind::DevfsDirectory { .. } => { // Devfs directory doesn't need cleanup log::debug!("sys_close: Closed devfs directory fd={}", fd); } + #[cfg(target_arch = "x86_64")] FdKind::DevptsDirectory { .. } => { // Devpts directory doesn't need cleanup log::debug!("sys_close: Closed devpts directory fd={}", fd); } + #[cfg(target_arch = "x86_64")] FdKind::TcpSocket(_) | FdKind::TcpListener(_) => { // Unbound/listening TCP socket doesn't need special cleanup log::debug!("sys_close: Closed TCP socket fd={}", fd); } + #[cfg(target_arch = "x86_64")] FdKind::TcpConnection(conn_id) => { // Close the TCP connection let _ = crate::net::tcp::tcp_close(&conn_id); log::debug!("sys_close: Closed TCP connection fd={}", fd); } + #[cfg(target_arch = "x86_64")] FdKind::PtyMaster(pty_num) => { // PTY master cleanup - decrement refcount, only release when all masters closed if let Some(pair) = crate::tty::pty::get(pty_num) { @@ -206,6 +214,7 @@ pub fn sys_close(fd: i32) -> SyscallResult { log::warn!("sys_close: PTY {} not found for master fd={}", pty_num, fd); } } + #[cfg(target_arch = "x86_64")] FdKind::PtySlave(pty_num) => { // PTY slave doesn't own the pair, just log closure log::debug!("sys_close: Closed PTY slave fd={} (pty {})", fd, pty_num); @@ -223,12 +232,14 @@ pub fn sys_close(fd: i32) -> SyscallResult { // Unix listener socket cleanup handled by Arc refcount log::debug!("sys_close: Closed Unix listener fd={}", fd); } + #[cfg(target_arch = "x86_64")] FdKind::FifoRead(path, buffer) => { // Close FIFO read end - decrement both FIFO entry and pipe buffer counts crate::ipc::fifo::close_fifo_read(&path); buffer.lock().close_read(); log::debug!("sys_close: Closed FIFO read end fd={} ({})", fd, path); } + #[cfg(target_arch = "x86_64")] FdKind::FifoWrite(path, buffer) => { // Close FIFO write end - decrement both FIFO entry and pipe buffer counts crate::ipc::fifo::close_fifo_write(&path); diff --git a/kernel/src/syscall/pty.rs b/kernel/src/syscall/pty.rs index 0291c4c8..3b591284 100644 --- a/kernel/src/syscall/pty.rs +++ b/kernel/src/syscall/pty.rs @@ -2,15 +2,22 @@ //! //! Implements the POSIX PTY syscalls: posix_openpt, grantpt, unlockpt, ptsname +#[cfg(target_arch = "x86_64")] use super::errno::ENOTTY; +#[cfg(target_arch = "x86_64")] use super::userptr::validate_user_buffer; use super::SyscallResult; +#[cfg(target_arch = "x86_64")] use crate::ipc::fd::{flags, FileDescriptor, FdKind}; +#[cfg(target_arch = "x86_64")] use crate::process::manager; +#[cfg(target_arch = "x86_64")] use crate::tty::pty; /// Open flags +#[cfg(target_arch = "x86_64")] const O_RDWR: u32 = 0x02; +#[cfg(target_arch = "x86_64")] const O_CLOEXEC: u32 = 0x80000; /// sys_posix_openpt - Open a new PTY master device @@ -28,6 +35,7 @@ const O_CLOEXEC: u32 = 0x80000; /// - EMFILE (24): Too many open files /// - ENOSPC (28): No PTY slots available /// - ESRCH (3): Process not found +#[cfg(target_arch = "x86_64")] pub fn sys_posix_openpt(flags: u64) -> SyscallResult { let flags_u32 = flags as u32; @@ -120,6 +128,7 @@ pub fn sys_posix_openpt(flags: u64) -> SyscallResult { /// - EBADF (9): Bad file descriptor /// - ENOTTY (25): fd is not a PTY master /// - ESRCH (3): Process not found +#[cfg(target_arch = "x86_64")] pub fn sys_grantpt(fd: u64) -> SyscallResult { let fd_i32 = fd as i32; @@ -184,6 +193,7 @@ pub fn sys_grantpt(fd: u64) -> SyscallResult { /// - ENOTTY (25): fd is not a PTY master /// - EIO (5): PTY pair not found (internal error) /// - ESRCH (3): Process not found +#[cfg(target_arch = "x86_64")] pub fn sys_unlockpt(fd: u64) -> SyscallResult { let fd_i32 = fd as i32; @@ -260,6 +270,7 @@ pub fn sys_unlockpt(fd: u64) -> SyscallResult { /// - EFAULT (14): Invalid buffer pointer /// - EIO (5): PTY pair not found (internal error) /// - ESRCH (3): Process not found +#[cfg(target_arch = "x86_64")] pub fn sys_ptsname(fd: u64, buf: u64, buflen: u64) -> SyscallResult { let fd_i32 = fd as i32; @@ -357,3 +368,23 @@ pub fn sys_ptsname(fd: u64, buf: u64, buflen: u64) -> SyscallResult { } } } + +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_posix_openpt(_flags: u64) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} + +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_grantpt(_fd: u64) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} + +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_unlockpt(_fd: u64) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} + +#[cfg(not(target_arch = "x86_64"))] +pub fn sys_ptsname(_fd: u64, _buf: u64, _buflen: u64) -> SyscallResult { + SyscallResult::Err(super::errno::ENOSYS as u64) +} diff --git a/kernel/src/syscall/socket.rs b/kernel/src/syscall/socket.rs index 1b929718..6242bdd8 100644 --- a/kernel/src/syscall/socket.rs +++ b/kernel/src/syscall/socket.rs @@ -4,8 +4,10 @@ use super::errno::{ EAFNOSUPPORT, EAGAIN, EBADF, EFAULT, EINVAL, ENOTSOCK, EADDRINUSE, EISCONN, EOPNOTSUPP, - ECONNREFUSED, ENOENT, ENETUNREACH, EINPROGRESS, ENOTCONN, ETIMEDOUT, + ECONNREFUSED, ENOENT, ENETUNREACH, }; +#[cfg(target_arch = "x86_64")] +use super::errno::{EINPROGRESS, ENOTCONN, ETIMEDOUT}; use super::{ErrorCode, SyscallResult}; use crate::socket::types::{AF_INET, AF_UNIX, SOCK_DGRAM, SOCK_STREAM, SockAddrIn, SockAddrUn}; use crate::socket::udp::UdpSocket; @@ -89,8 +91,16 @@ pub fn sys_socket(domain: u64, sock_type: u64, _protocol: u64) -> SyscallResult (FdKind::UdpSocket(socket), "UDP") } SOCK_STREAM => { - // Create TCP socket (initially unbound, port = 0) - (FdKind::TcpSocket(0), "TCP") + #[cfg(target_arch = "x86_64")] + { + // Create TCP socket (initially unbound, port = 0) + (FdKind::TcpSocket(0), "TCP") + } + #[cfg(not(target_arch = "x86_64"))] + { + log::debug!("sys_socket: TCP not supported on this architecture"); + return SyscallResult::Err(EOPNOTSUPP as u64); + } } _ => { log::debug!("sys_socket: unsupported type {} for AF_INET", base_type); @@ -228,7 +238,8 @@ pub fn sys_bind(fd: u64, addr_ptr: u64, addrlen: u64) -> SyscallResult { Err(e) => SyscallResult::Err(e as u64), } } - FdKind::TcpSocket(existing_port) => { + #[cfg(target_arch = "x86_64")] + FdKind::TcpSocket(existing_port) => { // TCP socket binding - update the socket's port if *existing_port != 0 { // Already bound @@ -762,6 +773,8 @@ pub fn sys_listen(fd: u64, backlog: u64) -> SyscallResult { return SyscallResult::Err(ErrorCode::NoSuchProcess as u64); } }; + #[cfg(not(target_arch = "x86_64"))] + let _ = pid; // Get the socket from fd table let fd_entry = match process.fd_table.get(fd as i32) { @@ -771,6 +784,7 @@ pub fn sys_listen(fd: u64, backlog: u64) -> SyscallResult { // Handle listen based on socket type match &fd_entry.kind { + #[cfg(target_arch = "x86_64")] FdKind::TcpSocket(port) => { if *port == 0 { // Not bound @@ -790,6 +804,7 @@ pub fn sys_listen(fd: u64, backlog: u64) -> SyscallResult { log::info!("TCP: Socket now listening on port {}", port); SyscallResult::Ok(0) } + #[cfg(target_arch = "x86_64")] FdKind::TcpListener(_) => { // Already listening SyscallResult::Err(EINVAL as u64) @@ -842,6 +857,7 @@ pub fn sys_listen(fd: u64, backlog: u64) -> SyscallResult { /// Internal enum to track listener type for accept enum ListenerType { + #[cfg(target_arch = "x86_64")] Tcp(u16), Unix(alloc::sync::Arc>), } @@ -862,6 +878,8 @@ enum ListenerType { /// The blocking pattern follows the same double-check approach as UDP recvfrom. pub fn sys_accept(fd: u64, addr_ptr: u64, addrlen_ptr: u64) -> SyscallResult { log::debug!("sys_accept: fd={}", fd); + #[cfg(not(target_arch = "x86_64"))] + let _ = (addr_ptr, addrlen_ptr); // Drain loopback queue for localhost connections (127.x.x.x, own IP). crate::net::drain_loopback_queue(); @@ -904,6 +922,7 @@ pub fn sys_accept(fd: u64, addr_ptr: u64, addrlen_ptr: u64) -> SyscallResult { // Determine listener type let lt = match &fd_entry.kind { + #[cfg(target_arch = "x86_64")] FdKind::TcpListener(p) => ListenerType::Tcp(*p), FdKind::UnixListener(l) => ListenerType::Unix(l.clone()), _ => return SyscallResult::Err(EOPNOTSUPP as u64), @@ -914,6 +933,7 @@ pub fn sys_accept(fd: u64, addr_ptr: u64, addrlen_ptr: u64) -> SyscallResult { // Dispatch based on listener type match listener_type { + #[cfg(target_arch = "x86_64")] ListenerType::Tcp(port) => { sys_accept_tcp(fd, port, is_nonblocking, thread_id, addr_ptr, addrlen_ptr) } @@ -924,6 +944,7 @@ pub fn sys_accept(fd: u64, addr_ptr: u64, addrlen_ptr: u64) -> SyscallResult { } /// Accept on TCP listener +#[cfg(target_arch = "x86_64")] fn sys_accept_tcp(fd: u64, port: u16, is_nonblocking: bool, thread_id: u64, addr_ptr: u64, addrlen_ptr: u64) -> SyscallResult { // Blocking accept loop loop { @@ -1233,7 +1254,17 @@ pub fn sys_connect(fd: u64, addr_ptr: u64, addrlen: u64) -> SyscallResult { // Dispatch based on address family match family { - AF_INET => sys_connect_tcp(fd, addr_ptr, addrlen), + AF_INET => { + #[cfg(target_arch = "x86_64")] + { + sys_connect_tcp(fd, addr_ptr, addrlen) + } + #[cfg(not(target_arch = "x86_64"))] + { + log::debug!("sys_connect: TCP not supported on this architecture"); + SyscallResult::Err(EOPNOTSUPP as u64) + } + } AF_UNIX => sys_connect_unix(fd, addr_ptr, addrlen), _ => { log::debug!("sys_connect: unsupported address family {}", family); @@ -1243,6 +1274,7 @@ pub fn sys_connect(fd: u64, addr_ptr: u64, addrlen: u64) -> SyscallResult { } /// Connect TCP socket +#[cfg(target_arch = "x86_64")] fn sys_connect_tcp(fd: u64, addr_ptr: u64, addrlen: u64) -> SyscallResult { // Validate address length for IPv4 if addrlen < 16 { @@ -1625,6 +1657,7 @@ pub fn sys_shutdown(fd: u64, how: u64) -> SyscallResult { // Must be a TCP connection match &fd_entry.kind { + #[cfg(target_arch = "x86_64")] FdKind::TcpConnection(conn_id) => { // Set shutdown flags on the connection let shut_rd = how == 0 || how == 2; // SHUT_RD or SHUT_RDWR @@ -1635,6 +1668,7 @@ pub fn sys_shutdown(fd: u64, how: u64) -> SyscallResult { log::info!("TCP: Shutdown fd={} how={}", fd, how); SyscallResult::Ok(0) } + #[cfg(target_arch = "x86_64")] FdKind::TcpSocket(_) | FdKind::TcpListener(_) => { // Not connected SyscallResult::Err(ENOTCONN as u64) diff --git a/kernel/src/syscall/wait.rs b/kernel/src/syscall/wait.rs index 57fd89bc..d2e170ad 100644 --- a/kernel/src/syscall/wait.rs +++ b/kernel/src/syscall/wait.rs @@ -75,7 +75,6 @@ pub fn sys_waitpid(pid: i64, status_ptr: u64, options: u32) -> SyscallResult { return SyscallResult::Err(ECHILD as u64); } - let children_copy: Vec<_> = current_process.children.clone(); drop(manager_guard); // Check if the specific child is already terminated @@ -131,7 +130,7 @@ pub fn sys_waitpid(pid: i64, status_ptr: u64, options: u32) -> SyscallResult { // pid == -1: Wait for any child -1 => { - let children_copy: Vec<_> = current_process.children.clone(); + let children_copy = current_process.children.clone(); drop(manager_guard); let terminated_child = { diff --git a/libs/libbreenix/Cargo.toml b/libs/libbreenix/Cargo.toml index adfb00b9..8685d6e9 100644 --- a/libs/libbreenix/Cargo.toml +++ b/libs/libbreenix/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["rlib"] [features] default = [] +runtime = [] [dependencies] # No dependencies - pure no_std library diff --git a/libs/libbreenix/src/argv.rs b/libs/libbreenix/src/argv.rs index 65c1df3c..b082f128 100644 --- a/libs/libbreenix/src/argv.rs +++ b/libs/libbreenix/src/argv.rs @@ -1,7 +1,7 @@ //! Command-line argument parsing for Breenix userspace programs //! //! This module provides utilities to parse argc/argv from the stack, -//! following the Linux x86_64 ABI convention. +//! following the Linux ABI convention on x86_64 and aarch64. //! //! At process startup, the stack layout is: //! ```text @@ -12,7 +12,7 @@ //! argv[n-1] pointer //! ... //! argv[0] pointer -//! argc <- RSP points here at _start +//! argc <- stack pointer points here at _start //! Low addresses: //! ``` //! @@ -133,7 +133,7 @@ impl Iterator for ArgsIter { /// # Safety /// /// This function must be called from `_start` with the original RSP value. -/// The RSP must point to a valid argc/argv structure set up by the kernel. +/// The stack pointer must point to a valid argc/argv structure set up by the kernel. /// /// # Usage /// @@ -145,6 +145,7 @@ impl Iterator for ArgsIter { /// // ... /// } /// ``` +#[cfg(target_arch = "x86_64")] #[inline(always)] pub unsafe fn get_args() -> Args { let argc: usize; @@ -167,6 +168,29 @@ pub unsafe fn get_args() -> Args { Args::new(argc, argv_ptr) } +#[cfg(target_arch = "aarch64")] +#[inline(always)] +pub unsafe fn get_args() -> Args { + let argc: usize; + let argv_ptr: *const *const u8; + + // Read argc from SP and argv from SP+8 + // The kernel sets up: [argc] [argv[0]] [argv[1]] ... [NULL] + // SP -> argc + // SP+8 -> argv[0] + // SP+16 -> argv[1] + // etc. + core::arch::asm!( + "ldr {argc}, [sp]", + "add {argv}, sp, #8", + argc = out(reg) argc, + argv = out(reg) argv_ptr, + options(nostack, preserves_flags, pure, readonly) + ); + + Args::new(argc, argv_ptr) +} + /// Get command-line arguments from a specific stack pointer. /// /// This is useful when you need to pass the original RSP from assembly. diff --git a/libs/libbreenix/src/lib.rs b/libs/libbreenix/src/lib.rs index f12a3362..d560900b 100644 --- a/libs/libbreenix/src/lib.rs +++ b/libs/libbreenix/src/lib.rs @@ -47,6 +47,8 @@ pub mod io; pub mod memory; pub mod process; pub mod pty; +#[cfg(feature = "runtime")] +pub mod runtime; pub mod signal; pub mod socket; pub mod syscall; diff --git a/libs/libbreenix/src/runtime.rs b/libs/libbreenix/src/runtime.rs new file mode 100644 index 00000000..e86dd14d --- /dev/null +++ b/libs/libbreenix/src/runtime.rs @@ -0,0 +1,41 @@ +//! Userspace runtime entry point for Breenix binaries. + +use core::arch::naked_asm; + +use crate::process::exit; + +extern "C" { + fn main(argc: usize, argv: *const *const u8) -> i32; +} + +#[cfg(target_arch = "x86_64")] +#[unsafe(naked)] +#[no_mangle] +pub extern "C" fn _start() -> ! { + naked_asm!( + "mov rdi, rsp", // Pass original RSP as first argument + "and rsp, -16", // Align stack to 16 bytes (ABI requirement) + "call {entry}", // Call runtime_entry(stack_ptr) + "ud2", // Should never return + entry = sym runtime_entry, + ) +} + +#[cfg(target_arch = "aarch64")] +#[unsafe(naked)] +#[no_mangle] +pub extern "C" fn _start() -> ! { + naked_asm!( + "mov x0, sp", // Pass original SP as first argument + "bl {entry}", // Call runtime_entry(stack_ptr) + "brk #1", // Should never return + entry = sym runtime_entry, + ) +} + +extern "C" fn runtime_entry(stack_ptr: *const u64) -> ! { + let argc = unsafe { *stack_ptr as usize }; + let argv = unsafe { stack_ptr.add(1) as *const *const u8 }; + let exit_code = unsafe { main(argc, argv) }; + exit(exit_code); +} diff --git a/scripts/create_ext2_disk.sh b/scripts/create_ext2_disk.sh index 7c194d61..4df9b7ee 100755 --- a/scripts/create_ext2_disk.sh +++ b/scripts/create_ext2_disk.sh @@ -12,6 +12,7 @@ # Usage: # ./scripts/create_ext2_disk.sh # ./scripts/create_ext2_disk.sh --arch aarch64 +# ./scripts/create_ext2_disk.sh --arch aarch64 --size 8 # # Or use xtask: # cargo run -p xtask -- create-ext2-disk @@ -30,8 +31,12 @@ while [[ $# -gt 0 ]]; do ARCH="$2" shift 2 ;; + --size) + SIZE_MB="$2" + shift 2 + ;; *) - echo "Usage: $0 [--arch x86_64|aarch64]" + echo "Usage: $0 [--arch x86_64|aarch64] [--size MB]" exit 1 ;; esac @@ -77,23 +82,27 @@ if [[ "$(uname)" == "Darwin" ]]; then echo " Using Docker to create ext2 filesystem..." + # Extract just the filename from OUTPUT_FILE for use in Docker + OUTPUT_FILENAME=$(basename "$OUTPUT_FILE") + docker run --rm --privileged \ -v "$TARGET_DIR:/work" \ -v "$USERSPACE_DIR:/binaries:ro" \ + -e "OUTPUT_FILENAME=$OUTPUT_FILENAME" \ alpine:latest \ sh -c ' set -e apk add --no-cache e2fsprogs >/dev/null 2>&1 # Create the empty disk image - dd if=/dev/zero of=/work/ext2.img bs=1M count='"$SIZE_MB"' status=none + dd if=/dev/zero of=/work/$OUTPUT_FILENAME bs=1M count='"$SIZE_MB"' status=none # Create ext2 filesystem - mke2fs -t ext2 -F /work/ext2.img >/dev/null 2>&1 + mke2fs -t ext2 -F /work/$OUTPUT_FILENAME >/dev/null 2>&1 # Mount and populate mkdir -p /mnt/ext2 - mount /work/ext2.img /mnt/ext2 + mount /work/$OUTPUT_FILENAME /mnt/ext2 # Create /bin and /sbin directories for coreutils mkdir -p /mnt/ext2/bin diff --git a/userspace/bin/coreutils/cat.rs b/userspace/bin/coreutils/cat.rs index f4528a1d..9e0cd4f8 100644 --- a/userspace/bin/coreutils/cat.rs +++ b/userspace/bin/coreutils/cat.rs @@ -9,7 +9,6 @@ #![no_std] #![no_main] -use core::arch::naked_asm; use core::panic::PanicInfo; use libbreenix::argv; use libbreenix::errno::Errno; @@ -78,34 +77,17 @@ fn print_error_bytes(path: &[u8], e: Errno) { let _ = stderr().write(b"\n"); } -/// Naked entry point that captures RSP before any prologue modifies it. -/// RSP points to argc on entry per Linux x86_64 ABI. -#[unsafe(naked)] #[no_mangle] -pub extern "C" fn _start() -> ! { - naked_asm!( - "mov rdi, rsp", // Pass original RSP as first argument - "and rsp, -16", // Align stack to 16 bytes (ABI requirement) - "call {main}", // Call rust_main(stack_ptr) - "ud2", // Should never return - main = sym rust_main, - ) -} - -/// Real entry point called from naked _start with the original stack pointer. -/// Note: stack_ptr points to the ORIGINAL RSP (argc location), not current RSP. -extern "C" fn rust_main(stack_ptr: *const u64) -> ! { - // Get command-line arguments from the original stack pointer - // stack_ptr was captured BEFORE the call instruction, so it points to argc - let args = unsafe { argv::get_args_from_stack(stack_ptr) }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; // If no arguments (besides program name), read from stdin if args.argc < 2 { if let Err(_e) = cat_stdin() { let _ = stderr().write_str("cat: error reading stdin\n"); - exit(1); + return 1; } - exit(0); + return 0; } // Process each file argument @@ -119,7 +101,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { } } - exit(exit_code); + exit_code } #[panic_handler] diff --git a/userspace/bin/coreutils/cp.rs b/userspace/bin/coreutils/cp.rs index 7b5fbff9..e7d957d6 100644 --- a/userspace/bin/coreutils/cp.rs +++ b/userspace/bin/coreutils/cp.rs @@ -8,7 +8,7 @@ #![no_main] use core::panic::PanicInfo; -use libbreenix::argv::get_args; +use libbreenix::argv; use libbreenix::errno::Errno; use libbreenix::fs::{close, open, open_with_mode, read, write, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY}; use libbreenix::io::{println, stderr}; @@ -92,19 +92,19 @@ fn build_path(arg: &[u8], buf: &mut [u8; 256]) -> Option { } #[no_mangle] -pub extern "C" fn _start() -> ! { - let args = unsafe { get_args() }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; if args.argc < 3 { print_usage(); - exit(1); + return 1; } let src_arg = match args.argv(1) { Some(arg) => arg, None => { print_usage(); - exit(1); + return 1; } }; @@ -112,7 +112,7 @@ pub extern "C" fn _start() -> ! { Some(arg) => arg, None => { print_usage(); - exit(1); + return 1; } }; @@ -123,7 +123,7 @@ pub extern "C" fn _start() -> ! { Some(len) => len, None => { let _ = stderr().write_str("cp: source path too long\n"); - exit(1); + return 1; } }; @@ -131,7 +131,7 @@ pub extern "C" fn _start() -> ! { Some(len) => len, None => { let _ = stderr().write_str("cp: destination path too long\n"); - exit(1); + return 1; } }; @@ -139,7 +139,7 @@ pub extern "C" fn _start() -> ! { Ok(s) => s, Err(_) => { let _ = stderr().write_str("cp: invalid source path encoding\n"); - exit(1); + return 1; } }; @@ -147,18 +147,18 @@ pub extern "C" fn _start() -> ! { Ok(s) => s, Err(_) => { let _ = stderr().write_str("cp: invalid destination path encoding\n"); - exit(1); + return 1; } }; match copy_file(src, dst) { Ok(()) => { println("cp: file copied"); - exit(0) + 0 } Err((e, is_src)) => { print_error(if is_src { src_arg } else { dst_arg }, e); - exit(1); + 1 } } } diff --git a/userspace/bin/coreutils/echo.rs b/userspace/bin/coreutils/echo.rs index 6fe25cb8..69c04db3 100644 --- a/userspace/bin/coreutils/echo.rs +++ b/userspace/bin/coreutils/echo.rs @@ -8,13 +8,13 @@ #![no_main] use core::panic::PanicInfo; -use libbreenix::argv::get_args; +use libbreenix::argv; use libbreenix::io::{print, println, stderr, stdout}; use libbreenix::process::exit; #[no_mangle] -pub extern "C" fn _start() -> ! { - let args = unsafe { get_args() }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; // Print each argument starting from argv[1], separated by spaces for i in 1..args.argc { @@ -29,7 +29,7 @@ pub extern "C" fn _start() -> ! { // Print final newline println(""); - exit(0); + 0 } #[panic_handler] diff --git a/userspace/bin/coreutils/false.rs b/userspace/bin/coreutils/false.rs index f3969276..c59e0ea8 100644 --- a/userspace/bin/coreutils/false.rs +++ b/userspace/bin/coreutils/false.rs @@ -12,8 +12,8 @@ use core::panic::PanicInfo; use libbreenix::process::exit; #[no_mangle] -pub extern "C" fn _start() -> ! { - exit(1); +pub extern "C" fn main(_argc: usize, _argv: *const *const u8) -> i32 { + 1 } #[panic_handler] diff --git a/userspace/bin/coreutils/head.rs b/userspace/bin/coreutils/head.rs index 61c69982..dfb8cf06 100644 --- a/userspace/bin/coreutils/head.rs +++ b/userspace/bin/coreutils/head.rs @@ -11,7 +11,6 @@ #![no_std] #![no_main] -use core::arch::naked_asm; use core::panic::PanicInfo; use libbreenix::argv; use libbreenix::errno::Errno; @@ -133,20 +132,9 @@ fn print_error_bytes(path: &[u8], e: Errno) { let _ = stderr().write(b"\n"); } -#[unsafe(naked)] #[no_mangle] -pub extern "C" fn _start() -> ! { - naked_asm!( - "mov rdi, rsp", - "and rsp, -16", - "call {main}", - "ud2", - main = sym rust_main, - ) -} - -extern "C" fn rust_main(stack_ptr: *const u64) -> ! { - let args = unsafe { argv::get_args_from_stack(stack_ptr) }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; let mut max_lines = DEFAULT_LINES; let mut file_start_idx = 1usize; @@ -163,7 +151,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { file_start_idx = 2; } else { let _ = stderr().write_str("head: invalid number of lines\n"); - exit(1); + return 1; } } else if args.argc >= 3 { // -n NUM format @@ -173,7 +161,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { file_start_idx = 3; } else { let _ = stderr().write_str("head: invalid number of lines\n"); - exit(1); + return 1; } } } @@ -185,9 +173,9 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { if file_start_idx >= args.argc { if let Err(_e) = head_stdin(max_lines) { let _ = stderr().write_str("head: error reading stdin\n"); - exit(1); + return 1; } - exit(0); + return 0; } // Process files @@ -213,7 +201,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { } } - exit(exit_code); + exit_code } #[panic_handler] diff --git a/userspace/bin/coreutils/ls.rs b/userspace/bin/coreutils/ls.rs index 0aef1ff4..86666739 100644 --- a/userspace/bin/coreutils/ls.rs +++ b/userspace/bin/coreutils/ls.rs @@ -8,7 +8,6 @@ #![no_std] #![no_main] -use core::arch::naked_asm; use core::panic::PanicInfo; use libbreenix::argv; use libbreenix::errno::Errno; @@ -81,23 +80,9 @@ fn print_error(path: &[u8], e: Errno) { let _ = stderr().write(b"\n"); } -/// Naked entry point that captures RSP before any prologue modifies it. -#[unsafe(naked)] #[no_mangle] -pub extern "C" fn _start() -> ! { - naked_asm!( - "mov rdi, rsp", // Pass original RSP as first argument - "and rsp, -16", // Align stack to 16 bytes (ABI requirement) - "call {main}", // Call rust_main(stack_ptr) - "ud2", // Should never return - main = sym rust_main, - ) -} - -/// Real entry point called from naked _start with the original stack pointer. -extern "C" fn rust_main(stack_ptr: *const u64) -> ! { - // Get command-line arguments from the original stack pointer - let args = unsafe { argv::get_args_from_stack(stack_ptr) }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; // Default to current directory if no arguments let path: &[u8] = if args.argc >= 2 { @@ -107,10 +92,10 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { }; match ls_directory(path) { - Ok(()) => exit(0), + Ok(()) => 0, Err(e) => { print_error(path, e); - exit(1); + 1 } } } diff --git a/userspace/bin/coreutils/mkdir.rs b/userspace/bin/coreutils/mkdir.rs index 0564ee43..493b6403 100644 --- a/userspace/bin/coreutils/mkdir.rs +++ b/userspace/bin/coreutils/mkdir.rs @@ -8,7 +8,7 @@ #![no_main] use core::panic::PanicInfo; -use libbreenix::argv::get_args; +use libbreenix::argv; use libbreenix::errno::Errno; use libbreenix::fs::mkdir; use libbreenix::io::{println, stderr}; @@ -34,14 +34,13 @@ fn print_usage() { } #[no_mangle] -pub extern "C" fn _start() -> ! { - // Get command-line arguments - must be first thing in _start - let args = unsafe { get_args() }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; // Need at least one argument (the directory name) if args.argc < 2 { print_usage(); - exit(1); + return 1; } // Get the directory path from argv[1] @@ -49,7 +48,7 @@ pub extern "C" fn _start() -> ! { Some(arg) => arg, None => { print_usage(); - exit(1); + return 1; } }; @@ -62,7 +61,7 @@ pub extern "C" fn _start() -> ! { // Absolute path - copy as-is if dir_arg.len() >= PATH_BUF_LEN { let _ = stderr().write_str("mkdir: path too long\n"); - exit(1); + return 1; } path_buf[..dir_arg.len()].copy_from_slice(dir_arg); path_len = dir_arg.len(); @@ -70,7 +69,7 @@ pub extern "C" fn _start() -> ! { // Relative path - prepend / if dir_arg.len() + 1 >= PATH_BUF_LEN { let _ = stderr().write_str("mkdir: path too long\n"); - exit(1); + return 1; } path_buf[0] = b'/'; path_buf[1..=dir_arg.len()].copy_from_slice(dir_arg); @@ -84,18 +83,18 @@ pub extern "C" fn _start() -> ! { Ok(s) => s, Err(_) => { let _ = stderr().write_str("mkdir: invalid path encoding\n"); - exit(1); + return 1; } }; match mkdir(path_str, 0o755) { Ok(()) => { println("mkdir: directory created"); - exit(0) + 0 } Err(e) => { print_error(dir_arg, e); - exit(1); + 1 } } } diff --git a/userspace/bin/coreutils/mv.rs b/userspace/bin/coreutils/mv.rs index e1f2b6ac..f36fd012 100644 --- a/userspace/bin/coreutils/mv.rs +++ b/userspace/bin/coreutils/mv.rs @@ -8,7 +8,7 @@ #![no_main] use core::panic::PanicInfo; -use libbreenix::argv::get_args; +use libbreenix::argv; use libbreenix::errno::Errno; use libbreenix::fs::rename; use libbreenix::io::{println, stderr}; @@ -57,19 +57,19 @@ fn build_path(arg: &[u8], buf: &mut [u8; 256]) -> Option { } #[no_mangle] -pub extern "C" fn _start() -> ! { - let args = unsafe { get_args() }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; if args.argc < 3 { print_usage(); - exit(1); + return 1; } let src_arg = match args.argv(1) { Some(arg) => arg, None => { print_usage(); - exit(1); + return 1; } }; @@ -77,7 +77,7 @@ pub extern "C" fn _start() -> ! { Some(arg) => arg, None => { print_usage(); - exit(1); + return 1; } }; @@ -88,7 +88,7 @@ pub extern "C" fn _start() -> ! { Some(len) => len, None => { let _ = stderr().write_str("mv: source path too long\n"); - exit(1); + return 1; } }; @@ -96,7 +96,7 @@ pub extern "C" fn _start() -> ! { Some(len) => len, None => { let _ = stderr().write_str("mv: destination path too long\n"); - exit(1); + return 1; } }; @@ -104,7 +104,7 @@ pub extern "C" fn _start() -> ! { Ok(s) => s, Err(_) => { let _ = stderr().write_str("mv: invalid source path encoding\n"); - exit(1); + return 1; } }; @@ -112,18 +112,18 @@ pub extern "C" fn _start() -> ! { Ok(s) => s, Err(_) => { let _ = stderr().write_str("mv: invalid destination path encoding\n"); - exit(1); + return 1; } }; match rename(src, dst) { Ok(()) => { println("mv: file moved"); - exit(0) + 0 } Err(e) => { print_error(src_arg, dst_arg, e); - exit(1); + 1 } } } diff --git a/userspace/bin/coreutils/rm.rs b/userspace/bin/coreutils/rm.rs index ac9b84a8..796cc7be 100644 --- a/userspace/bin/coreutils/rm.rs +++ b/userspace/bin/coreutils/rm.rs @@ -8,7 +8,7 @@ #![no_main] use core::panic::PanicInfo; -use libbreenix::argv::get_args; +use libbreenix::argv; use libbreenix::errno::Errno; use libbreenix::fs::unlink; use libbreenix::io::{println, stderr}; @@ -33,19 +33,19 @@ fn print_usage() { } #[no_mangle] -pub extern "C" fn _start() -> ! { - let args = unsafe { get_args() }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; if args.argc < 2 { print_usage(); - exit(1); + return 1; } let file_arg = match args.argv(1) { Some(arg) => arg, None => { print_usage(); - exit(1); + return 1; } }; @@ -57,14 +57,14 @@ pub extern "C" fn _start() -> ! { if file_arg.starts_with(b"/") { if file_arg.len() >= PATH_BUF_LEN { let _ = stderr().write_str("rm: path too long\n"); - exit(1); + return 1; } path_buf[..file_arg.len()].copy_from_slice(file_arg); path_len = file_arg.len(); } else { if file_arg.len() + 1 >= PATH_BUF_LEN { let _ = stderr().write_str("rm: path too long\n"); - exit(1); + return 1; } path_buf[0] = b'/'; path_buf[1..=file_arg.len()].copy_from_slice(file_arg); @@ -76,18 +76,18 @@ pub extern "C" fn _start() -> ! { Ok(s) => s, Err(_) => { let _ = stderr().write_str("rm: invalid path encoding\n"); - exit(1); + return 1; } }; match unlink(path_str) { Ok(()) => { println("rm: file removed"); - exit(0) + 0 } Err(e) => { print_error(file_arg, e); - exit(1); + 1 } } } diff --git a/userspace/bin/coreutils/rmdir.rs b/userspace/bin/coreutils/rmdir.rs index f4389b5c..4a03da51 100644 --- a/userspace/bin/coreutils/rmdir.rs +++ b/userspace/bin/coreutils/rmdir.rs @@ -8,7 +8,7 @@ #![no_main] use core::panic::PanicInfo; -use libbreenix::argv::get_args; +use libbreenix::argv; use libbreenix::errno::Errno; use libbreenix::fs::rmdir; use libbreenix::io::{println, stderr}; @@ -34,19 +34,19 @@ fn print_usage() { } #[no_mangle] -pub extern "C" fn _start() -> ! { - let args = unsafe { get_args() }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; if args.argc < 2 { print_usage(); - exit(1); + return 1; } let dir_arg = match args.argv(1) { Some(arg) => arg, None => { print_usage(); - exit(1); + return 1; } }; @@ -58,14 +58,14 @@ pub extern "C" fn _start() -> ! { if dir_arg.starts_with(b"/") { if dir_arg.len() >= PATH_BUF_LEN { let _ = stderr().write_str("rmdir: path too long\n"); - exit(1); + return 1; } path_buf[..dir_arg.len()].copy_from_slice(dir_arg); path_len = dir_arg.len(); } else { if dir_arg.len() + 1 >= PATH_BUF_LEN { let _ = stderr().write_str("rmdir: path too long\n"); - exit(1); + return 1; } path_buf[0] = b'/'; path_buf[1..=dir_arg.len()].copy_from_slice(dir_arg); @@ -77,18 +77,18 @@ pub extern "C" fn _start() -> ! { Ok(s) => s, Err(_) => { let _ = stderr().write_str("rmdir: invalid path encoding\n"); - exit(1); + return 1; } }; match rmdir(path_str) { Ok(()) => { println("rmdir: directory removed"); - exit(0) + 0 } Err(e) => { print_error(dir_arg, e); - exit(1); + 1 } } } diff --git a/userspace/bin/coreutils/tail.rs b/userspace/bin/coreutils/tail.rs index 361f0437..0287f348 100644 --- a/userspace/bin/coreutils/tail.rs +++ b/userspace/bin/coreutils/tail.rs @@ -14,7 +14,6 @@ #![no_std] #![no_main] -use core::arch::naked_asm; use core::panic::PanicInfo; use libbreenix::argv; use libbreenix::errno::Errno; @@ -220,20 +219,9 @@ fn print_error_bytes(path: &[u8], e: Errno) { let _ = stderr().write(b"\n"); } -#[unsafe(naked)] #[no_mangle] -pub extern "C" fn _start() -> ! { - naked_asm!( - "mov rdi, rsp", - "and rsp, -16", - "call {main}", - "ud2", - main = sym rust_main, - ) -} - -extern "C" fn rust_main(stack_ptr: *const u64) -> ! { - let args = unsafe { argv::get_args_from_stack(stack_ptr) }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; let mut max_lines = DEFAULT_LINES; let mut file_start_idx = 1usize; @@ -249,7 +237,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { file_start_idx = 2; } else { let _ = stderr().write_str("tail: invalid number of lines\n"); - exit(1); + return 1; } } else if args.argc >= 3 { // -n NUM format @@ -259,7 +247,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { file_start_idx = 3; } else { let _ = stderr().write_str("tail: invalid number of lines\n"); - exit(1); + return 1; } } } @@ -270,9 +258,9 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { // If no files, read from stdin if file_start_idx >= args.argc { if let Err(_e) = tail_stdin(max_lines) { - exit(1); + return 1; } - exit(0); + return 0; } // Process files @@ -298,7 +286,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { } } - exit(exit_code); + exit_code } #[panic_handler] diff --git a/userspace/bin/coreutils/true.rs b/userspace/bin/coreutils/true.rs index 1cbf8ffc..c4d7293b 100644 --- a/userspace/bin/coreutils/true.rs +++ b/userspace/bin/coreutils/true.rs @@ -12,8 +12,8 @@ use core::panic::PanicInfo; use libbreenix::process::exit; #[no_mangle] -pub extern "C" fn _start() -> ! { - exit(0); +pub extern "C" fn main(_argc: usize, _argv: *const *const u8) -> i32 { + 0 } #[panic_handler] diff --git a/userspace/bin/coreutils/wc.rs b/userspace/bin/coreutils/wc.rs index cab0e1b2..7154bc4a 100644 --- a/userspace/bin/coreutils/wc.rs +++ b/userspace/bin/coreutils/wc.rs @@ -15,7 +15,6 @@ #![no_std] #![no_main] -use core::arch::naked_asm; use core::panic::PanicInfo; use libbreenix::argv; use libbreenix::errno::Errno; @@ -222,20 +221,9 @@ fn print_error_bytes(path: &[u8], e: Errno) { let _ = stderr().write(b"\n"); } -#[unsafe(naked)] #[no_mangle] -pub extern "C" fn _start() -> ! { - naked_asm!( - "mov rdi, rsp", - "and rsp, -16", - "call {main}", - "ud2", - main = sym rust_main, - ) -} - -extern "C" fn rust_main(stack_ptr: *const u64) -> ! { - let args = unsafe { argv::get_args_from_stack(stack_ptr) }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; let mut show_lines = false; let mut show_words = false; @@ -256,7 +244,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { let _ = stderr().write_str("wc: invalid option -- '"); let _ = stderr().write(&arg[j..j + 1]); let _ = stderr().write_str("'\n"); - exit(1); + return 1; } } } @@ -276,10 +264,10 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { Ok(counts) => print_counts(&counts, &opts, None), Err(_e) => { let _ = stderr().write_str("wc: error reading stdin\n"); - exit(1); + return 1; } } - exit(0); + return 0; } // Process files @@ -307,7 +295,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { print_counts(&total, &opts, Some(b"total")); } - exit(exit_code); + exit_code } #[panic_handler] diff --git a/userspace/bin/coreutils/which.rs b/userspace/bin/coreutils/which.rs index 718d1440..65e93e99 100644 --- a/userspace/bin/coreutils/which.rs +++ b/userspace/bin/coreutils/which.rs @@ -8,7 +8,6 @@ #![no_std] #![no_main] -use core::arch::naked_asm; use core::panic::PanicInfo; use libbreenix::argv; use libbreenix::fs::{access, X_OK}; @@ -50,27 +49,14 @@ fn build_path(dir: &[u8], name: &[u8], buf: &mut [u8; 256]) -> Option { Some(total) } -/// Naked entry point that captures RSP before any prologue modifies it. -#[unsafe(naked)] #[no_mangle] -pub extern "C" fn _start() -> ! { - naked_asm!( - "mov rdi, rsp", // Pass original RSP as first argument - "and rsp, -16", // Align stack to 16 bytes (ABI requirement) - "call {main}", // Call rust_main(stack_ptr) - "ud2", // Should never return - main = sym rust_main, - ) -} - -/// Real entry point called from naked _start with the original stack pointer. -extern "C" fn rust_main(stack_ptr: *const u64) -> ! { - let args = unsafe { argv::get_args_from_stack(stack_ptr) }; +pub extern "C" fn main(argc: usize, argv_ptr: *const *const u8) -> i32 { + let args = unsafe { argv::Args::new(argc, argv_ptr) }; if args.argc < 2 { let _ = stderr().write(b"which: missing command name\n"); let _ = stderr().write(b"Usage: which COMMAND\n"); - exit(1); + return 1; } // Get command name (first argument after program name) @@ -78,7 +64,7 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { Some(name) if !name.is_empty() => name, _ => { let _ = stderr().write(b"which: empty command name\n"); - exit(1); + return 1; } }; @@ -87,9 +73,9 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { if is_executable(cmd_name) { print_bytes(cmd_name); println(""); - exit(0); + return 0; } else { - exit(1); + return 1; } } @@ -100,13 +86,13 @@ extern "C" fn rust_main(stack_ptr: *const u64) -> ! { if is_executable(&path_buf[..len]) { print_bytes(&path_buf[..len]); println(""); - exit(0); + return 0; } } } // Not found - exit(1); + 1 } /// Print bytes as string diff --git a/userspace/tests/Cargo.toml b/userspace/tests/Cargo.toml index 2f32b117..4852e0b2 100644 --- a/userspace/tests/Cargo.toml +++ b/userspace/tests/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" [dependencies] libbreenix = { path = "../../libs/libbreenix" } +[features] +runtime = ["libbreenix/runtime"] + # ============================================================================ # EXAMPLES - Demo programs and shell # ============================================================================ diff --git a/userspace/tests/build-aarch64.sh b/userspace/tests/build-aarch64.sh index 7ed86b48..9b599a6c 100755 --- a/userspace/tests/build-aarch64.sh +++ b/userspace/tests/build-aarch64.sh @@ -47,6 +47,24 @@ BINARIES=( "telnetd" ) +# Binaries that rely on the libbreenix runtime _start (no local _start) +RUNTIME_BINS=( + "cat" + "ls" + "echo" + "mkdir" + "rmdir" + "rm" + "cp" + "mv" + "true" + "false" + "head" + "tail" + "wc" + "which" +) + # Create output directory for ARM64 binaries mkdir -p aarch64 @@ -56,7 +74,14 @@ echo "Building ${#BINARIES[@]} ARM64 userspace binaries..." # Build each binary individually to avoid building x86_64-only binaries for bin in "${BINARIES[@]}"; do echo " Building $bin..." - if ! cargo build --release --target aarch64-breenix.json -Z build-std=core,alloc --bin "$bin" 2>&1 | grep -E "^error" | head -3; then + FEATURES=() + for runtime_bin in "${RUNTIME_BINS[@]}"; do + if [ "$bin" = "$runtime_bin" ]; then + FEATURES=(--features runtime) + break + fi + done + if ! cargo build --release --target aarch64-breenix.json -Z build-std=core,alloc "${FEATURES[@]}" --bin "$bin" 2>&1 | grep -E "^error" | head -3; then : # Success, no error output fi done diff --git a/userspace/tests/build.sh b/userspace/tests/build.sh index 98477e91..83e6a013 100755 --- a/userspace/tests/build.sh +++ b/userspace/tests/build.sh @@ -154,20 +154,45 @@ BINARIES=( "rm_argv_test" ) +RUNTIME_BINS=( + "cat" + "ls" + "echo" + "mkdir" + "rmdir" + "rm" + "cp" + "mv" + "true" + "false" + "head" + "tail" + "wc" + "which" +) + echo "Building ${#BINARIES[@]} userspace binaries with libbreenix..." echo "" -# Build with cargo (config is in .cargo/config.toml) -# This will compile libbreenix first, then link it into each binary -cargo build --release 2>&1 | while read line; do - # Highlight libbreenix compilation - if echo "$line" | grep -q "Compiling libbreenix"; then - echo " [libbreenix] $line" - elif echo "$line" | grep -q "Compiling userspace_tests"; then - echo " [userspace] $line" - else - echo " $line" - fi +# Build each binary with the appropriate feature set +for bin in "${BINARIES[@]}"; do + echo " Building $bin..." + FEATURES=() + for runtime_bin in "${RUNTIME_BINS[@]}"; do + if [ "$bin" = "$runtime_bin" ]; then + FEATURES=(--features runtime) + break + fi + done + cargo build --release "${FEATURES[@]}" --bin "$bin" 2>&1 | while read line; do + if echo "$line" | grep -q "Compiling libbreenix"; then + echo " [libbreenix] $line" + elif echo "$line" | grep -q "Compiling userspace_tests"; then + echo " [userspace] $line" + else + echo " $line" + fi + done done echo "" From c10ced1211efbe5816c65aeb6583266f7ea40cc4 Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Tue, 27 Jan 2026 09:17:34 -0500 Subject: [PATCH 2/2] arm64: fix setup_mmu link register preservation and device shareability - Save/restore x30 (link register) around setup_mmu helper calls - Change device memory shareability from inner to none for MMIO regions - Add clarifying comment about PL011 UART in device block mapping Co-Authored-By: Claude Opus 4.5 --- kernel/src/arch_impl/aarch64/boot.S | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/src/arch_impl/aarch64/boot.S b/kernel/src/arch_impl/aarch64/boot.S index e1b9d636..00d392df 100644 --- a/kernel/src/arch_impl/aarch64/boot.S +++ b/kernel/src/arch_impl/aarch64/boot.S @@ -25,13 +25,14 @@ .equ DESC_TABLE, (1 << 0) | (1 << 1) // 0b11 .equ DESC_AF, (1 << 10) .equ DESC_SH_INNER, (0b11 << 8) +.equ DESC_SH_NONE, (0b00 << 8) .equ DESC_ATTR_DEVICE, (0 << 2) .equ DESC_ATTR_NORMAL, (1 << 2) .equ DESC_AP_KERNEL, (0 << 6) // EL1 RW, EL0 no access .equ DESC_PXN, (1 << 53) .equ DESC_UXN, (1 << 54) -.equ BLOCK_FLAGS_DEVICE, (DESC_BLOCK | DESC_AF | DESC_SH_INNER | DESC_AP_KERNEL | DESC_ATTR_DEVICE | DESC_PXN | DESC_UXN) +.equ BLOCK_FLAGS_DEVICE, (DESC_BLOCK | DESC_AF | DESC_SH_NONE | DESC_AP_KERNEL | DESC_ATTR_DEVICE | DESC_PXN | DESC_UXN) .equ BLOCK_FLAGS_NORMAL, (DESC_BLOCK | DESC_AF | DESC_SH_INNER | DESC_AP_KERNEL | DESC_ATTR_NORMAL) // MAIR attributes @@ -245,6 +246,7 @@ lower_el_aarch32_serror: .section .text.boot setup_mmu: + str x30, [sp, #-16]! // Save link register // Zero page tables ldr x0, =ttbr0_l0 bl zero_table @@ -280,7 +282,7 @@ setup_mmu: orr x1, x1, #DESC_TABLE str x1, [x0] - // TTBR1 L1[0] = device (high-half direct map) + // TTBR1 L1[0] = device (high-half direct map, includes PL011 @ 0x0900_0000) ldr x0, =ttbr1_l1 ldr x1, =0x00000000 ldr x2, =BLOCK_FLAGS_DEVICE @@ -330,6 +332,7 @@ setup_mmu: msr sctlr_el1, x0 isb + ldr x30, [sp], #16 // Restore link register ret // Zero a 4KB table at x0