From 2cb607f53352efdeebff59866b06b220374cc147 Mon Sep 17 00:00:00 2001 From: Bokdeuk Jeong Date: Mon, 17 Nov 2025 02:17:30 -0500 Subject: [PATCH 1/7] Add wfi/wfe delivery code to/from the host Signed-off-by: Bokdeuk Jeong --- rmm/src/rmi/rec/handlers.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/rmm/src/rmi/rec/handlers.rs b/rmm/src/rmi/rec/handlers.rs index 0f25f1ffb908..cb6f8feb4162 100644 --- a/rmm/src/rmi/rec/handlers.rs +++ b/rmm/src/rmi/rec/handlers.rs @@ -233,9 +233,18 @@ pub fn set_event_handler(rmi: &mut RmiHandle) { crate::rsi::ripas::complete_ripas(&mut rec, &run)?; let wfx_flag = run.entry_flags(); - if wfx_flag.get_masked(EntryFlag::TRAP_WFI | EntryFlag::TRAP_WFE) != 0 { - warn!("Islet does not support re-configuring the WFI(E) trap"); - warn!("TWI(E) in HCR_EL2 is currently fixed to 'no trap'"); + #[cfg(not(any(miri, test, fuzzing)))] + { + let hcr_el2 = HCR_EL2.get(); + let mut wfx = 0; + let wfx_mask = !(3 << 13); + if wfx_flag.get_masked(EntryFlag::TRAP_WFI) != 0 { + wfx |= 1 << 13; + } + if wfx_flag.get_masked(EntryFlag::TRAP_WFE) != 0 { + wfx |= 1 << 14; + } + HCR_EL2.set((hcr_el2 & wfx_mask) | wfx); } #[cfg(not(any(miri, test, fuzzing)))] From 0b8117a2cf651b3eb9395a8fd1cf22513e0ebcb4 Mon Sep 17 00:00:00 2001 From: Bokdeuk Jeong Date: Mon, 17 Nov 2025 02:20:41 -0500 Subject: [PATCH 2/7] Add ns timer state save/restore To pass gic_timer_rel1_trig test suite in the acs test, add the timer state management for the non-sec world. Signed-off-by: Bokdeuk Jeong --- plat/Cargo.toml | 1 + rmm/Cargo.toml | 1 + rmm/src/rec/timer.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ scripts/cca_base.py | 1 + 4 files changed, 45 insertions(+) diff --git a/plat/Cargo.toml b/plat/Cargo.toml index 0be55a748c6e..a69bd66083a3 100644 --- a/plat/Cargo.toml +++ b/plat/Cargo.toml @@ -18,6 +18,7 @@ max_level_debug = ["log/max_level_debug", "islet_rmm/max_level_debug"] max_level_trace = ["log/max_level_trace", "islet_rmm/max_level_trace"] stat = ["islet_rmm/stat"] gst_page_table = ["islet_rmm/gst_page_table"] +ns_state_save = ["islet_rmm/ns_state_save"] fvp = ["islet_rmm/fvp"] qemu = ["islet_rmm/qemu"] diff --git a/rmm/Cargo.toml b/rmm/Cargo.toml index faafce2ea64e..914218906c50 100644 --- a/rmm/Cargo.toml +++ b/rmm/Cargo.toml @@ -40,6 +40,7 @@ stat = [] gst_page_table = [] fvp = [] qemu = [] +ns_state_save = [] # The below are features relevant for model checking mc_rmi_features = [] diff --git a/rmm/src/rec/timer.rs b/rmm/src/rec/timer.rs index 4650aabc7fa7..e6029c986327 100644 --- a/rmm/src/rec/timer.rs +++ b/rmm/src/rec/timer.rs @@ -4,6 +4,44 @@ use crate::rmi::rec::run::Run; use aarch64_cpu::registers::*; +#[cfg(feature = "ns_state_save")] +mod ns_timer { + use super::*; + use crate::config::NUM_OF_CPU; + use crate::cpu::get_cpu_id; + use crate::rec::context::TimerRegister; + use core::array::from_fn; + use lazy_static::lazy_static; + use spin::mutex::Mutex; + + lazy_static! { + static ref NS_TIMER: [Mutex; NUM_OF_CPU] = + from_fn(|_| Mutex::new(TimerRegister::default())); + } + + pub(super) fn restore() { + let ns_timer = NS_TIMER[get_cpu_id()].lock(); + CNTVOFF_EL2.set(ns_timer.cntvoff_el2); + CNTPOFF_EL2.set(ns_timer.cntpoff_el2); + CNTV_CVAL_EL0.set(ns_timer.cntv_cval_el0); + CNTV_CTL_EL0.set(ns_timer.cntv_ctl_el0); + CNTP_CVAL_EL0.set(ns_timer.cntp_cval_el0); + CNTP_CTL_EL0.set(ns_timer.cntp_ctl_el0); + CNTHCTL_EL2.set(ns_timer.cnthctl_el2); + } + + pub(super) fn save() { + let mut timer = NS_TIMER[get_cpu_id()].lock(); + timer.cntvoff_el2 = CNTVOFF_EL2.get(); + timer.cntv_cval_el0 = CNTV_CVAL_EL0.get(); + timer.cntv_ctl_el0 = CNTV_CTL_EL0.get(); + timer.cntpoff_el2 = CNTPOFF_EL2.get(); + timer.cntp_cval_el0 = CNTP_CVAL_EL0.get(); + timer.cntp_ctl_el0 = CNTP_CTL_EL0.get(); + timer.cnthctl_el2 = CNTHCTL_EL2.get(); + } +} + pub fn init_timer(rec: &mut Rec<'_>) { let timer = &mut rec.context.timer; timer.cnthctl_el2 = (CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET).into(); @@ -17,6 +55,8 @@ pub fn set_cnthctl(rec: &mut Rec<'_>, val: u64) { #[cfg(not(fuzzing))] pub fn restore_state(rec: &Rec<'_>) { let timer = &rec.context.timer; + #[cfg(feature = "ns_state_save")] + ns_timer::save(); CNTVOFF_EL2.set(timer.cntvoff_el2); CNTPOFF_EL2.set(timer.cntpoff_el2); @@ -38,6 +78,8 @@ pub fn save_state(rec: &mut Rec<'_>) { timer.cntp_cval_el0 = CNTP_CVAL_EL0.get(); timer.cntp_ctl_el0 = CNTP_CTL_EL0.get(); timer.cnthctl_el2 = CNTHCTL_EL2.get(); + #[cfg(feature = "ns_state_save")] + ns_timer::restore(); } pub fn send_state_to_host(rec: &Rec<'_>, run: &mut Run) -> Result<(), Error> { diff --git a/scripts/cca_base.py b/scripts/cca_base.py index 3e0d1f3bbd09..4f07220eb9e9 100755 --- a/scripts/cca_base.py +++ b/scripts/cca_base.py @@ -207,6 +207,7 @@ def get_rmm_features(self): features += ["--features", "stat"] if args.normal_world == "acs": features += ["--features", "gst_page_table"] + features += ["--features", "ns_state_save"] features += ["--features", self.platform_name] From 8473ca73e43fd3bbbac836bcfa1c8aed00a60b43 Mon Sep 17 00:00:00 2001 From: Bokdeuk Jeong Date: Mon, 17 Nov 2025 03:42:49 -0500 Subject: [PATCH 3/7] update skipped-tests Signed-off-by: Bokdeuk Jeong --- skipped-tests.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/skipped-tests.txt b/skipped-tests.txt index 20a5ff730c15..08d645a32537 100644 --- a/skipped-tests.txt +++ b/skipped-tests.txt @@ -1,7 +1,5 @@ cmd_multithread_realm_mp -exception_rec_exit_wfi exception_rec_exit_wfe attestation_rec_exit_irq -gic_timer_rel1_trig mm_gpf_exception cmd_secure_test From fa7bda21376d4e55618435fc76f5c090583800d0 Mon Sep 17 00:00:00 2001 From: Bokdeuk Jeong Date: Tue, 25 Nov 2025 21:24:48 -0500 Subject: [PATCH 4/7] plat: don't print bootup messages before enabling mmu Do not print messages until MMU is turned on, except on cpu0. Cores other than cpu0 at this point are still in its mmu off state with d-cache disabled and i-cache enabled. This may cause incosistencies between cpus with mmu enabled and cpus with mmu disabled. Logging involves buffer allocation and its global data struct for heap (linked_list) could be corrupted due to the reason above. Signed-off-by: Bokdeuk Jeong --- plat/src/main.rs | 23 +++++++++++++---------- rmm/src/lib.rs | 5 +++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/plat/src/main.rs b/plat/src/main.rs index cc1a1fc9c32e..2603c522e554 100644 --- a/plat/src/main.rs +++ b/plat/src/main.rs @@ -9,7 +9,6 @@ extern crate log; mod entry; mod plat; -use aarch64_cpu::registers::*; use islet_rmm::allocator; use islet_rmm::config::PlatformMemoryLayout; use islet_rmm::cpu; @@ -24,15 +23,19 @@ extern "C" { #[no_mangle] pub unsafe fn main(x0: u64, x1: u64, x2: u64, x3: u64) -> ! { let cpuid: usize = x0 as usize; - info!( - "booted on core {:2} with EL{}!", - cpuid, - CurrentEL.read(CurrentEL::EL) as u8 - ); - info!( - "boot args: x0:0x{:X}, x1:0x{:X}, x2:0x{:X}, x3:0x{:X}", - x0, x1, x2, x3 - ); + // Do not print here until MMU is turned on, except on cpu0. + // Cores other than cpu0 at this point are still in its mmu + // off state with d-cache disabled and i-cache enabled. + // This may cause incosistencies between cpus with mmu enabled + // and cpus with mmu disabled. + // Logging involves buffer allocation and its internal data struct + // for heap (linked_list) could be corrupted due to the reason above. + if x0 == 0 { + info!( + "boot args: x0:0x{:X}, x1:0x{:X}, x2:0x{:X}, x3:0x{:X}", + x0, x1, x2, x3 + ); + } if cpuid != cpu::get_cpu_id() { panic!( diff --git a/rmm/src/lib.rs b/rmm/src/lib.rs index 328544883b7d..2aa5098257ec 100755 --- a/rmm/src/lib.rs +++ b/rmm/src/lib.rs @@ -79,6 +79,11 @@ use core::ptr::addr_of; pub unsafe fn start(cpu_id: usize, layout: PlatformMemoryLayout) { let el3_shared_buf = layout.el3_shared_buf; setup_mmu_cfg(layout); + info!( + "booted on core {:2} with EL{}!", + cpu_id, + CurrentEL.read(CurrentEL::EL) as u8 + ); setup_el2(); #[cfg(feature = "gst_page_table")] setup_gst(); From f42b55fdec3126bf224c5eb7aa1584aef75232b5 Mon Sep 17 00:00:00 2001 From: Bokdeuk Jeong Date: Tue, 25 Nov 2025 21:37:31 -0500 Subject: [PATCH 5/7] acs: update acs test pass count Update acs test count but leave cmd_multithread_realm_mp test excluded. cmd_mutithread_realm_mp test passes now. However, an Inst abort took place at the end of the mp test within the host for unknown reason. Signed-off-by: Bokdeuk Jeong --- scripts/tests/acs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tests/acs.sh b/scripts/tests/acs.sh index d3af2a078222..b012ffc44324 100755 --- a/scripts/tests/acs.sh +++ b/scripts/tests/acs.sh @@ -3,7 +3,7 @@ set -e # Control these variables -EXPECTED=95 +EXPECTED=97 TIMEOUT=30 ROOT=$(git rev-parse --show-toplevel) From 22256c5b8f0588d525e46f05527a65a48a23a379 Mon Sep 17 00:00:00 2001 From: Bokdeuk Jeong Date: Thu, 27 Nov 2025 21:11:33 -0500 Subject: [PATCH 6/7] ns_state: put ns state related together Signed-off-by: Bokdeuk Jeong --- rmm/src/rec/mod.rs | 16 ++++++++++++++++ rmm/src/rec/timer.rs | 14 ++++++++++---- rmm/src/rmi/rec/handlers.rs | 4 ++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/rmm/src/rec/mod.rs b/rmm/src/rec/mod.rs index b8e7dde85afc..5cc505d0c6e9 100644 --- a/rmm/src/rec/mod.rs +++ b/rmm/src/rec/mod.rs @@ -391,3 +391,19 @@ pub fn run() -> Result<[usize; 4], Error> { pub fn max_recs_order() -> usize { MAX_RECS_ORDER_VALUE as usize } + +// Note: Islet intends to manage states only for the realm world. +// Handling the ns state here does not align Islet's desgin. +// Save the host state only if necessary. +pub fn save_host_state(rec: &Rec<'_>) { + pmu::save_host_state(rec); + // TODO: Apply 'ns_state_save' feature to the save_host_state func. + // For that, we need to move the code here from the corresponding patches + // in the nw-linux. + timer::save_host_state(rec); +} + +pub fn restore_host_state(rec: &Rec<'_>) { + pmu::restore_host_state(rec); + timer::restore_host_state(rec); +} diff --git a/rmm/src/rec/timer.rs b/rmm/src/rec/timer.rs index e6029c986327..0111c69b38a5 100644 --- a/rmm/src/rec/timer.rs +++ b/rmm/src/rec/timer.rs @@ -55,8 +55,6 @@ pub fn set_cnthctl(rec: &mut Rec<'_>, val: u64) { #[cfg(not(fuzzing))] pub fn restore_state(rec: &Rec<'_>) { let timer = &rec.context.timer; - #[cfg(feature = "ns_state_save")] - ns_timer::save(); CNTVOFF_EL2.set(timer.cntvoff_el2); CNTPOFF_EL2.set(timer.cntpoff_el2); @@ -78,8 +76,6 @@ pub fn save_state(rec: &mut Rec<'_>) { timer.cntp_cval_el0 = CNTP_CVAL_EL0.get(); timer.cntp_ctl_el0 = CNTP_CTL_EL0.get(); timer.cnthctl_el2 = CNTHCTL_EL2.get(); - #[cfg(feature = "ns_state_save")] - ns_timer::restore(); } pub fn send_state_to_host(rec: &Rec<'_>, run: &mut Run) -> Result<(), Error> { @@ -91,3 +87,13 @@ pub fn send_state_to_host(rec: &Rec<'_>, run: &mut Run) -> Result<(), Error> { run.set_cntp_cval(timer.cntp_cval_el0 - timer.cntpoff_el2); Ok(()) } + +pub fn save_host_state(_rec: &Rec<'_>) { + #[cfg(feature = "ns_state_save")] + ns_timer::save(); +} + +pub fn restore_host_state(_rec: &Rec<'_>) { + #[cfg(feature = "ns_state_save")] + ns_timer::restore(); +} diff --git a/rmm/src/rmi/rec/handlers.rs b/rmm/src/rmi/rec/handlers.rs index cb6f8feb4162..5d5a813ee618 100644 --- a/rmm/src/rmi/rec/handlers.rs +++ b/rmm/src/rmi/rec/handlers.rs @@ -250,7 +250,7 @@ pub fn set_event_handler(rmi: &mut RmiHandle) { #[cfg(not(any(miri, test, fuzzing)))] activate_stage2_mmu(&rec); - crate::rec::pmu::save_host_state(&rec); + crate::rec::save_host_state(&rec); let mut ret_ns; loop { ret_ns = true; @@ -314,7 +314,7 @@ pub fn set_event_handler(rmi: &mut RmiHandle) { crate::rec::gic::send_state_to_host(&rec, &mut run)?; crate::rec::timer::send_state_to_host(&rec, &mut run)?; crate::rec::pmu::send_state_to_host(&rec, &mut run)?; - crate::rec::pmu::restore_host_state(&rec); + crate::rec::restore_host_state(&rec); // NOTICE: do not modify `run` after copy_to_ptr(). it won't have any effect. rmm.page_table.map(run_pa, false); From ceda1a9d80453946ed5f61742cae4c7314ff6cf7 Mon Sep 17 00:00:00 2001 From: Bokdeuk Jeong Date: Thu, 27 Nov 2025 23:38:44 -0500 Subject: [PATCH 7/7] timer: implement spec in A6.2 section I: On REC entry, for both the EL1 Virtual Timer and the EL1 Physical Timer, if the EL1 timer asserts its output in the state described in the REC exit structure from the previous REC exit then the RMM masks the hardware timer signal before returning to the Realm. Signed-off-by: Bokdeuk Jeong --- rmm/src/asm.rs | 11 +++++++++++ rmm/src/rec/mod.rs | 1 + rmm/src/rec/timer.rs | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/rmm/src/asm.rs b/rmm/src/asm.rs index 394841f5d2b7..40c0887ea8da 100644 --- a/rmm/src/asm.rs +++ b/rmm/src/asm.rs @@ -81,3 +81,14 @@ pub fn dcache_flush(addr: usize, len: usize) { } } } + +#[inline(always)] +pub fn isb() { + #[cfg(target_arch = "aarch64")] + unsafe { + core::arch::asm!("isb", options(nomem, nostack)) + } + + #[cfg(not(target_arch = "aarch64"))] + unimplemented!() +} diff --git a/rmm/src/rec/mod.rs b/rmm/src/rec/mod.rs index 5cc505d0c6e9..52bab532625f 100644 --- a/rmm/src/rec/mod.rs +++ b/rmm/src/rec/mod.rs @@ -374,6 +374,7 @@ pub fn run_prepare(rd: &Rd, vcpu: usize, rec: &mut Rec<'_>, incr_pc: usize) -> R if incr_pc == 1 { rec.context.elr_el2 += 4; } + timer::update_timer_assertion(rec); debug!("resuming: {:#x}", rec.context.elr_el2); rec.into_current(); diff --git a/rmm/src/rec/timer.rs b/rmm/src/rec/timer.rs index 0111c69b38a5..e9fc75d448d1 100644 --- a/rmm/src/rec/timer.rs +++ b/rmm/src/rec/timer.rs @@ -1,4 +1,5 @@ use super::Rec; +use crate::asm::isb; use crate::rmi::error::Error; use crate::rmi::rec::run::Run; @@ -97,3 +98,41 @@ pub fn restore_host_state(_rec: &Rec<'_>) { #[cfg(feature = "ns_state_save")] ns_timer::restore(); } + +// RMM spec A6.2 Realm timers, I +// On REC entry, for both the EL1 Virtual Timer and the EL1 Physical Timer, +// if the EL1 timer asserts its output in the state described in the REC exit +// structure from the previous REC exit then the RMM masks the hardware timer +// signal before returning to the Realm. +pub fn update_timer_assertion(rec: &mut Rec<'_>) { + const CNTHCTL_EL2_CNTVMASK: u64 = 0x1 << 18; + const CNTHCTL_EL2_CNTPMASK: u64 = 0x1 << 19; + let timer = &mut rec.context.timer; + + // Get recently saved timer control registers + let cnthctl_old = timer.cnthctl_el2; + + // Check if virtual timer is asserted + if CNTV_CTL_EL0.matches_all( + CNTV_CTL_EL0::ISTATUS::SET + CNTV_CTL_EL0::IMASK::CLEAR + CNTV_CTL_EL0::ENABLE::SET, + ) { + timer.cnthctl_el2 |= CNTHCTL_EL2_CNTVMASK; + } else { + timer.cnthctl_el2 &= !CNTHCTL_EL2_CNTVMASK; // Clear MASK + } + + // Check if physical timer is asserted + if CNTP_CTL_EL0.matches_all( + CNTP_CTL_EL0::ISTATUS::SET + CNTP_CTL_EL0::IMASK::CLEAR + CNTP_CTL_EL0::ENABLE::SET, + ) { + timer.cnthctl_el2 |= CNTHCTL_EL2_CNTPMASK; + } else { + timer.cnthctl_el2 &= !CNTHCTL_EL2_CNTPMASK; // Clear MASK + } + + // If cnthctl changed, write it back and ensure synchronization + if cnthctl_old != timer.cnthctl_el2 { + CNTHCTL_EL2.set(timer.cnthctl_el2); + isb(); + } +}