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

Support for full interrupts virtualization #257

Merged
merged 6 commits into from
Oct 30, 2024
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
35 changes: 34 additions & 1 deletion Cargo.lock

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

5 changes: 5 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 All @@ -28,11 +31,13 @@ members = [
# Payload
"payload/hello_world",
"payload/test_protect_payload_payload",

# Crates
"crates/abi",
"crates/core",
"crates/config_helpers",
"crates/config_select",
"crates/test_helpers",

# Tooling
"runner",
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
6 changes: 6 additions & 0 deletions crates/test_helpers/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "test_helpers"
version = "0.1.0"
edition = "2021"

[dependencies]
42 changes: 42 additions & 0 deletions crates/test_helpers/src/clint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Clint utilities
//!
//! The functions exposed in this modules assumes the CLINT is located at the same addresses as on
//! the QEMU virt platform, which we use for out tests.

/// Clint base, assuming a QEMU virt-like layout.
const CLINT_BASE: usize = 0x2000000;
const MTIME_OFFSET: usize = 0xBFF8;
const MTIMECMP_OFFSET: usize = 0x4000;

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

/// Set mtimecmp deadline in the future
pub fn set_mtimecmp_deadline(delta: usize, hart: usize) {
let current_mtime = read_mtime();
let future_time = current_mtime.saturating_add(delta);

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

/// Send an MSI to the given hart.
pub fn send_msi(hart: usize) {
let msip_ptr = (CLINT_BASE + 4 * hart) as *mut u32;
unsafe {
msip_ptr.write_volatile(1);
}
}

/// Clear MSI for the given hart.
pub fn clear_msi(hart: usize) {
let msip_ptr = (CLINT_BASE + 4 * hart) as *mut u32;
unsafe {
msip_ptr.write_volatile(0);
}
}
8 changes: 8 additions & 0 deletions crates/test_helpers/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Test helpers
//!
//! Utility functions, macros, and contants for the Miralis integration tests (firmware and
//! payloads).

#![no_std]

pub mod clint;
63 changes: 35 additions & 28 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,15 @@ To sum up, in riscv, in order to trap to M-Mode, we need:

In order to properly virtualize interrupts and correctly handle them, we need to follow many rules. The goal is to ensure that all interrupts destined to the firmware are correctly virtualized, so that the firmware get exactly its destined interrupts.

Virtualizing interrupts requires virtualizing the interrupt source.
Miralis is designed to be minimal, and therefore explicitely avoid virtualizing devices such as disks and network cards, which are usually managed by the OS rather than the firmware.
The current implementation of Miralis focuses on virtualizing the M-mode interrupts only: M-mode timer, software, and external interrupts.
For that purpose Miralis forces the delegation of all other interrupts to the payload.

We virtualize registers inside the virtual context of the firmware. Registers `mie`, `mip`, `mideleg`, `mstatus` will have their virtual counterparts `vmie`, `vmie`, `vmideleg` and `vmstatus`. In this section, we will also say that the firmware is running in **vM-mode** (M-mode virtualized by Miralis inside U-mode). Let's now separate the cases into the three execution states that could occur:

#### Firmware

When the firmware is running, we want Miralis to receive **all** interrupts the firmware expects to receive: no interrupt is delegated to firmware. We then set `mideleg` to 0 when executing the firmware. We also want to receive **only** interrupts the firmware expects to receive. We then must filter `mie` register to not trap on delegated interrupts or disabled interrupts. If an interrupt occurs, we need to reflect `mip` into `vmip` to let the firmware know which one.

> **(MIDELEG-VM-MODE)**
Expand All @@ -138,6 +143,7 @@ When the firmware is running, we want Miralis to receive **all** interrupts the
> vmip = mip, if mode = vM

#### Payload

When switching to S-mode, we want to install `vmideleg` into `mideleg` and `vmie` to `mie`, because the states of `mideleg` and `mie` may influence S-mode interrupts handling.

> **(MIDELEG-S-MODE)**
Expand All @@ -147,6 +153,7 @@ When switching to S-mode, we want to install `vmideleg` into `mideleg` and `vmie
> mie ≡ vmie, if mode = S

#### Miralis

When Miralis is running, we don't want to receive interrupts: we want to handle them one by one. A simple way to ensure that is to make sure that `mstatus.MIE` is always 0. As interrupts are globally enabled for M-mode when a less-privileged mode is running, Miralis will still get the interrupts of S-mode (e.g. payload) and U-mode (e.g. firmware).

> **(MSTATUS-MIE)**
Expand All @@ -158,57 +165,57 @@ Now we show that if Miralis get an interrupt from firmware or the payload, it's
When running in **vM-mode**, we have the following properties:

```
┌──────────┐
┌──>│ Firmware │───┐ (1) An interrupt occurs when the firmware is
│ └──────────┘ | running. Switch to Miralis.
(2)│ |(1) (2) Miralis virtualizes interrupt and
│ | transmit handling to firmware's interrupt
│ ┌──────────┐ | handler.
┌──────────┐
┌──>│ Firmware │───┐ (1) An interrupt occurs when the firmware is
│ └──────────┘ | running. Switch to Miralis.
(2)│ |(1) (2) Miralis virtualizes interrupt and
│ | transmit handling to firmware's interrupt
│ ┌──────────┐ | handler.
└───│ Miralis │<──┘
└──────────┘
Miralis receives interrupt i when executing firmware:
└──────────┘

Miralis receives interrupt i when executing firmware:
(RISCV-SPEC)
⟹ mstatus.MIE = -, mie[i] = 1, mip[i] = 1, mideleg[i] = 0
⟹ mstatus.MIE = -, mie[i] = 1, mip[i] = 1, mideleg[i] = 0

(MIE-VM-MODE)
⟹ vmstatus.MIE = 1, vmie[i] = 1, vmideleg[i] = 0

(MIP-VM-MODE)
⟹ vmip[i] = mip [i] = 1
Then, vmstatus.MIE = 1 ∧ vmip[i] = 1 ∧ vmie[i] = 1 ∧ vmideleg[i] = 0

Then, vmstatus.MIE = 1 ∧ vmip[i] = 1 ∧ vmie[i] = 1 ∧ vmideleg[i] = 0
(RISCV-SPEC)
⟹ virtual context is set as a tap occured in the firmware,
we can forward interrupt handling to firmware.
we can forward interrupt handling to firmware.
```

When running in **S-mode**, we have the following properties:

```
┌─────────┐ ┌─────────┐ (1) An interrupt occurs when the payload is
│ Payload │<──┐ ┌──>│Firmware │ running. Switch to Miralis.
└─────────┘ │ │ └─────────┘ (2) Miralis virtualizes interrupt and transmit
┌─────────┐ ┌─────────┐ (1) An interrupt occurs when the payload is
│ Payload │<──┐ ┌──>│Firmware │ running. Switch to Miralis if not delegated.
└─────────┘ │ │ └─────────┘ (2) Miralis virtualizes interrupt and transmit
│ (4)│ │(2) │ handling to firmware's interrupt handler.
(1)│ │ │ │(3) (3) Firmware's handler handle interrupt then
│ ┌─────────┐ │ mret to payload.
(1)│ │ │ │(3) (3) Firmware's handler handle interrupt then
│ ┌─────────┐ │ mret to payload.
└───>│ Miralis │<───┘ (4) Miralis installs registers and emulate
└─────────┘ mret to payload.
└─────────┘ mret to payload.

Miralis receives interrupt i when executing payload:
(RISCV-SPEC)
⟹ mstatus.MIE = -, mie[i] = 1, mip[i] = 1, mideleg[i] = 0
Miralis receives interrupt i when executing payload:
(RISCV-SPEC)
⟹ mstatus.MIE = -, mie[i] = 1, mip[i] = 1, mideleg[i] = 0

(MIE-S-MODE, MIDELEG-S-MODE)
⟹ vmie[i] = 1, vmideleg[i] = 0
⟹ vmie[i] = 1, vmideleg[i] = 0

(MIP-VM-MODE)
⟹ vmip[i] = mip [i] = 1
Then, vmstatus.MIE = - ∧ vmip[i] = 1 ∧ vmie[i] = 1 ∧ vmideleg[i] = 0

Then, vmstatus.MIE = - ∧ vmip[i] = 1 ∧ vmie[i] = 1 ∧ vmideleg[i] = 0
(RISCV-SPEC)
⟹ virtual context is set as a tap occured to the firmware,
we can forward interrupt handling to firmware.
we can forward interrupt handling to firmware.
```

### Software external interrupt virtualization
Expand Down
13 changes: 13 additions & 0 deletions firmware/clint_interrupt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "clint_interrupt"
version = "0.1.0"
edition = "2021"

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

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