From 54abf1a9640710fbac8112470b69db3880fbf897 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Tue, 12 May 2026 14:58:02 +0100 Subject: [PATCH 1/4] Stop ElXVirtualTimer and ElXPhysicalTimer from implementing Send. These are core-local types and we need to try and stop people moving them into threads running on other cores, at the expense of stopping people moving them into threads running on the same core. If this causes you an issue, you can just unsafely conjure up the values on demand, or use raw CP15 sycalls from the aarch32_cpu::register module. --- aarch32-cpu/src/generic_timer/el0.rs | 35 +++++++++++++++++++++------- aarch32-cpu/src/generic_timer/el1.rs | 22 +++++++++++++---- aarch32-cpu/src/generic_timer/el2.rs | 24 +++++++++++++++---- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/aarch32-cpu/src/generic_timer/el0.rs b/aarch32-cpu/src/generic_timer/el0.rs index 90dd61f3..192c01b5 100644 --- a/aarch32-cpu/src/generic_timer/el0.rs +++ b/aarch32-cpu/src/generic_timer/el0.rs @@ -1,5 +1,7 @@ //! Code and types for Generic Timer support at EL0 on Armv8-R. +use core::marker::PhantomData; + use crate::register; /// Represents our Generic Physical Timer when we are running at EL0. @@ -7,7 +9,12 @@ use crate::register; /// Note that for most of these APIs to work, EL0 needs to have been granted /// access using methods like /// [El1PhysicalTimer::el0_access_physical_counter](crate::generic_timer::El1PhysicalTimer::el0_access_physical_counter). -pub struct El0PhysicalTimer(); +/// +/// This type is not [Send] because it is a per-core type and should not be moved across +/// cores on an SMP system. +pub struct El0PhysicalTimer { + _phantom: PhantomData<*const u8>, +} impl El0PhysicalTimer { /// Create an EL0 Timer handle for the Physical Timer. @@ -17,11 +24,13 @@ impl El0PhysicalTimer { /// /// # Safety /// - /// Only create one of these at any given time, as they access shared - /// mutable state within the processor and do read-modify-writes on that - /// state. + /// Only create one Physical Timer handle (at any EL) at any given time, as + /// they access shared mutable state within the processor and do + /// read-modify-writes on that state. pub unsafe fn new() -> El0PhysicalTimer { - El0PhysicalTimer() + El0PhysicalTimer { + _phantom: PhantomData, + } } } @@ -80,17 +89,25 @@ impl super::GenericTimer for El0PhysicalTimer { /// Note that for most of these APIs to work, EL0 needs to have been granted /// access using methods like /// [El1VirtualTimer::el0_access_virtual_counter](crate::generic_timer::El1VirtualTimer::el0_access_virtual_counter). -pub struct El0VirtualTimer(); +/// +/// This type is not [Send] because it is a per-core type and should not be moved across +/// cores on an SMP system. +pub struct El0VirtualTimer { + _phantom: PhantomData<*const u8>, +} impl El0VirtualTimer { /// Create an EL0 Timer handle for the Virtual Timer. /// /// # Safety /// - /// Only create one of these at any given time, as they access shared - /// mutable state within the processor and do read-modify-writes on that state. + /// Only create one Virtual Timer handle (at any EL) at any given time, as + /// they access shared mutable state within the processor and do + /// read-modify-writes on that state. pub unsafe fn new() -> El0VirtualTimer { - El0VirtualTimer() + El0VirtualTimer { + _phantom: PhantomData, + } } } diff --git a/aarch32-cpu/src/generic_timer/el1.rs b/aarch32-cpu/src/generic_timer/el1.rs index 9af1d901..dde027fa 100644 --- a/aarch32-cpu/src/generic_timer/el1.rs +++ b/aarch32-cpu/src/generic_timer/el1.rs @@ -5,6 +5,12 @@ use crate::register; use super::{El0PhysicalTimer, El0VirtualTimer, GenericTimer}; /// Represents our Physical Timer when we are running at EL1. +/// +/// This works exactly like [El0PhysicalTimer], but it gives you extra methods +/// for functionality that only processors running at EL1 can access. +/// +/// This type is not [Send] because it is a per-core type and should not be moved across +/// cores on an SMP system. pub struct El1PhysicalTimer(pub(crate) El0PhysicalTimer); impl El1PhysicalTimer { @@ -12,8 +18,9 @@ impl El1PhysicalTimer { /// /// # Safety /// - /// Only create one of these at any given time, as they access shared - /// mutable state within the processor and do read-modify-writes on that state. + /// Only create one Physical Timer handle (at any EL) at any given time, as + /// they access shared mutable state within the processor and do + /// read-modify-writes on that state. pub unsafe fn new() -> El1PhysicalTimer { unsafe { El1PhysicalTimer(El0PhysicalTimer::new()) } } @@ -80,6 +87,12 @@ impl GenericTimer for El1PhysicalTimer { } /// Represents our Virtual Timer when we are running at EL1. +/// +/// This works exactly like [El0VirtualTimer], but it gives you extra methods +/// for functionality that only processors running at EL1 can access. +/// +/// This type is not [Send] because it is a per-core type and should not be moved across +/// cores on an SMP system. pub struct El1VirtualTimer(El0VirtualTimer); impl El1VirtualTimer { @@ -87,8 +100,9 @@ impl El1VirtualTimer { /// /// # Safety /// - /// Only create one of these at any given time, as they access shared - /// mutable state within the processor and do read-modify-writes on that state. + /// Only create one Virtual Timer handle (at any EL) at any given time, as + /// they access shared mutable state within the processor and do + /// read-modify-writes on that state. pub unsafe fn new() -> El1VirtualTimer { unsafe { El1VirtualTimer(El0VirtualTimer::new()) } } diff --git a/aarch32-cpu/src/generic_timer/el2.rs b/aarch32-cpu/src/generic_timer/el2.rs index fd34c915..ad80e701 100644 --- a/aarch32-cpu/src/generic_timer/el2.rs +++ b/aarch32-cpu/src/generic_timer/el2.rs @@ -5,6 +5,12 @@ use crate::register; use super::{El1PhysicalTimer, El1VirtualTimer, GenericTimer}; /// Represents our Physical Timer when we are running at EL2. +/// +/// This works exactly like [El1PhysicalTimer], but it gives you extra methods +/// for functionality that only processors running at EL2 can access. +/// +/// This type is not [Send] because it is a per-core type and should not be moved across +/// cores on an SMP system. pub struct El2PhysicalTimer(El1PhysicalTimer); impl El2PhysicalTimer { @@ -12,8 +18,9 @@ impl El2PhysicalTimer { /// /// # Safety /// - /// Only create one of these at any given time, as they access shared - /// mutable state within the processor and do read-modify-writes on that state. + /// Only create one Physical Timer handle (at any EL) at any given time, as + /// they access shared mutable state within the processor and do + /// read-modify-writes on that state. pub unsafe fn new() -> El2PhysicalTimer { unsafe { El2PhysicalTimer(El1PhysicalTimer::new()) } } @@ -76,15 +83,22 @@ impl GenericTimer for El2PhysicalTimer { } /// Represents our Virtual Timer when we are running at EL1. +/// +/// This works exactly like [El1VirtualTimer], but it gives you extra methods +/// for functionality that only processors running at EL2 can access. +/// +/// This type is not [Send] because it is a per-core type and should not be moved +/// across cores on an SMP system. pub struct El2VirtualTimer(El1VirtualTimer); impl El2VirtualTimer { - /// Create an EL2 Generic Timer handle + /// Create an EL2 Virtual Timer handle /// /// # Safety /// - /// Only create one of these at any given time, as they access shared - /// mutable state within the processor and do read-modify-writes on that state. + /// Only create one Virtual Timer handle (at any EL) at any given time, as + /// they access shared mutable state within the processor and do + /// read-modify-writes on that state. pub unsafe fn new() -> El2VirtualTimer { unsafe { El2VirtualTimer(El1VirtualTimer::new()) } } From b6df0b93fe5e26cb248adaad1d557c62012ec74e Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Mon, 8 Jun 2026 09:18:54 +0100 Subject: [PATCH 2/4] Make Hyp Timer !Send --- aarch32-cpu/src/generic_timer/el2.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/aarch32-cpu/src/generic_timer/el2.rs b/aarch32-cpu/src/generic_timer/el2.rs index ad80e701..3b0aba6f 100644 --- a/aarch32-cpu/src/generic_timer/el2.rs +++ b/aarch32-cpu/src/generic_timer/el2.rs @@ -1,5 +1,7 @@ //! Code and types for Generic Timer support at EL2 on Armv8-R. +use core::marker::PhantomData; + use crate::register; use super::{El1PhysicalTimer, El1VirtualTimer, GenericTimer}; @@ -160,8 +162,16 @@ impl GenericTimer for El2VirtualTimer { } } -/// Represents our Hypervisor-specific Physical Timer when we are running at EL1. -pub struct El2HypPhysicalTimer(); +/// Represents our Hypervisor-specific Physical Timer. +/// +/// This is designed for use by a hypervisor, whilst an EL1 application +/// concurrently uses the Physical Timer and/or the Virtual Timer. +/// +/// This type is not [Send] because it is a per-core type and should not be moved across +/// cores on an SMP system. +pub struct El2HypPhysicalTimer { + _phantom: PhantomData<*const u8>, +} impl El2HypPhysicalTimer { /// Create a Timer handle for the EL2-specific Hyp Physical Timer. @@ -169,9 +179,13 @@ impl El2HypPhysicalTimer { /// # Safety /// /// Only create one of these at any given time, as they access shared - /// mutable state within the processor and do read-modify-writes on that state. + /// mutable state within the processor and do read-modify-writes on that + /// state. This timer is distinct from the Physical Timer and the Virtual + /// Timer, and so can exist concurrently. pub unsafe fn new() -> El2HypPhysicalTimer { - El2HypPhysicalTimer() + El2HypPhysicalTimer { + _phantom: PhantomData, + } } } From 1248a2e20e3f2ea9e28d9f1f45fed579e9b24836 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Mon, 8 Jun 2026 09:19:04 +0100 Subject: [PATCH 3/4] Make EL1 and EL2 MPUs !Send --- aarch32-cpu/src/pmsav8.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/aarch32-cpu/src/pmsav8.rs b/aarch32-cpu/src/pmsav8.rs index 5508fa87..47984fa6 100644 --- a/aarch32-cpu/src/pmsav8.rs +++ b/aarch32-cpu/src/pmsav8.rs @@ -6,6 +6,8 @@ //! //! [armv8r]: https://developer.arm.com/documentation/ddi0568/latest/ +use core::marker::PhantomData; + use arbitrary_int::{u26, u3}; use crate::register; @@ -28,7 +30,12 @@ pub enum Error { } /// Represents our PMSAv8-32 EL1 MPU -pub struct El1Mpu(); +/// +/// This type is not [Send] because it is a per-core type and should not be moved across +/// cores on an SMP system. +pub struct El1Mpu { + _phantom: PhantomData<*const u8>, +} impl El1Mpu { /// Create an MPU handle @@ -38,7 +45,9 @@ impl El1Mpu { /// Only create one of these at any given time, as they access shared /// mutable state within the processor and do read-modify-writes on that state. pub unsafe fn new() -> El1Mpu { - El1Mpu() + El1Mpu { + _phantom: PhantomData, + } } /// How many MPU regions are there? @@ -199,7 +208,12 @@ impl El1Mpu { } /// Represents our PMSAv8-32 EL2 MPU -pub struct El2Mpu(); +/// +/// This type is not [Send] because it is a per-core type and should not be moved across +/// cores on an SMP system. +pub struct El2Mpu { + _phantom: PhantomData<*const u8>, +} impl El2Mpu { /// Create an EL2 MPU handle @@ -209,7 +223,9 @@ impl El2Mpu { /// Only create one of these at any given time, as they access shared /// mutable state within the processor and do read-modify-writes on that state. pub unsafe fn new() -> El2Mpu { - El2Mpu() + El2Mpu { + _phantom: PhantomData, + } } /// How many EL2 MPU regions are there? @@ -407,6 +423,7 @@ pub struct El1Region { // Creating a static Region is fine - the pointers within it // only go to the MPU and aren't accessed via Rust code +unsafe impl Send for El1Region {} unsafe impl Sync for El1Region {} /// Configuration for the PMSAv8-32 EL2 MPU @@ -454,6 +471,7 @@ pub struct El2Region { // Creating a static El2Region is fine - the pointers within it // only go to the MPU and aren't accessed via Rust code +unsafe impl Send for El2Region {} unsafe impl Sync for El2Region {} /// Describes the memory ordering and cacheability of a region From 112b42d1644edeb4239bb0feb5e0520d3e2b0585 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Mon, 8 Jun 2026 10:24:27 +0100 Subject: [PATCH 4/4] Make PMSAv7 MPU !Send --- aarch32-cpu/src/pmsav7.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/aarch32-cpu/src/pmsav7.rs b/aarch32-cpu/src/pmsav7.rs index 0db24a88..e6c8b8a4 100644 --- a/aarch32-cpu/src/pmsav7.rs +++ b/aarch32-cpu/src/pmsav7.rs @@ -5,6 +5,8 @@ //! //! [armv7]: https://developer.arm.com/documentation/ddi0406/latest +use core::marker::PhantomData; + use crate::register; use arbitrary_int::{u2, u3}; @@ -23,7 +25,12 @@ pub enum Error { } /// Represents our PMSAv7 MPU -pub struct Mpu(); +/// +/// This type is not [Send] because it is a per-core type and should not be moved across +/// cores on an SMP system. +pub struct Mpu { + _phantom: PhantomData<*const u8>, +} impl Mpu { /// Create an MPU handle @@ -33,7 +40,9 @@ impl Mpu { /// Only create one of these at any given time, as they access shared /// mutable state within the processor and do read-modify-writes on that state. pub unsafe fn new() -> Mpu { - Mpu() + Mpu { + _phantom: PhantomData, + } } /// How many MPU instruction regions are there?