In theory, `read_volatile` and `write_volatile` in Rust can have UB in case of the data races [1]. However, kernel uses volatiles to implement READ_ONCE() and WRITE_ONCE(), and expects races on these marked accesses don't cause UB. And they are proven to have a lot of usages in kernel. To close this gap, `read_once` and `write_once` are introduced, they have the same semantics as `READ_ONCE` and `WRITE_ONCE` especially regarding data races under the assumption that `read_volatile` and `write_volatile` have the same behavior as a volatile pointer in C from a compiler point of view. Longer term solution is to work with Rust language side for a better way to implement `read_once` and `write_once`. But so far, it should be good enough. Suggested-by: Alice Ryhl <aliceryhl@xxxxxxxxxx> Link: https://doc.rust-lang.org/std/ptr/fn.read_volatile.html#safety [1] Signed-off-by: Boqun Feng <boqun.feng@xxxxxxxxx> --- Notice I also make the primitives only work on T: Copy, since I don't think Rust side and C side will use a !Copy type to communicate, but we can always remove that constrait later. rust/kernel/prelude.rs | 2 ++ rust/kernel/types.rs | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index ae21600970b3..351ad182bc63 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -38,3 +38,5 @@ pub use super::init::{InPlaceInit, Init, PinInit}; pub use super::current; + +pub use super::types::{read_once, write_once}; diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index d849e1979ac7..b0872f751f97 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -432,3 +432,46 @@ pub enum Either<L, R> { /// Constructs an instance of [`Either`] containing a value of type `R`. Right(R), } + +/// (Concurrent) Primitives to interact with C side, which are considered as marked access: +/// +/// tools/memory-memory/Documentation/access-marking.txt + +/// The counter part of C `READ_ONCE()`. +/// +/// The semantics is exactly the same as `READ_ONCE()`, especially when used for intentional data +/// races. +/// +/// # Safety +/// +/// * `src` must be valid for reads. +/// * `src` must be properly aligned. +/// * `src` must point to a properly initialized value of value `T`. +#[inline(always)] +pub unsafe fn read_once<T: Copy>(src: *const T) -> T { + // SAFETY: the read is valid because of the function's safety requirement, plus the assumption + // here is that 1) a volatile pointer dereference in C and 2) a `read_volatile` in Rust have the + // same semantics, so this function should have the same behavior as `READ_ONCE()` regarding + // data races. + unsafe { src.read_volatile() } +} + +/// The counter part of C `WRITE_ONCE()`. +/// +/// The semantics is exactly the same as `WRITE_ONCE()`, especially when used for intentional data +/// races. +/// +/// # Safety +/// +/// * `dst` must be valid for writes. +/// * `dst` must be properly aligned. +#[inline(always)] +pub unsafe fn write_once<T: Copy>(dst: *mut T, value: T) { + // SAFETY: the write is valid because of the function's safety requirement, plus the assumption + // here is that 1) a write to a volatile pointer dereference in C and 2) a `write_volatile` in + // Rust have the same semantics, so this function should have the same behavior as + // `WRITE_ONCE()` regarding data races. + unsafe { + core::ptr::write_volatile(dst, value); + } +} -- 2.41.0