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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docker/qemu/run-aarch64-boot-test-native.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ run_single_test() {
mkdir -p "$OUTPUT_DIR"

# Run QEMU with 30s timeout
# Always include GPU and keyboard so kernel VirtIO enumeration finds them
timeout 30 qemu-system-aarch64 \
-M virt -cpu cortex-a72 -m 512 \
-kernel "$KERNEL" \
-display none -no-reboot \
-device virtio-gpu-device \
-device virtio-keyboard-device \
-device virtio-blk-device,drive=ext2 \
-drive if=none,id=ext2,format=raw,readonly=on,file="$EXT2_DISK" \
-serial file:"$OUTPUT_DIR/serial.txt" &
Expand Down
7 changes: 5 additions & 2 deletions docker/qemu/run-aarch64-boot-test-strict.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,21 @@ run_single_test() {
mkdir -p "$OUTPUT_DIR"

# Run QEMU with 20s timeout (shorter since we expect consistent success)
# Always include GPU and keyboard so kernel VirtIO enumeration finds them
timeout 20 qemu-system-aarch64 \
-M virt -cpu cortex-a72 -m 512 \
-kernel "$KERNEL" \
-display none -no-reboot \
-device virtio-gpu-device \
-device virtio-keyboard-device \
-device virtio-blk-device,drive=ext2 \
-drive if=none,id=ext2,format=raw,readonly=on,file="$EXT2_DISK" \
-serial file:"$OUTPUT_DIR/serial.txt" &
local QEMU_PID=$!

# Wait for kernel output (15s max, checking every 1.5s)
# Wait for kernel output (18s max, checking every 1.5s)
local BOOT_COMPLETE=false
for i in $(seq 1 10); do
for i in $(seq 1 12); do
if [ -f "$OUTPUT_DIR/serial.txt" ]; then
if grep -qE "(breenix>|Welcome to Breenix|Interactive Shell)" "$OUTPUT_DIR/serial.txt" 2>/dev/null; then
BOOT_COMPLETE=true
Expand Down
2 changes: 2 additions & 0 deletions docker/qemu/run-aarch64-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ docker run --rm \
-m 512 \
-kernel /breenix/kernel \
-display none \
-device virtio-gpu-device \
-device virtio-keyboard-device \
-no-reboot \
-serial file:/output/serial.txt \
&
Expand Down
9 changes: 5 additions & 4 deletions docker/qemu/run-aarch64-userspace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,9 @@ fi
echo "Starting QEMU ARM64 with VirtIO devices..."

# Run QEMU with ARM64 virt machine and VirtIO devices
# QEMU virt machine VirtIO MMIO addresses:
# 0x0a000000 - 0x0a003fff: virtio@a000000 (device 0)
# 0x0a004000 - 0x0a007fff: virtio@a004000 (device 1)
# etc.
# QEMU virt machine provides 32 VirtIO MMIO slots at:
# 0x0a000000 + n*0x200 for n=0..31
# Devices are assigned from slot 31 downward.
docker run --rm \
-v "$KERNEL:/breenix/kernel:ro" \
-v "$EXT2_DISK:/breenix/ext2.img:ro" \
Expand All @@ -77,6 +76,8 @@ docker run --rm \
-kernel /breenix/kernel \
-drive if=none,id=ext2disk,format=raw,readonly=on,file=/breenix/ext2.img \
-device virtio-blk-device,drive=ext2disk \
-device virtio-gpu-device \
-device virtio-keyboard-device \
-display none \
-no-reboot \
-serial file:/output/serial.txt \
Expand Down
30 changes: 30 additions & 0 deletions kernel/src/arch_impl/aarch64/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,33 @@ pub const KERNEL_STACK_SIZE: usize = 512 * 1024;

/// Guard page size between stacks.
pub const STACK_GUARD_SIZE: usize = PAGE_SIZE;

// ============================================================================
// Per-CPU Stack Region Constants
// ============================================================================

/// Base address for per-CPU kernel stacks region (ARM64).
/// Uses a region within the HHDM (higher-half direct map) that is mapped
/// by the boot page tables. Placed at physical 0x4100_0000 (16MB into RAM
/// after kernel) to stay within typical 512MB QEMU RAM configs.
///
/// QEMU virt RAM layout: physical 0x4000_0000 (1GB mark) for N MB
/// With 512MB RAM: physical 0x4000_0000 to 0x6000_0000
///
/// Stack layout in RAM:
/// - 0x4000_0000 - 0x4100_0000: Kernel image (~16MB)
/// - 0x4100_0000 - 0x4200_0000: Per-CPU stacks (16MB for 8 CPUs)
/// - 0x4200_0000 - 0x6000_0000: Heap and dynamic allocations
///
/// Virtual: 0xFFFF_0000_4100_0000
/// Physical: 0x4100_0000
pub const PERCPU_STACK_REGION_BASE: u64 = HHDM_BASE + 0x4100_0000;

/// Maximum number of CPUs supported on ARM64.
/// Limited to 8 to keep stack region within 512MB RAM constraint.
/// (8 CPUs * 2MB stride = 16MB total)
pub const MAX_CPUS: usize = 8;

/// Total size of per-CPU stack region (ARM64).
/// 8 CPUs * 2MB stride = 16MB
pub const PERCPU_STACK_REGION_SIZE: usize = MAX_CPUS * 2 * 1024 * 1024;
9 changes: 8 additions & 1 deletion kernel/src/arch_impl/aarch64/exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,14 @@ pub extern "C" fn handle_irq() {

// SPIs (32-1019) - Shared peripheral interrupts
// Note: No logging here - interrupt handlers must be < 1000 cycles
32..=1019 => {}
32..=1019 => {
// VirtIO input (keyboard) interrupt dispatch
if let Some(input_irq) = crate::drivers::virtio::input_mmio::get_irq() {
if irq_id == input_irq {
crate::drivers::virtio::input_mmio::handle_interrupt();
}
}
}

// Should not happen - GIC filters invalid IDs (1020+)
_ => {}
Expand Down
13 changes: 13 additions & 0 deletions kernel/src/arch_impl/aarch64/gic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,19 @@ impl InterruptController for Gicv2 {
let reg_index = irq / IRQS_PER_ENABLE_REG;
let bit = irq % IRQS_PER_ENABLE_REG;

// For SPIs (32+), ensure CPU target is set to CPU 0
if irq >= 32 {
let target_reg = irq / 4;
let target_byte = irq % 4;
let current = gicd_read(GICD_ITARGETSR + (target_reg as usize * 4));
let mask = 0xFFu32 << (target_byte * 8);
let target_val = 0x01u32 << (target_byte * 8); // CPU 0
gicd_write(
GICD_ITARGETSR + (target_reg as usize * 4),
(current & !mask) | target_val,
);
}

// Write 1 to ISENABLER to enable (writes of 0 have no effect)
gicd_write(GICD_ISENABLER + (reg_index as usize * 4), 1 << bit);
}
Expand Down
2 changes: 0 additions & 2 deletions kernel/src/arch_impl/aarch64/timer_interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,6 @@ fn poll_keyboard_to_stdin() {
if pressed {
let shift = SHIFT_PRESSED.load(core::sync::atomic::Ordering::Relaxed);
if let Some(c) = input_mmio::keycode_to_char(keycode, shift) {
// Debug marker: VirtIO key event -> stdin
raw_serial_str(b"[VIRTIO_KEY]");
// Push to stdin buffer so userspace can read it
crate::ipc::stdin::push_byte_from_irq(c as u8);
}
Expand Down
2 changes: 2 additions & 0 deletions kernel/src/graphics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub mod arm64_fb;
pub mod demo;
pub mod double_buffer;
pub mod font;
#[cfg(target_arch = "aarch64")]
pub mod particles;
pub mod primitives;
#[cfg(all(target_arch = "x86_64", feature = "interactive"))]
pub mod render_queue;
Expand Down
30 changes: 0 additions & 30 deletions kernel/src/ipc/stdin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,6 @@ pub fn push_byte_from_irq(byte: u8) -> bool {
// Try to acquire the buffer lock - don't block in interrupt context
if let Some(mut buffer) = STDIN_BUFFER.try_lock() {
if buffer.push_byte(byte) {
// Debug marker: byte successfully pushed to stdin
#[cfg(target_arch = "aarch64")]
{
crate::serial_aarch64::raw_serial_str(b"[STDIN_PUSH]");
}
drop(buffer);

// Try to wake blocked readers (may fail if scheduler lock is held)
Expand Down Expand Up @@ -158,46 +153,21 @@ fn wake_blocked_readers_try() {
crate::task::scheduler::set_need_resched();
}

/// Raw serial output for debugging - write a string without locks
#[cfg(target_arch = "aarch64")]
#[inline(always)]
fn raw_serial_str(s: &[u8]) {
crate::serial_aarch64::raw_serial_str(s);
}

/// Wake blocked readers on ARM64 (non-blocking version for interrupt context)
#[cfg(target_arch = "aarch64")]
fn wake_blocked_readers_try() {
let readers: alloc::vec::Vec<u64> = {
if let Some(mut blocked) = BLOCKED_READERS.try_lock() {
blocked.drain(..).collect()
} else {
// Debug marker: couldn't get lock
raw_serial_str(b"[STDIN_LOCK_FAIL]");
return; // Can't get lock, readers will be woken when they retry
}
};

if readers.is_empty() {
// Debug marker: no readers to wake
raw_serial_str(b"[STDIN_NO_READERS]");
return;
}

// Debug marker: waking readers with count
match readers.len() {
1 => raw_serial_str(b"[WAKE_READERS:1]"),
2 => raw_serial_str(b"[WAKE_READERS:2]"),
3 => raw_serial_str(b"[WAKE_READERS:3]"),
4 => raw_serial_str(b"[WAKE_READERS:4]"),
5 => raw_serial_str(b"[WAKE_READERS:5]"),
6 => raw_serial_str(b"[WAKE_READERS:6]"),
7 => raw_serial_str(b"[WAKE_READERS:7]"),
8 => raw_serial_str(b"[WAKE_READERS:8]"),
9 => raw_serial_str(b"[WAKE_READERS:9]"),
_ => raw_serial_str(b"[WAKE_READERS:N]"),
}

// Try to wake threads via the scheduler
crate::task::scheduler::with_scheduler(|sched| {
for thread_id in &readers {
Expand Down
Loading
Loading