Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clint interrupts virtualization #199

Closed
wants to merge 4 commits into from
Closed
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
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ members = [
"src",

# Firmware
"firmware/clint_interrupt",
"firmware/clint_interrupt_multihart",
"firmware/clint_interrupt_priority",
"firmware/csr_ops",
"firmware/default",
"firmware/ecall",
Expand Down
13 changes: 11 additions & 2 deletions crates/abi/src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! This is a logger implementation that uses the Miralis SBI to log messages.

use core::fmt::Write;
use core::sync::atomic::{AtomicBool, Ordering};

use log::{LevelFilter, Metadata, Record};

Expand Down Expand Up @@ -38,10 +39,18 @@ impl log::Log for Logger {
///
/// This function is called automatically by `setup_binary!`.
pub fn init() {
static IS_INITIALIZED: AtomicBool = AtomicBool::new(false);
static LOGGER: Logger = Logger {};

log::set_logger(&LOGGER).unwrap();
log::set_max_level(LevelFilter::Trace);
match IS_INITIALIZED.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) {
Ok(_) => {
log::set_logger(&LOGGER).unwrap();
log::set_max_level(LevelFilter::Trace);
}
Err(_) => {
log::warn!("Logger is already initialized, skipping init");
}
};
}

// —————————————————————————————— Stack Buffer —————————————————————————————— //
Expand Down
12 changes: 12 additions & 0 deletions firmware/clint_interrupt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "clint_interrupt"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "clint_interrupt"
path = "main.rs"

[dependencies]
miralis_abi = { path = "../../crates/abi" }
log = { workspace = true }
146 changes: 146 additions & 0 deletions firmware/clint_interrupt/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#![no_std]
#![no_main]

use core::arch::{asm, global_asm};
use core::usize;

use miralis_abi::{failure, setup_binary, success};

setup_binary!(main);

/// This test verifies the general functionality of MTI (Machine Timer Interrupt) virtualization, including:
/// 1. The timer interrupt is delivered within a reasonable time when the timer is set and interrupts are enabled.
/// 2. Firmware cannot directly clear the M-mode timer interrupt (MTI) by writing to `vMIP.MTIP`.
/// 3. Writing to the appropriate memory-mapped register correctly clears the interrupt.
/// 4. If the virtual interrupt is not cleared, the firmware will trap on each execution cycle.
fn main() -> ! {
// Setup a timer deadline
set_mtimecmp_future_value(0);

// Configure trap handler and enable interrupts
unsafe {
asm!(
"csrw mtvec, {handler}", // Setup trap handler
"csrs mie, {mtie}", // Enable machine timer interrupt (MTIE)
handler = in(reg) _raw_interrupt_trap_handler as usize,
mtie = in(reg) 0x80,
);
}

for _ in 1..REPETITIONS {
unsafe {
asm!(
"csrs mstatus, {mstatus_mie}", // Enable global interrupts (MIE), expect to trap immediately
mstatus_mie = in(reg) 0x8,
)
};
}
unsafe {
asm!("ebreak");
}
failure()
}

// ———————————————————————————— Timer Interrupt ————————————————————————————— //

const CLINT_BASE: usize = 0x2000000;
const MTIME_OFFSET: usize = 0xBFF8;
const MTIMECMP_OFFSET: usize = 0x4000;
const REPETITIONS: usize = 10;

static mut INTERRUPT_COUNTER: usize = 0;

// Get the current mtime value
fn get_current_mtime() -> usize {
let mtime_ptr = (CLINT_BASE + MTIME_OFFSET) as *const usize;
unsafe { mtime_ptr.read_volatile() }
}

// Set mtimecmp value in the future
fn set_mtimecmp_future_value(value: usize) {
let current_mtime = get_current_mtime();
let future_time = current_mtime.saturating_add(value);

let mtimecmp_ptr = (CLINT_BASE + MTIMECMP_OFFSET) as *mut usize;
unsafe {
mtimecmp_ptr.write_volatile(future_time);
}
}

/// This function should be called from the raw trap handler
extern "C" fn trap_handler() {
let mip: usize;
let mcause: usize;
let mepc: usize;

unsafe {
asm!(
"csrr {0}, mip",
"csrr {1}, mcause",
"csrr {2}, mepc",
out(reg) mip,
out(reg) mcause,
out(reg) mepc,
);
}

if mcause == 0x8000000000000007 {
log::trace!("Trapped on interrupt, mip {:b}, at {:x}", mip, mepc);

if unsafe { INTERRUPT_COUNTER } > REPETITIONS {
failure()
}; // Shouldn't trap if the interrupt pending bit was cleared

log::trace!("Counter {:?}", unsafe { INTERRUPT_COUNTER });

// Do not actually clear the interrupt first time: expect to trap again
if unsafe { INTERRUPT_COUNTER } == REPETITIONS {
set_mtimecmp_future_value(usize::MAX);
log::trace!("Now timer should clear");
unsafe {
asm!("mret",);
}
};

unsafe {
INTERRUPT_COUNTER += 1;
// Try clearing mtip directly - shouldn't work
asm!(
"csrc mip, {mip_mtie}",
"mret",
mip_mtie = in(reg) 0x80,
);
}
} else if mcause == 3 {
if unsafe { INTERRUPT_COUNTER } == REPETITIONS {
success()
} else {
log::warn!(
"Expected to get interrupt {} times, got {}",
REPETITIONS,
unsafe { INTERRUPT_COUNTER }
);
failure()
}
} else {
log::debug!("Not a timer exception! {}", mcause);
failure();
}
}

// —————————————————————————————— Trap Handler —————————————————————————————— //

global_asm!(
r#"
.text
.align 4
.global _raw_interrupt_trap_handler
_raw_interrupt_trap_handler:
j {trap_handler} // Jump immediately into the Rust trap handler
"#,
trap_handler = sym trap_handler,
);

extern "C" {
fn _raw_interrupt_trap_handler();
}
12 changes: 12 additions & 0 deletions firmware/clint_interrupt_multihart/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "clint_interrupt_multihart"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "clint_interrupt_multihart"
path = "main.rs"

[dependencies]
miralis_abi = { path = "../../crates/abi" }
log = { workspace = true }
107 changes: 107 additions & 0 deletions firmware/clint_interrupt_multihart/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#![no_std]
#![no_main]

use core::arch::{asm, global_asm};
use core::usize;

use miralis_abi::{failure, setup_binary, success};

setup_binary!(main);

/// This test verifies the functionality of virtualized Machine Software Interrupt (MSI) between two harts.
/// It ensures that one hart can trigger an interrupt on another hart by writing to the target hart's
/// memory-mapped interrupt register.
///
/// Specifically, the test checks:
/// 1. Hart A can successfully write to Hart B's MSI memory-mapped register.
/// 2. Hart B correctly receives and handles the MSI triggered by Hart A's write.
///
/// This test ensures proper inter-hart communication through software interrupts, confirming that
/// MSIs can be triggered and handled correctly in a multi-hart environment.
fn main() -> ! {
let hart_id: usize;
unsafe {
asm!(
"csrr {0}, mhartid",
"csrw mtvec, {handler}",
out(reg) hart_id,
handler = in(reg) _raw_interrupt_trap_handler as usize,
);
}

assert!(hart_id < 2, "Expected only 2 harts for this test");

match hart_id {
0 => {
unsafe {
asm!(
"csrs mstatus, {mstatus_mie}", // Enable interrupts (MIE)
"csrs mie, {msie}", // Enable software timer interrupt (MSIE)
"wfi", // Wait for other hart to send an interrupt
mstatus_mie = in(reg) 0x8,
msie = in(reg) 0x8,
);
}
success()
}
1 => {
set_msip(0, 1);
loop {
unsafe { asm!("nop") };
}
}
_ => failure(),
}
}

// ———————————————————————————— Timer Interrupt ————————————————————————————— //

const CLINT_BASE: usize = 0x2000000;
const MSIP_WIDTH: usize = 0x4;

// Set msip bit pending for other hart
fn set_msip(hart: usize, value: u32) {
log::debug!("Set interrupt for hart {:}", hart);

let msip_ptr = (CLINT_BASE + MSIP_WIDTH * hart) as *mut u32;
unsafe {
msip_ptr.write_volatile(value);
}
}

/// This function should be called from the raw trap handler
extern "C" fn trap_handler() {
let mcause: usize;
unsafe {
asm!(
"csrr {0}, mcause",
out(reg) mcause,
);
}

if mcause == 0x8000000000000003 {
set_msip(0, 0);
unsafe {
asm!("mret",);
}
} else {
failure();
}
}

// —————————————————————————————— Trap Handler —————————————————————————————— //

global_asm!(
r#"
.text
.align 4
.global _raw_interrupt_trap_handler
_raw_interrupt_trap_handler:
j {trap_handler} // Jump immediately into the Rust trap handler
"#,
trap_handler = sym trap_handler,
);

extern "C" {
fn _raw_interrupt_trap_handler();
}
12 changes: 12 additions & 0 deletions firmware/clint_interrupt_priority/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "clint_interrupt_priority"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "clint_interrupt_priority"
path = "main.rs"

[dependencies]
miralis_abi = { path = "../../crates/abi" }
log = { workspace = true }
Loading
Loading