Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
QEMU 10.1.2 monitor - type 'help' for more information
66 changes: 66 additions & 0 deletions .github/workflows/qemu.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: QEMU tests
on:
merge_group:
pull_request:
push:
branches:
- master

env:
CARGO_TERM_COLOR: always

jobs:
# Verify the example output with run-pass tests
testexamples:
name: QEMU run
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
input:
- toolchain: stable
target: riscv32imac-unknown-none-elf
qemu-system: riscv32
example: hello

- toolchain: nightly
target: riscv32imac-unknown-none-elf
qemu-system: riscv32
example: hello

- toolchain: stable
target: riscv64gc-unknown-none-elf
qemu-system: riscv64
example: hello

- toolchain: nightly
target: riscv64gc-unknown-none-elf
qemu-system: riscv64
example: hello

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure Rust target ${{ matrix.input.target }}
run: |
rustup toolchain install ${{ matrix.input.toolchain }}
rustup default ${{ matrix.input.toolchain }}
rustup target add ${{ matrix.input.target }}

- name: Cache Dependencies
uses: Swatinem/rust-cache@v2

- name: Install QEMU
run: |
sudo apt update
sudo apt install -y qemu-system-${{ matrix.input.qemu-system }}

- name: Check which QEMU is used
run: |
which qemu-system-${{ matrix.input.qemu-system }}

- name: Run-pass tests
run: cargo run --package xtask -- qemu --target ${{ matrix.input.target }} --example ${{ matrix.input.example }}


1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"riscv-types",
"tests-build",
"tests-trybuild",
"xtask",
]

default-members = [
Expand Down
Empty file.
Empty file.
1 change: 1 addition & 0 deletions riscv-rt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ defmt = { version = "1.0.1", optional = true }

[dev-dependencies]
panic-halt = "1.0.0"
riscv-semihosting = { path = "../riscv-semihosting", version = "0.2.1" }

[features]
pre-init = []
Expand Down
14 changes: 14 additions & 0 deletions riscv-rt/examples/device_s_mode.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
MEMORY
{
RAM : ORIGIN = 0x80200000, LENGTH = 16K
FLASH : ORIGIN = 0x20000000, LENGTH = 4M
}

REGION_ALIAS("REGION_TEXT", FLASH);
REGION_ALIAS("REGION_RODATA", FLASH);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

INCLUDE link.x
11 changes: 11 additions & 0 deletions riscv-rt/examples/device_virt_m.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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);
INCLUDE link.x
11 changes: 11 additions & 0 deletions riscv-rt/examples/device_virt_s.x
Original file line number Diff line number Diff line change
@@ -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
45 changes: 45 additions & 0 deletions riscv-rt/examples/hello.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#![no_std]
#![no_main]
extern crate panic_halt;
use riscv_rt::entry;
const UART_BASE: usize = 0x1000_0000;
const UART_THR: usize = UART_BASE + 0;
const UART_IER: usize = UART_BASE + 1;
const UART_FCR: usize = UART_BASE + 2;
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;
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_IER, 0x00);
uart_write_reg(UART_LCR, LCR_8N1);
uart_write_reg(UART_FCR, 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_write_str(s: &str) {
for &b in s.as_bytes() {
uart_write_byte(b);
}
}
#[entry]
fn main() -> ! {
uart_init();
uart_write_str("HELLO_QEMU\n");
loop {}
}
7 changes: 7 additions & 0 deletions xtask/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "xtask"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1"
139 changes: 139 additions & 0 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use anyhow::{bail, Context};
use std::{
fs,
path::PathBuf,
process::{Command, Stdio},
thread,
time::Duration,
};

fn main() -> anyhow::Result<()> {
let mut args = std::env::args().skip(1).collect::<Vec<_>>();
if args.is_empty() || args[0] != "qemu" {
bail!("usage: cargo run -p xtask -- qemu --target <triple> --example <name>");
}
args.remove(0);
let mut target = None;
let mut example = None;
let mut features: Option<String> = None;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--target" => {
target = Some(args.get(i + 1).context("missing target")?.clone());
i += 2;
}
"--example" => {
example = Some(args.get(i + 1).context("missing example")?.clone());
i += 2;
}
"--features" => {
features = Some(args.get(i + 1).context("missing features")?.clone());
i += 2;
}
_ => {
bail!("unknown arg {}", args[i]);
}
}
}
let target = target.context("--target required")?;
let example = example.context("--example required")?;
let mut rustflags = "-C link-arg=-Triscv-rt/examples/device_virt_m.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 mut cmd = Command::new("cargo");
cmd.env("RUSTFLAGS", rustflags).args([
"build",
"--package",
"riscv-rt",
"--release",
"--target",
&target,
"--example",
&example,
]);
cmd.apply_features(features.as_deref());
let status = cmd.status()?;
if !status.success() {
bail!("build failed");
}

let qemu = if target.starts_with("riscv32") {
"qemu-system-riscv32"
} else {
"qemu-system-riscv64"
};
let mut qemu_args = vec![
"-machine",
"virt",
"-nographic",
"-serial",
"stdio",
"-monitor",
"none",
];
if !features.as_deref().unwrap_or("").contains("s-mode") {
qemu_args.push("-bios");
qemu_args.push("none");
}
let kernel_path = format!("target/{}/release/examples/{}", target, example);
let mut child = Command::new(qemu)
.args(&qemu_args)
.arg("-kernel")
.arg(&kernel_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.context("running qemu")?;
thread::sleep(Duration::from_secs(2));
let _ = child.kill();
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.contains("QEMU") && !l.contains("monitor"))
.collect::<Vec<_>>()
.join("\n");
let stdout = if stdout.is_empty() {
String::new()
} else {
format!("{}\n", stdout.trim())
};

let expected_path: PathBuf = ["ci", "expected", &target, &format!("{}.run", example)]
.iter()
.collect();
if !expected_path.exists() {
fs::create_dir_all(expected_path.parent().unwrap())?;
fs::write(&expected_path, stdout.as_bytes())?;
bail!("expected output created; re-run CI");
}
let expected = fs::read_to_string(&expected_path)?;
if expected != stdout {
bail!(
"output mismatch\nexpected: {}\nactual: {}",
expected,
stdout
);
}
if !stdout.is_empty() {
println!("{}", stdout.trim_end());
}
Ok(())
}

trait CmdExt {
fn apply_features(&mut self, f: Option<&str>) -> &mut Self;
}
impl CmdExt for std::process::Command {
fn apply_features(&mut self, f: Option<&str>) -> &mut Self {
if let Some(feat) = f {
self.arg("--features").arg(feat);
}
self
}
}
Loading