From 1b6b400a852cf164c80637af433493b0ef62c1db Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Mon, 22 Dec 2025 17:00:51 +0530 Subject: [PATCH 1/7] Move QEMU executables to a non-default member, test-qemu, and add multi-hart tests --- .github/workflows/qemu.yaml | 20 ++- Cargo.toml | 1 + ci/expected/multi_hart.run | 3 + tests-qemu/Cargo.toml | 19 +++ tests-qemu/build.rs | 27 ++++ tests-qemu/examples/multi_hart.rs | 147 ++++++++++++++++++ .../examples/qemu_semihosting.rs | 5 - .../examples/qemu_uart.rs | 5 - tests-qemu/memory-m-mode-multihart.x | 12 ++ .../memory-m-mode.x | 0 tests-qemu/memory-s-mode.x | 11 ++ tests-qemu/src/lib.rs | 1 + xtask/src/main.rs | 62 +++++--- 13 files changed, 278 insertions(+), 35 deletions(-) create mode 100644 ci/expected/multi_hart.run create mode 100644 tests-qemu/Cargo.toml create mode 100644 tests-qemu/build.rs create mode 100644 tests-qemu/examples/multi_hart.rs rename {riscv-rt => tests-qemu}/examples/qemu_semihosting.rs (50%) rename {riscv-rt => tests-qemu}/examples/qemu_uart.rs (87%) create mode 100644 tests-qemu/memory-m-mode-multihart.x rename riscv-rt/examples/device_virt.x => tests-qemu/memory-m-mode.x (100%) create mode 100644 tests-qemu/memory-s-mode.x create mode 100644 tests-qemu/src/lib.rs diff --git a/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml index 805f00dd..9df2f347 100644 --- a/.github/workflows/qemu.yaml +++ b/.github/workflows/qemu.yaml @@ -32,9 +32,15 @@ jobs: qemu: riscv64 - target: riscv64gc-unknown-none-elf qemu: riscv64 - example: - - qemu_uart - - qemu_semihosting + example-features: + - example: qemu_uart + features: "" + - example: qemu_uart + features: "s-mode" + - example: qemu_semihosting + features: "" + - example: multi_hart + features: "multi-hart" steps: - name: Checkout @@ -55,5 +61,9 @@ jobs: sudo apt install -y qemu-system-${{ matrix.target-qemu.qemu }} - name: Run-pass tests - run: cargo run --package xtask -- qemu --target ${{ matrix.target-qemu.target }} --example ${{ matrix.example }} - + run: | + if [ -n "${{ matrix.example-features.features }}" ]; then + cargo run --package xtask -- qemu --target ${{ matrix.target-qemu.target }} --example ${{ matrix.example-features.example }} --features ${{ matrix.example-features.features }} + else + cargo run --package xtask -- qemu --target ${{ matrix.target-qemu.target }} --example ${{ matrix.example-features.example }} + fi diff --git a/Cargo.toml b/Cargo.toml index 65b34667..a86f6b13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "riscv-target-parser", "riscv-types", "tests-build", + "tests-qemu", "tests-trybuild", "xtask", ] diff --git a/ci/expected/multi_hart.run b/ci/expected/multi_hart.run new file mode 100644 index 00000000..567b19b2 --- /dev/null +++ b/ci/expected/multi_hart.run @@ -0,0 +1,3 @@ +Hart 0: Initializing +Hart 1: Running +Hart 0: Both harts done diff --git a/tests-qemu/Cargo.toml b/tests-qemu/Cargo.toml new file mode 100644 index 00000000..a32aff0f --- /dev/null +++ b/tests-qemu/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "tests-qemu" +version = "0.1.0" +edition = "2021" + +[build-dependencies] + +[dependencies] +panic-halt = "1.0" +riscv = { path = "../riscv" } +riscv-rt = { path = "../riscv-rt" } +riscv-semihosting = { path = "../riscv-semihosting" } + +[features] +default = ["m-mode", "single-hart"] +s-mode = ["riscv-rt/s-mode", "single-hart"] +m-mode = [] +single-hart = ["riscv-rt/single-hart", "riscv/critical-section-single-hart"] +multi-hart = ["m-mode"] diff --git a/tests-qemu/build.rs b/tests-qemu/build.rs new file mode 100644 index 00000000..1ad78f59 --- /dev/null +++ b/tests-qemu/build.rs @@ -0,0 +1,27 @@ +use std::{env, fs::File, io::Write, path::PathBuf}; + +fn main() { + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + let s_mode = env::var_os("CARGO_FEATURE_S_MODE").is_some(); + let multi_hart = env::var_os("CARGO_FEATURE_MULTI_HART").is_some(); + + let memory_x = match (s_mode, multi_hart) { + (true, _) => include_bytes!("memory-s-mode.x").as_slice(), + (false, true) => include_bytes!("memory-m-mode-multihart.x").as_slice(), + (false, false) => include_bytes!("memory-m-mode.x").as_slice(), + }; + + File::create(out.join("memory.x")) + .unwrap() + .write_all(memory_x) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rustc-link-arg=-Tmemory.x"); + + println!("cargo:rerun-if-changed=memory-m-mode.x"); + println!("cargo:rerun-if-changed=memory-m-mode-multihart.x"); + println!("cargo:rerun-if-changed=memory-s-mode.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/tests-qemu/examples/multi_hart.rs b/tests-qemu/examples/multi_hart.rs new file mode 100644 index 00000000..96247d2d --- /dev/null +++ b/tests-qemu/examples/multi_hart.rs @@ -0,0 +1,147 @@ +#![no_std] +#![no_main] + +extern crate panic_halt; + +use core::arch::global_asm; +use core::sync::atomic::{AtomicBool, Ordering}; +use riscv_rt::entry; +use riscv_semihosting::debug::{self, EXIT_SUCCESS}; + +const UART_BASE: usize = 0x1000_0000; +const UART_THR: usize = UART_BASE; +const UART_LCR: usize = UART_BASE + 3; +const UART_LSR: usize = UART_BASE + 5; +const LCR_DLAB: u8 = 1 << 7; +const LCR_8N1: u8 = 0x03; +const LSR_THRE: u8 = 1 << 5; + +static UART_LOCK: AtomicBool = AtomicBool::new(false); +static HART0_READY: AtomicBool = AtomicBool::new(false); +static HART1_DONE: AtomicBool = AtomicBool::new(false); + +fn uart_lock() { + while UART_LOCK + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + core::hint::spin_loop(); + } +} + +fn uart_unlock() { + UART_LOCK.store(false, Ordering::Release); +} + +unsafe fn uart_write_reg(off: usize, v: u8) { + (off as *mut u8).write_volatile(v); +} + +unsafe fn uart_read_reg(off: usize) -> u8 { + (off as *const u8).read_volatile() +} + +fn uart_init() { + unsafe { + uart_write_reg(UART_LCR, LCR_DLAB); + uart_write_reg(UART_THR, 0x01); + uart_write_reg(UART_BASE + 1, 0x00); + uart_write_reg(UART_LCR, LCR_8N1); + uart_write_reg(UART_BASE + 2, 0x07); + } +} + +fn uart_write_byte(b: u8) { + unsafe { + while (uart_read_reg(UART_LSR) & LSR_THRE) == 0 {} + uart_write_reg(UART_THR, b); + } +} + +fn uart_print(s: &str) { + uart_lock(); + for &b in s.as_bytes() { + uart_write_byte(b); + } + uart_unlock(); +} + +// Custom _mp_hook implementation in assembly +// Hart 0 returns 1 (true) to initialize RAM +// Hart 1 waits for IPI via CLINT, then returns 0 (false) to skip RAM init +global_asm!( + r#" +.section .init.mp_hook, "ax" +.global _mp_hook +_mp_hook: + beqz a0, 2f // if hart 0, return true + + // Hart 1: Wait for IPI + // Clear any pending software interrupt first + li t0, 0x02000004 // CLINT msip address for hart 1 + sw zero, 0(t0) + + // Enable machine software interrupts + li t0, 8 // MIE.MSIE bit + csrs mie, t0 + +1: wfi // Wait for interrupt + // Check if software interrupt pending + csrr t0, mip + andi t0, t0, 8 // Check MSIP bit + beqz t0, 1b // If not set, keep waiting + + // Disable machine software interrupts + li t0, 8 + csrc mie, t0 + + // Clear the software interrupt + li t0, 0x02000004 + sw zero, 0(t0) + + // Return false (0) - don't initialize RAM again + li a0, 0 + ret + +2: // Hart 0: return true to initialize RAM + li a0, 1 + ret +"# +); + +#[entry] +fn main(hartid: usize) -> ! { + if hartid == 0 { + uart_init(); + uart_print("Hart 0: Initializing\n"); + HART0_READY.store(true, Ordering::Release); + + // Small delay to allow Hart 1 to reach wfi() in _mp_hook + // This is needed because we can't use atomics in assembly _mp_hook + for _ in 0..100000 { + core::hint::spin_loop(); + } + + // Send IPI to Hart 1 (write to CLINT msip register for hart 1) + unsafe { + (0x02000004usize as *mut u32).write_volatile(1); + } + + while !HART1_DONE.load(Ordering::Acquire) { + core::hint::spin_loop(); + } + + uart_print("Hart 0: Both harts done\n"); + debug::exit(EXIT_SUCCESS); + } else { + while !HART0_READY.load(Ordering::Acquire) { + core::hint::spin_loop(); + } + uart_print("Hart 1: Running\n"); + HART1_DONE.store(true, Ordering::Release); + } + + loop { + core::hint::spin_loop(); + } +} diff --git a/riscv-rt/examples/qemu_semihosting.rs b/tests-qemu/examples/qemu_semihosting.rs similarity index 50% rename from riscv-rt/examples/qemu_semihosting.rs rename to tests-qemu/examples/qemu_semihosting.rs index 52d6eb40..6fbbae45 100644 --- a/riscv-rt/examples/qemu_semihosting.rs +++ b/tests-qemu/examples/qemu_semihosting.rs @@ -1,8 +1,3 @@ -//! Semihosting example for QEMU -//! -//! This example uses RISC-V semihosting to print output and cleanly exit QEMU. -//! Run with: `qemu-system-riscv32 -machine virt -nographic -semihosting-config enable=on,target=native -bios none -kernel ` - #![no_std] #![no_main] diff --git a/riscv-rt/examples/qemu_uart.rs b/tests-qemu/examples/qemu_uart.rs similarity index 87% rename from riscv-rt/examples/qemu_uart.rs rename to tests-qemu/examples/qemu_uart.rs index 81971ea7..6031eb73 100644 --- a/riscv-rt/examples/qemu_uart.rs +++ b/tests-qemu/examples/qemu_uart.rs @@ -1,8 +1,3 @@ -//! UART example for QEMU virt machine -//! -//! This example demonstrates direct UART output on QEMU's virt machine. -//! It writes to the NS16550-compatible UART at 0x1000_0000. - #![no_std] #![no_main] diff --git a/tests-qemu/memory-m-mode-multihart.x b/tests-qemu/memory-m-mode-multihart.x new file mode 100644 index 00000000..e9e4fe61 --- /dev/null +++ b/tests-qemu/memory-m-mode-multihart.x @@ -0,0 +1,12 @@ +MEMORY +{ + RAM : ORIGIN = 0x80000000, LENGTH = 16M +} +REGION_ALIAS("REGION_TEXT", RAM); +REGION_ALIAS("REGION_RODATA", RAM); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); +_max_hart_id = 1; +INCLUDE link.x diff --git a/riscv-rt/examples/device_virt.x b/tests-qemu/memory-m-mode.x similarity index 100% rename from riscv-rt/examples/device_virt.x rename to tests-qemu/memory-m-mode.x diff --git a/tests-qemu/memory-s-mode.x b/tests-qemu/memory-s-mode.x new file mode 100644 index 00000000..2f9a9585 --- /dev/null +++ b/tests-qemu/memory-s-mode.x @@ -0,0 +1,11 @@ +MEMORY +{ + RAM : ORIGIN = 0x80200000, LENGTH = 16M +} +REGION_ALIAS("REGION_TEXT", RAM); +REGION_ALIAS("REGION_RODATA", RAM); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); +INCLUDE link.x diff --git a/tests-qemu/src/lib.rs b/tests-qemu/src/lib.rs new file mode 100644 index 00000000..0c9ac1ac --- /dev/null +++ b/tests-qemu/src/lib.rs @@ -0,0 +1 @@ +#![no_std] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 876dae51..30508fa5 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -23,10 +23,32 @@ fn find_golden_file(target: &str, example: &str) -> Option { None } +fn filter_output(raw: &str, s_mode: bool) -> String { + let mut lines: Vec<&str> = raw.lines().collect(); + + if s_mode { + if let Some(pos) = lines.iter().position(|l| l.contains("Boot HART MEDELEG")) { + lines = lines.into_iter().skip(pos + 1).collect(); + } + } + + let filtered: Vec<&str> = lines + .into_iter() + .filter(|l| !l.starts_with("QEMU ") && !l.contains("monitor")) + .collect(); + + let result = filtered.join("\n"); + if result.is_empty() { + String::new() + } else { + format!("{}\n", result.trim()) + } +} + fn main() -> anyhow::Result<()> { let mut args = std::env::args().skip(1).collect::>(); if args.is_empty() || args[0] != "qemu" { - bail!("usage: cargo run -p xtask -- qemu --target --example "); + bail!("usage: cargo run -p xtask -- qemu --target --example [--features ]"); } args.remove(0); let mut target = None; @@ -54,24 +76,27 @@ fn main() -> anyhow::Result<()> { } let target = target.context("--target required")?; let example = example.context("--example required")?; - let mut rustflags = "-C link-arg=-Triscv-rt/examples/device_virt.x".to_string(); - if let Some(f) = &features { - if f.contains("s-mode") { - rustflags = "-C link-arg=-Triscv-rt/examples/device_virt_s.x".into(); - } - } + + let feat = features.as_deref().unwrap_or(""); + let s_mode = feat.contains("s-mode"); + let multi_hart = feat.contains("multi-hart"); let mut cmd = Command::new("cargo"); - cmd.env("RUSTFLAGS", rustflags).args([ + cmd.args([ "build", "--package", - "riscv-rt", + "tests-qemu", "--release", "--target", &target, "--example", &example, ]); + // Disable default features when specifying s-mode or multi-hart + // since they are mutually exclusive configurations + if s_mode || multi_hart { + cmd.arg("--no-default-features"); + } cmd.apply_features(features.as_deref()); let status = cmd.status()?; if !status.success() { @@ -94,10 +119,16 @@ fn main() -> anyhow::Result<()> { "-semihosting-config", "enable=on,target=native", ]; - if !features.as_deref().unwrap_or("").contains("s-mode") { + if !s_mode { qemu_args.push("-bios"); qemu_args.push("none"); } + let smp_arg; + if multi_hart { + smp_arg = "2".to_string(); + qemu_args.push("-smp"); + qemu_args.push(&smp_arg); + } let kernel_path = format!("target/{}/release/examples/{}", target, example); let child = Command::new(qemu) .args(&qemu_args) @@ -109,16 +140,7 @@ fn main() -> anyhow::Result<()> { .context("running qemu")?; let output = child.wait_with_output()?; let raw_stdout = String::from_utf8_lossy(&output.stdout).into_owned(); - let stdout = raw_stdout - .lines() - .filter(|l| !l.starts_with("QEMU ") && !l.contains("monitor")) - .collect::>() - .join("\n"); - let stdout = if stdout.is_empty() { - String::new() - } else { - format!("{}\n", stdout.trim()) - }; + let stdout = filter_output(&raw_stdout, s_mode); let expected_path = match find_golden_file(&target, &example) { Some(p) => p, From 9d45dbd16d70a8d19e7aced5e350e7f0a0d5533d Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Mon, 22 Dec 2025 17:16:22 +0530 Subject: [PATCH 2/7] Fix CI failing --- .github/workflows/qemu.yaml | 39 +++++++++++++++++++++++++++++++++++-- riscv-rt/CHANGELOG.md | 1 + 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml index 9df2f347..d4630152 100644 --- a/.github/workflows/qemu.yaml +++ b/.github/workflows/qemu.yaml @@ -35,12 +35,47 @@ jobs: example-features: - example: qemu_uart features: "" - - example: qemu_uart - features: "s-mode" - example: qemu_semihosting features: "" + - example: qemu_uart + features: "s-mode" - example: multi_hart features: "multi-hart" + exclude: + # multi_hart requires atomics extension ('a'), exclude targets without it + - example-features: + example: multi_hart + target-qemu: + target: riscv32i-unknown-none-elf + - example-features: + example: multi_hart + target-qemu: + target: riscv32im-unknown-none-elf + - example-features: + example: multi_hart + target-qemu: + target: riscv32imc-unknown-none-elf + # s-mode on riscv32 requires OpenSBI which has issues with some targets + - example-features: + features: "s-mode" + target-qemu: + target: riscv32i-unknown-none-elf + - example-features: + features: "s-mode" + target-qemu: + target: riscv32im-unknown-none-elf + - example-features: + features: "s-mode" + target-qemu: + target: riscv32imc-unknown-none-elf + - example-features: + features: "s-mode" + target-qemu: + target: riscv32imac-unknown-none-elf + - example-features: + features: "s-mode" + target-qemu: + target: riscv32imafc-unknown-none-elf steps: - name: Checkout diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index a1a2d3fc..fb674d66 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Moved QEMU test examples to dedicated `tests-qemu` crate with support for multiple configurations (m-mode, s-mode, single-hart, multi-hart) - Added examples for CI tests using semihosting and UART - New `no-mhartid` feature to load 0 to `a0` instead of reading `mhartid`. - New `no-xtvec` feature that removes interrupt stuff. From 209053fe4fa2ea89ef2bfa004c15da0c39594730 Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Sat, 27 Dec 2025 15:07:31 +0530 Subject: [PATCH 3/7] Address review --- tests-qemu/Cargo.toml | 5 ++- tests-qemu/build.rs | 13 ++++--- tests-qemu/examples/multi_hart.rs | 34 ++++++------------- tests-qemu/examples/qemu_semihosting.rs | 4 +++ tests-qemu/examples/qemu_uart.rs | 5 +++ ...-m-mode-multihart.x => memory-multihart.x} | 0 tests-qemu/{memory-m-mode.x => memory.x} | 0 7 files changed, 30 insertions(+), 31 deletions(-) rename tests-qemu/{memory-m-mode-multihart.x => memory-multihart.x} (100%) rename tests-qemu/{memory-m-mode.x => memory.x} (100%) diff --git a/tests-qemu/Cargo.toml b/tests-qemu/Cargo.toml index a32aff0f..33a06f2c 100644 --- a/tests-qemu/Cargo.toml +++ b/tests-qemu/Cargo.toml @@ -12,8 +12,7 @@ riscv-rt = { path = "../riscv-rt" } riscv-semihosting = { path = "../riscv-semihosting" } [features] -default = ["m-mode", "single-hart"] +default = ["single-hart"] s-mode = ["riscv-rt/s-mode", "single-hart"] -m-mode = [] single-hart = ["riscv-rt/single-hart", "riscv/critical-section-single-hart"] -multi-hart = ["m-mode"] +multi-hart = [] diff --git a/tests-qemu/build.rs b/tests-qemu/build.rs index 1ad78f59..6d2ecdd3 100644 --- a/tests-qemu/build.rs +++ b/tests-qemu/build.rs @@ -6,10 +6,15 @@ fn main() { let s_mode = env::var_os("CARGO_FEATURE_S_MODE").is_some(); let multi_hart = env::var_os("CARGO_FEATURE_MULTI_HART").is_some(); + // Multi-hart is only supported in M-mode + if multi_hart && s_mode { + panic!("multi-hart feature is only compatible with M-mode, not S-mode"); + } + let memory_x = match (s_mode, multi_hart) { (true, _) => include_bytes!("memory-s-mode.x").as_slice(), - (false, true) => include_bytes!("memory-m-mode-multihart.x").as_slice(), - (false, false) => include_bytes!("memory-m-mode.x").as_slice(), + (_, true) => include_bytes!("memory-multihart.x").as_slice(), + _ => include_bytes!("memory.x").as_slice(), }; File::create(out.join("memory.x")) @@ -20,8 +25,8 @@ fn main() { println!("cargo:rustc-link-arg=-Tmemory.x"); - println!("cargo:rerun-if-changed=memory-m-mode.x"); - println!("cargo:rerun-if-changed=memory-m-mode-multihart.x"); + println!("cargo:rerun-if-changed=memory.x"); + println!("cargo:rerun-if-changed=memory-multihart.x"); println!("cargo:rerun-if-changed=memory-s-mode.x"); println!("cargo:rerun-if-changed=build.rs"); } diff --git a/tests-qemu/examples/multi_hart.rs b/tests-qemu/examples/multi_hart.rs index 96247d2d..84f59048 100644 --- a/tests-qemu/examples/multi_hart.rs +++ b/tests-qemu/examples/multi_hart.rs @@ -1,3 +1,8 @@ +//! Multi-hart example demonstrating IPI-based hart synchronization. +//! +//! Hart 0 initializes UART and wakes Hart 1 via software interrupt (CLINT). +//! Both harts print messages and synchronize before exit. + #![no_std] #![no_main] @@ -17,7 +22,6 @@ const LCR_8N1: u8 = 0x03; const LSR_THRE: u8 = 1 << 5; static UART_LOCK: AtomicBool = AtomicBool::new(false); -static HART0_READY: AtomicBool = AtomicBool::new(false); static HART1_DONE: AtomicBool = AtomicBool::new(false); fn uart_lock() { @@ -68,7 +72,7 @@ fn uart_print(s: &str) { // Custom _mp_hook implementation in assembly // Hart 0 returns 1 (true) to initialize RAM -// Hart 1 waits for IPI via CLINT, then returns 0 (false) to skip RAM init +// Hart 1 polls for IPI via CLINT, then returns 0 (false) to skip RAM init global_asm!( r#" .section .init.mp_hook, "ax" @@ -76,24 +80,15 @@ global_asm!( _mp_hook: beqz a0, 2f // if hart 0, return true - // Hart 1: Wait for IPI + // Hart 1: Poll for IPI (no interrupts, just polling) // Clear any pending software interrupt first li t0, 0x02000004 // CLINT msip address for hart 1 sw zero, 0(t0) - // Enable machine software interrupts - li t0, 8 // MIE.MSIE bit - csrs mie, t0 - -1: wfi // Wait for interrupt - // Check if software interrupt pending +1: // Poll mip register for software interrupt pending csrr t0, mip andi t0, t0, 8 // Check MSIP bit - beqz t0, 1b // If not set, keep waiting - - // Disable machine software interrupts - li t0, 8 - csrc mie, t0 + beqz t0, 1b // If not set, keep polling // Clear the software interrupt li t0, 0x02000004 @@ -114,13 +109,6 @@ fn main(hartid: usize) -> ! { if hartid == 0 { uart_init(); uart_print("Hart 0: Initializing\n"); - HART0_READY.store(true, Ordering::Release); - - // Small delay to allow Hart 1 to reach wfi() in _mp_hook - // This is needed because we can't use atomics in assembly _mp_hook - for _ in 0..100000 { - core::hint::spin_loop(); - } // Send IPI to Hart 1 (write to CLINT msip register for hart 1) unsafe { @@ -134,9 +122,7 @@ fn main(hartid: usize) -> ! { uart_print("Hart 0: Both harts done\n"); debug::exit(EXIT_SUCCESS); } else { - while !HART0_READY.load(Ordering::Acquire) { - core::hint::spin_loop(); - } + // Hart 1 reaches here after _mp_hook detects IPI uart_print("Hart 1: Running\n"); HART1_DONE.store(true, Ordering::Release); } diff --git a/tests-qemu/examples/qemu_semihosting.rs b/tests-qemu/examples/qemu_semihosting.rs index 6fbbae45..12b6bf22 100644 --- a/tests-qemu/examples/qemu_semihosting.rs +++ b/tests-qemu/examples/qemu_semihosting.rs @@ -1,3 +1,7 @@ +//! Semihosting example for QEMU +//! +//! This example uses RISC-V semihosting to print output and cleanly exit QEMU. +//! Run with: `qemu-system-riscv32 -machine virt -nographic -semihosting-config enable=on,target=native -bios none -kernel ` #![no_std] #![no_main] diff --git a/tests-qemu/examples/qemu_uart.rs b/tests-qemu/examples/qemu_uart.rs index 6031eb73..81971ea7 100644 --- a/tests-qemu/examples/qemu_uart.rs +++ b/tests-qemu/examples/qemu_uart.rs @@ -1,3 +1,8 @@ +//! UART example for QEMU virt machine +//! +//! This example demonstrates direct UART output on QEMU's virt machine. +//! It writes to the NS16550-compatible UART at 0x1000_0000. + #![no_std] #![no_main] diff --git a/tests-qemu/memory-m-mode-multihart.x b/tests-qemu/memory-multihart.x similarity index 100% rename from tests-qemu/memory-m-mode-multihart.x rename to tests-qemu/memory-multihart.x diff --git a/tests-qemu/memory-m-mode.x b/tests-qemu/memory.x similarity index 100% rename from tests-qemu/memory-m-mode.x rename to tests-qemu/memory.x From b8aee79e33fb451a22bf4c0bb54d26a626807ad8 Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Sat, 27 Dec 2025 15:19:25 +0530 Subject: [PATCH 4/7] Fix CI failing --- tests-qemu/build.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests-qemu/build.rs b/tests-qemu/build.rs index 6d2ecdd3..fd667edf 100644 --- a/tests-qemu/build.rs +++ b/tests-qemu/build.rs @@ -6,11 +6,7 @@ fn main() { let s_mode = env::var_os("CARGO_FEATURE_S_MODE").is_some(); let multi_hart = env::var_os("CARGO_FEATURE_MULTI_HART").is_some(); - // Multi-hart is only supported in M-mode - if multi_hart && s_mode { - panic!("multi-hart feature is only compatible with M-mode, not S-mode"); - } - + // Multi-hart is only supported in M-mode; s-mode takes priority if both are enabled let memory_x = match (s_mode, multi_hart) { (true, _) => include_bytes!("memory-s-mode.x").as_slice(), (_, true) => include_bytes!("memory-multihart.x").as_slice(), From cc5a1a4a4d2a21b3a89e2035c4e0dedf5d253440 Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Mon, 29 Dec 2025 21:52:15 +0530 Subject: [PATCH 5/7] Address review --- tests-qemu/build.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests-qemu/build.rs b/tests-qemu/build.rs index fd667edf..39457e97 100644 --- a/tests-qemu/build.rs +++ b/tests-qemu/build.rs @@ -6,7 +6,11 @@ fn main() { let s_mode = env::var_os("CARGO_FEATURE_S_MODE").is_some(); let multi_hart = env::var_os("CARGO_FEATURE_MULTI_HART").is_some(); - // Multi-hart is only supported in M-mode; s-mode takes priority if both are enabled + // Multi-hart is only supported in M-mode + if s_mode && multi_hart { + panic!("multi-hart feature is not compatible with s-mode"); + } + let memory_x = match (s_mode, multi_hart) { (true, _) => include_bytes!("memory-s-mode.x").as_slice(), (_, true) => include_bytes!("memory-multihart.x").as_slice(), From 149c39ce0b6d6e64c2dbdb998344541eb57f1bc5 Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Mon, 29 Dec 2025 21:54:39 +0530 Subject: [PATCH 6/7] revert --- tests-qemu/build.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests-qemu/build.rs b/tests-qemu/build.rs index 39457e97..fd667edf 100644 --- a/tests-qemu/build.rs +++ b/tests-qemu/build.rs @@ -6,11 +6,7 @@ fn main() { let s_mode = env::var_os("CARGO_FEATURE_S_MODE").is_some(); let multi_hart = env::var_os("CARGO_FEATURE_MULTI_HART").is_some(); - // Multi-hart is only supported in M-mode - if s_mode && multi_hart { - panic!("multi-hart feature is not compatible with s-mode"); - } - + // Multi-hart is only supported in M-mode; s-mode takes priority if both are enabled let memory_x = match (s_mode, multi_hart) { (true, _) => include_bytes!("memory-s-mode.x").as_slice(), (_, true) => include_bytes!("memory-multihart.x").as_slice(), From 73f1a1cd6432d3f610f71eb270a22304e36cd4a5 Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Wed, 7 Jan 2026 06:50:27 +0530 Subject: [PATCH 7/7] address review --- .github/workflows/qemu.yaml | 18 +----------------- xtask/src/main.rs | 4 +--- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml index d4630152..b62acb42 100644 --- a/.github/workflows/qemu.yaml +++ b/.github/workflows/qemu.yaml @@ -59,23 +59,7 @@ jobs: - example-features: features: "s-mode" target-qemu: - target: riscv32i-unknown-none-elf - - example-features: - features: "s-mode" - target-qemu: - target: riscv32im-unknown-none-elf - - example-features: - features: "s-mode" - target-qemu: - target: riscv32imc-unknown-none-elf - - example-features: - features: "s-mode" - target-qemu: - target: riscv32imac-unknown-none-elf - - example-features: - features: "s-mode" - target-qemu: - target: riscv32imafc-unknown-none-elf + qemu: riscv32 steps: - name: Checkout diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 30508fa5..face31d3 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -123,11 +123,9 @@ fn main() -> anyhow::Result<()> { qemu_args.push("-bios"); qemu_args.push("none"); } - let smp_arg; if multi_hart { - smp_arg = "2".to_string(); qemu_args.push("-smp"); - qemu_args.push(&smp_arg); + qemu_args.push("2"); } let kernel_path = format!("target/{}/release/examples/{}", target, example); let child = Command::new(qemu)