Skip to content

Commit e446e29

Browse files
committed
Add stage-2 pagetables
This makes the RITM memory invisible from the guest.
1 parent 433692b commit e446e29

15 files changed

Lines changed: 539 additions & 39 deletions

File tree

.github/workflows/rust.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,26 @@ jobs:
3131
- uses: actions/checkout@v6
3232
- name: Format Rust code
3333
run: cargo fmt --all -- --check
34+
35+
test:
36+
runs-on: ubuntu-latest
37+
steps:
38+
- uses: actions/checkout@v6
39+
- name: Install aarch64 toolchain
40+
uses: dtolnay/rust-toolchain@v1
41+
with:
42+
toolchain: stable
43+
targets: aarch64-unknown-none
44+
components: llvm-tools
45+
- name: Install QEMU
46+
run: |
47+
sudo apt-get update && \
48+
sudo apt-get install -y --no-install-recommends qemu-system-arm ipxe-qemu
49+
- name: Install cargo-binutils
50+
uses: taiki-e/install-action@v2
51+
with:
52+
tool: cargo-binutils
53+
- name: Prepare QEMU
54+
run: sudo chown $(whoami):$(whoami) /dev/vhost-vsock && sudo chmod g+rw /dev/vhost-vsock
55+
- name: Run the tests
56+
run: make test

Cargo.lock

Lines changed: 22 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
[workspace]
2+
members = [
3+
".",
4+
"tests/isolation_test",
5+
]
6+
17
[package]
28
name = "ritm"
39
version = "0.1.0"
@@ -10,7 +16,7 @@ keywords = ["arm", "aarch64", "cortex-a", "osdev"]
1016
categories = ["embedded", "no-std"]
1117

1218
[dependencies]
13-
aarch64-paging = { version = "0.11", default-features = false }
19+
aarch64-paging = "0.11"
1420
aarch64-rt = { version = "0.4.3", features = ["el2", "exceptions", "initial-pagetable", "psci"], default-features = false }
1521
arm-pl011-uart = "0.4"
1622
arm-psci = "0.2"
@@ -24,6 +30,9 @@ safe-mmio = "0.2"
2430
smccc = "0.2"
2531
spin = { version = "0.10", features = ["lazy", "once", "spin_mutex"], default-features = false }
2632

33+
[patch.crates-io]
34+
aarch64-paging = { git = "https://github.com/m4tx/aarch64-paging.git", rev = "7c788ce47730e895edd6ff3ce7979c231b98dcc3" }
35+
2736
[lints.rust]
2837
deprecated-safe = "warn"
2938
keyword-idents = "warn"

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ PAYLOAD ?= payload.bin
1313
QEMU_BIN := target/ritm.qemu.bin
1414
QEMU_RUSTFLAGS := "--cfg platform=\"qemu\""
1515

16-
.PHONY: all build.qemu clean clippy qemu
16+
.PHONY: all build.qemu clean clippy qemu test
1717

1818
all: $(QEMU_BIN)
1919

@@ -37,6 +37,9 @@ qemu: $(QEMU_BIN)
3737
-device virtconsole,chardev=char0 \
3838
-device vhost-vsock-device,id=virtiosocket0,guest-cid=102
3939

40+
test:
41+
tests/isolation_test.py
42+
4043
clean:
4144
cargo clean
4245
rm -f target/*.bin

src/arch.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,31 @@
99
//! Architecture-specific code.
1010
1111
use arm_sysregs::{SctlrEl2, read_sctlr_el2};
12-
use core::arch::naked_asm;
12+
use core::arch::{asm, naked_asm};
13+
14+
/// Data Synchronization Barrier.
15+
pub fn dsb() {
16+
// SAFETY: Data Synchronization Barrier is always safe.
17+
unsafe {
18+
asm!("dsb sy", options(nostack, preserves_flags));
19+
}
20+
}
21+
22+
/// Instruction Synchronization Barrier.
23+
pub fn isb() {
24+
// SAFETY: Instruction Synchronization Barrier is always safe.
25+
unsafe {
26+
asm!("isb", options(nostack, preserves_flags));
27+
}
28+
}
29+
30+
/// TLBI VMALLS12E1 - VMID-based Stage-1/Stage-2 combined invalidation for the EL1&0 regime.
31+
pub fn tlbi_vmalls12e1() {
32+
// SAFETY: TLBI VMALLS12E1 is always safe.
33+
unsafe {
34+
asm!("tlbi vmalls12e1", options(nostack, preserves_flags));
35+
}
36+
}
1337

1438
/// Disables MMU and caches.
1539
///

src/hypervisor.rs

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,30 @@
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
88

9-
use core::arch::naked_asm;
10-
9+
use crate::{
10+
arch,
11+
platform::{Platform, PlatformImpl},
12+
simple_map::SimpleMap,
13+
};
14+
use aarch64_paging::descriptor::Stage2Attributes;
15+
use aarch64_paging::idmap::IdMap;
1116
use aarch64_rt::{RegisterStateRef, Stack};
1217
use arm_sysregs::{
13-
CnthctlEl2, CntvoffEl2, ElrEl2, HcrEl2, MpidrEl1, SpsrEl2, read_cnthctl_el2, read_esr_el2,
14-
read_far_el2, read_hcr_el2, read_mpidr_el1, read_spsr_el2, write_cnthctl_el2,
15-
write_cntvoff_el2, write_elr_el2, write_hcr_el2, write_spsr_el2,
18+
CnthctlEl2, CntvoffEl2, ElrEl1, ElrEl2, EsrEl1, FarEl1, HcrEl2, MpidrEl1, SpsrEl1, SpsrEl2,
19+
VtcrEl2, read_cnthctl_el2, read_esr_el2, read_far_el2, read_hcr_el2, read_mpidr_el1,
20+
read_spsr_el2, read_vbar_el1, write_cnthctl_el2, write_cntvoff_el2, write_elr_el1,
21+
write_elr_el2, write_esr_el1, write_far_el1, write_hcr_el2, write_spsr_el1, write_spsr_el2,
22+
write_vtcr_el2,
1623
};
24+
use core::arch::naked_asm;
1725
use log::debug;
26+
use spin::Once;
1827
use spin::mutex::SpinMutex;
1928

20-
use crate::{
21-
platform::{Platform, PlatformImpl},
22-
simple_map::SimpleMap,
23-
};
29+
static STAGE2_MAP: Once<SpinMutex<IdMap<Stage2Attributes>>> = Once::new();
2430

2531
const SPSR_EL1H: u8 = 5;
32+
const T0SZ_MAX_SIZE: u8 = 64;
2633

2734
/// Entry point for EL1 execution.
2835
///
@@ -35,10 +42,12 @@ const SPSR_EL1H: u8 = 5;
3542
/// address for EL1 execution that never returns.
3643
/// This function must be called in EL2.
3744
pub unsafe fn entry_point_el1(arg0: u64, arg1: u64, arg2: u64, arg3: u64, entry_point: u64) -> ! {
45+
setup_stage2();
3846
// Setup EL1
3947
let mut hcr = read_hcr_el2();
4048
hcr |= HcrEl2::RW;
4149
hcr |= HcrEl2::TSC;
50+
hcr |= HcrEl2::VM;
4251
hcr -= HcrEl2::IMO;
4352
// SAFETY: We are configuring HCR_EL2 to allow EL1 execution.
4453
unsafe {
@@ -83,6 +92,39 @@ pub unsafe fn entry_point_el1(arg0: u64, arg1: u64, arg2: u64, arg3: u64, entry_
8392
}
8493
}
8594

95+
fn setup_stage2() {
96+
debug!("Setting up stage 2 page table");
97+
let mut idmap = STAGE2_MAP
98+
.call_once(|| SpinMutex::new(PlatformImpl::make_stage2_pagetable()))
99+
.lock();
100+
101+
let root_pa = idmap.root_address().0;
102+
debug!("Root PA: {root_pa:#x}");
103+
104+
// Activate the page table
105+
// SAFETY: We are initializing the Stage 2 translation. The guest is not running yet.
106+
let ttbr = unsafe { idmap.activate() };
107+
debug!("idmap.activate() returned ttbr={ttbr:#x}");
108+
109+
let mut vtcr = VtcrEl2::default();
110+
vtcr.set_ps(2); // 40 bit physical address size
111+
vtcr.set_tg0(0); // 4kB granule size
112+
vtcr.set_sh0(3); // Inner shareable memory
113+
vtcr.set_orgn0(1); // Outer Write-Back Read-Allocate Write-Allocate Cacheable
114+
vtcr.set_irgn0(1); // Inner Write-Back Read-Allocate Write-Allocate Cacheable
115+
vtcr.set_sl0(2); // L0 starting level
116+
vtcr.set_t0sz(T0SZ_MAX_SIZE - 40); // 40 bit size offset
117+
debug!("Writing VTCR_EL2={vtcr:#x}...");
118+
// SAFETY: We are initializing the Stage 2 translation. The guest is not running yet.
119+
unsafe {
120+
write_vtcr_el2(vtcr);
121+
}
122+
arch::tlbi_vmalls12e1();
123+
arch::dsb();
124+
arch::isb();
125+
debug!("Stage 2 activation complete.");
126+
}
127+
86128
/// Returns to EL1.
87129
///
88130
/// This function executes the `eret` instruction to return to EL1 with the provided arguments.
@@ -179,15 +221,61 @@ pub fn handle_sync_lower(mut register_state: RegisterStateRef) {
179221
}
180222
}
181223
}
182-
ExceptionClass::Unknown(_) => {
224+
ExceptionClass::DataAbortLowerEL => {
225+
inject_data_abort(&mut register_state);
226+
}
227+
ExceptionClass::Unknown(val) => {
183228
panic!(
184-
"Unexpected sync_lower, esr={esr_el2:#x}, far={:#x}, register_state={register_state:?}",
229+
"Unexpected sync_lower, esr={esr_el2:#x}, ec={val:#x}, far={:#x}, register_state={register_state:?}",
185230
read_far_el2(),
186231
);
187232
}
188233
}
189234
}
190235

236+
fn inject_data_abort(register_state: &mut RegisterStateRef) {
237+
// SAFETY: We are modifying the saved register state to redirect execution.
238+
let regs = unsafe { register_state.get_mut() };
239+
let fault_addr = read_far_el2();
240+
let esr = read_esr_el2();
241+
242+
debug!("Injecting data abort to guest: fault_addr={fault_addr:#x}, esr={esr:#x}");
243+
244+
// Read guest VBAR
245+
let vbar = read_vbar_el1().bits();
246+
assert_ne!(
247+
vbar, 0,
248+
"Guest VBAR_EL1 is 0, cannot inject data abort. Fault addr: {fault_addr:#x}"
249+
);
250+
let handler = vbar + 0x200; // Current EL with SPx Sync
251+
252+
// Save current context to guest EL1 regs
253+
// SAFETY: We are accessing EL1 system registers to inject exception.
254+
unsafe {
255+
write_elr_el1(ElrEl1::from_bits_retain(regs.elr as u64));
256+
write_spsr_el1(SpsrEl1::from_bits_retain(regs.spsr));
257+
write_far_el1(FarEl1::from_bits_retain(fault_addr.va()));
258+
}
259+
write_esr_el1(EsrEl1::from_bits_retain(esr.bits()));
260+
261+
// Redirect execution
262+
#[expect(
263+
clippy::cast_possible_truncation,
264+
reason = "only 64-bit target is supported"
265+
)]
266+
{
267+
regs.elr = handler as usize;
268+
}
269+
// Mask all interrupts (DAIF) and set mode to EL1h
270+
let mut spsr = SpsrEl1::default();
271+
spsr.set_m_3_0(SPSR_EL1H);
272+
spsr |= SpsrEl1::D;
273+
spsr |= SpsrEl1::A;
274+
spsr |= SpsrEl1::I;
275+
spsr |= SpsrEl1::F;
276+
regs.spsr = spsr.bits();
277+
}
278+
191279
const AARCH64_INSTRUCTION_LENGTH: usize = 4;
192280

193281
fn try_handle_psci(register_state: &mut RegisterStateRef) -> Result<(), arm_psci::Error> {
@@ -369,6 +457,8 @@ enum ExceptionClass {
369457
HvcTrappedInAArch64,
370458
/// SMC instruction execution in `AArch64` state.
371459
SmcTrappedInAArch64,
460+
/// Data Abort taken without a change in Exception Level.
461+
DataAbortLowerEL,
372462
#[allow(unused)]
373463
/// Unknown exception class.
374464
Unknown(u8),
@@ -379,6 +469,7 @@ impl From<u8> for ExceptionClass {
379469
match value {
380470
0x16 => Self::HvcTrappedInAArch64,
381471
0x17 => Self::SmcTrappedInAArch64,
472+
0x24 => Self::DataAbortLowerEL,
382473
_ => Self::Unknown(value),
383474
}
384475
}

0 commit comments

Comments
 (0)