Skip to content

Commit d0893ea

Browse files
Merge pull request #382 from KushalMeghani1644/update_qemu_ci
Move QEMU executables to a non-default member `test-qemu`, and add multi-hart tests
2 parents 29ef54b + 73f1a1c commit d0893ea

File tree

14 files changed

+282
-26
lines changed

14 files changed

+282
-26
lines changed

.github/workflows/qemu.yaml

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,34 @@ jobs:
3232
qemu: riscv64
3333
- target: riscv64gc-unknown-none-elf
3434
qemu: riscv64
35-
example:
36-
- qemu_uart
37-
- qemu_semihosting
35+
example-features:
36+
- example: qemu_uart
37+
features: ""
38+
- example: qemu_semihosting
39+
features: ""
40+
- example: qemu_uart
41+
features: "s-mode"
42+
- example: multi_hart
43+
features: "multi-hart"
44+
exclude:
45+
# multi_hart requires atomics extension ('a'), exclude targets without it
46+
- example-features:
47+
example: multi_hart
48+
target-qemu:
49+
target: riscv32i-unknown-none-elf
50+
- example-features:
51+
example: multi_hart
52+
target-qemu:
53+
target: riscv32im-unknown-none-elf
54+
- example-features:
55+
example: multi_hart
56+
target-qemu:
57+
target: riscv32imc-unknown-none-elf
58+
# s-mode on riscv32 requires OpenSBI which has issues with some targets
59+
- example-features:
60+
features: "s-mode"
61+
target-qemu:
62+
qemu: riscv32
3863

3964
steps:
4065
- name: Checkout
@@ -55,5 +80,9 @@ jobs:
5580
sudo apt install -y qemu-system-${{ matrix.target-qemu.qemu }}
5681
5782
- name: Run-pass tests
58-
run: cargo run --package xtask -- qemu --target ${{ matrix.target-qemu.target }} --example ${{ matrix.example }}
59-
83+
run: |
84+
if [ -n "${{ matrix.example-features.features }}" ]; then
85+
cargo run --package xtask -- qemu --target ${{ matrix.target-qemu.target }} --example ${{ matrix.example-features.example }} --features ${{ matrix.example-features.features }}
86+
else
87+
cargo run --package xtask -- qemu --target ${{ matrix.target-qemu.target }} --example ${{ matrix.example-features.example }}
88+
fi

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ members = [
99
"riscv-target-parser",
1010
"riscv-types",
1111
"tests-build",
12+
"tests-qemu",
1213
"tests-trybuild",
1314
"xtask",
1415
]

ci/expected/multi_hart.run

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Hart 0: Initializing
2+
Hart 1: Running
3+
Hart 0: Both harts done

riscv-rt/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111

1212
### Added
1313

14+
- Moved QEMU test examples to dedicated `tests-qemu` crate with support for multiple configurations (m-mode, s-mode, single-hart, multi-hart)
1415
- Added examples for CI tests using semihosting and UART
1516
- New `no-mhartid` feature to load 0 to `a0` instead of reading `mhartid`.
1617
- New `no-xtvec` feature that removes interrupt stuff.

tests-qemu/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "tests-qemu"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[build-dependencies]
7+
8+
[dependencies]
9+
panic-halt = "1.0"
10+
riscv = { path = "../riscv" }
11+
riscv-rt = { path = "../riscv-rt" }
12+
riscv-semihosting = { path = "../riscv-semihosting" }
13+
14+
[features]
15+
default = ["single-hart"]
16+
s-mode = ["riscv-rt/s-mode", "single-hart"]
17+
single-hart = ["riscv-rt/single-hart", "riscv/critical-section-single-hart"]
18+
multi-hart = []

tests-qemu/build.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use std::{env, fs::File, io::Write, path::PathBuf};
2+
3+
fn main() {
4+
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
5+
6+
let s_mode = env::var_os("CARGO_FEATURE_S_MODE").is_some();
7+
let multi_hart = env::var_os("CARGO_FEATURE_MULTI_HART").is_some();
8+
9+
// Multi-hart is only supported in M-mode; s-mode takes priority if both are enabled
10+
let memory_x = match (s_mode, multi_hart) {
11+
(true, _) => include_bytes!("memory-s-mode.x").as_slice(),
12+
(_, true) => include_bytes!("memory-multihart.x").as_slice(),
13+
_ => include_bytes!("memory.x").as_slice(),
14+
};
15+
16+
File::create(out.join("memory.x"))
17+
.unwrap()
18+
.write_all(memory_x)
19+
.unwrap();
20+
println!("cargo:rustc-link-search={}", out.display());
21+
22+
println!("cargo:rustc-link-arg=-Tmemory.x");
23+
24+
println!("cargo:rerun-if-changed=memory.x");
25+
println!("cargo:rerun-if-changed=memory-multihart.x");
26+
println!("cargo:rerun-if-changed=memory-s-mode.x");
27+
println!("cargo:rerun-if-changed=build.rs");
28+
}

tests-qemu/examples/multi_hart.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//! Multi-hart example demonstrating IPI-based hart synchronization.
2+
//!
3+
//! Hart 0 initializes UART and wakes Hart 1 via software interrupt (CLINT).
4+
//! Both harts print messages and synchronize before exit.
5+
6+
#![no_std]
7+
#![no_main]
8+
9+
extern crate panic_halt;
10+
11+
use core::arch::global_asm;
12+
use core::sync::atomic::{AtomicBool, Ordering};
13+
use riscv_rt::entry;
14+
use riscv_semihosting::debug::{self, EXIT_SUCCESS};
15+
16+
const UART_BASE: usize = 0x1000_0000;
17+
const UART_THR: usize = UART_BASE;
18+
const UART_LCR: usize = UART_BASE + 3;
19+
const UART_LSR: usize = UART_BASE + 5;
20+
const LCR_DLAB: u8 = 1 << 7;
21+
const LCR_8N1: u8 = 0x03;
22+
const LSR_THRE: u8 = 1 << 5;
23+
24+
static UART_LOCK: AtomicBool = AtomicBool::new(false);
25+
static HART1_DONE: AtomicBool = AtomicBool::new(false);
26+
27+
fn uart_lock() {
28+
while UART_LOCK
29+
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
30+
.is_err()
31+
{
32+
core::hint::spin_loop();
33+
}
34+
}
35+
36+
fn uart_unlock() {
37+
UART_LOCK.store(false, Ordering::Release);
38+
}
39+
40+
unsafe fn uart_write_reg(off: usize, v: u8) {
41+
(off as *mut u8).write_volatile(v);
42+
}
43+
44+
unsafe fn uart_read_reg(off: usize) -> u8 {
45+
(off as *const u8).read_volatile()
46+
}
47+
48+
fn uart_init() {
49+
unsafe {
50+
uart_write_reg(UART_LCR, LCR_DLAB);
51+
uart_write_reg(UART_THR, 0x01);
52+
uart_write_reg(UART_BASE + 1, 0x00);
53+
uart_write_reg(UART_LCR, LCR_8N1);
54+
uart_write_reg(UART_BASE + 2, 0x07);
55+
}
56+
}
57+
58+
fn uart_write_byte(b: u8) {
59+
unsafe {
60+
while (uart_read_reg(UART_LSR) & LSR_THRE) == 0 {}
61+
uart_write_reg(UART_THR, b);
62+
}
63+
}
64+
65+
fn uart_print(s: &str) {
66+
uart_lock();
67+
for &b in s.as_bytes() {
68+
uart_write_byte(b);
69+
}
70+
uart_unlock();
71+
}
72+
73+
// Custom _mp_hook implementation in assembly
74+
// Hart 0 returns 1 (true) to initialize RAM
75+
// Hart 1 polls for IPI via CLINT, then returns 0 (false) to skip RAM init
76+
global_asm!(
77+
r#"
78+
.section .init.mp_hook, "ax"
79+
.global _mp_hook
80+
_mp_hook:
81+
beqz a0, 2f // if hart 0, return true
82+
83+
// Hart 1: Poll for IPI (no interrupts, just polling)
84+
// Clear any pending software interrupt first
85+
li t0, 0x02000004 // CLINT msip address for hart 1
86+
sw zero, 0(t0)
87+
88+
1: // Poll mip register for software interrupt pending
89+
csrr t0, mip
90+
andi t0, t0, 8 // Check MSIP bit
91+
beqz t0, 1b // If not set, keep polling
92+
93+
// Clear the software interrupt
94+
li t0, 0x02000004
95+
sw zero, 0(t0)
96+
97+
// Return false (0) - don't initialize RAM again
98+
li a0, 0
99+
ret
100+
101+
2: // Hart 0: return true to initialize RAM
102+
li a0, 1
103+
ret
104+
"#
105+
);
106+
107+
#[entry]
108+
fn main(hartid: usize) -> ! {
109+
if hartid == 0 {
110+
uart_init();
111+
uart_print("Hart 0: Initializing\n");
112+
113+
// Send IPI to Hart 1 (write to CLINT msip register for hart 1)
114+
unsafe {
115+
(0x02000004usize as *mut u32).write_volatile(1);
116+
}
117+
118+
while !HART1_DONE.load(Ordering::Acquire) {
119+
core::hint::spin_loop();
120+
}
121+
122+
uart_print("Hart 0: Both harts done\n");
123+
debug::exit(EXIT_SUCCESS);
124+
} else {
125+
// Hart 1 reaches here after _mp_hook detects IPI
126+
uart_print("Hart 1: Running\n");
127+
HART1_DONE.store(true, Ordering::Release);
128+
}
129+
130+
loop {
131+
core::hint::spin_loop();
132+
}
133+
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
//!
33
//! This example uses RISC-V semihosting to print output and cleanly exit QEMU.
44
//! Run with: `qemu-system-riscv32 -machine virt -nographic -semihosting-config enable=on,target=native -bios none -kernel <binary>`
5-
65
#![no_std]
76
#![no_main]
87

tests-qemu/memory-multihart.x

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
MEMORY
2+
{
3+
RAM : ORIGIN = 0x80000000, LENGTH = 16M
4+
}
5+
REGION_ALIAS("REGION_TEXT", RAM);
6+
REGION_ALIAS("REGION_RODATA", RAM);
7+
REGION_ALIAS("REGION_DATA", RAM);
8+
REGION_ALIAS("REGION_BSS", RAM);
9+
REGION_ALIAS("REGION_HEAP", RAM);
10+
REGION_ALIAS("REGION_STACK", RAM);
11+
_max_hart_id = 1;
12+
INCLUDE link.x

0 commit comments

Comments
 (0)