From 130410b0916a17336552c820566d95444c06c21d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 17:59:04 +0100 Subject: [PATCH 1/3] Revert removal of the `VolatileCell` type This reverts commit 8e7184549f9bbd522058d68456985328108d1949. --- src/cell.rs | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 2 files changed, 283 insertions(+) create mode 100644 src/cell.rs diff --git a/src/cell.rs b/src/cell.rs new file mode 100644 index 0000000..704b670 --- /dev/null +++ b/src/cell.rs @@ -0,0 +1,281 @@ +// ! Provides the wrapper type `VolatileCell`, which wraps any copy-able type and allows for +// ! volatile memory access to wrapped value. Volatile memory accesses are never optimized away by +// ! the compiler, and are useful in many low-level systems programming and concurrent contexts. +// ! +// ! # Dealing with Volatile Pointers +// ! +// ! Frequently, one may have to deal with volatile pointers, eg, writes to specific memory +// ! locations. The canonical way to solve this is to cast the pointer to a volatile wrapper +// ! directly, eg: +// ! +// ! ```rust +// ! use volatile::VolatileCell; +// ! +// ! let mut_ptr = 0xFEE00000 as *mut u32; +// ! +// ! let volatile_ptr = mut_ptr as *mut VolatileCell; +// ! ``` +// ! +// ! and then perform operations on the pointer as usual in a volatile way. This method works as all +// ! of the volatile wrapper types are the same size as their contained values. + +use crate::{ + access::{Access, ReadOnly, ReadWrite, Readable, Writable}, + ptr_send::VolatilePtr, +}; +use core::{cell::UnsafeCell, fmt, marker::PhantomData, ptr::NonNull}; + +/// A wrapper type around a volatile variable, which allows for volatile reads and writes +/// to the contained value. The stored type needs to be `Copy`, as volatile reads and writes +/// take and return copies of the value. +/// +/// Volatile operations instruct the compiler to skip certain optimizations for these +/// operations. For example, the compiler will not optimize them away even if it thinks +/// that the operations have no observable effect. This is for example desirable when +/// the value is stored in a special memory region that has side effects, such as +/// memory-mapped device registers. +/// +/// Note that this wrapper types *does not* enforce any atomicity guarantees. To get atomicity, +/// use the [`core::sync::atomic`] module. +/// +/// The size of this struct is the same as the size of the contained type. +#[derive(Default)] +#[repr(transparent)] +pub struct VolatileCell { + value: UnsafeCell, + access: PhantomData, +} + +impl VolatileCell { + /// Construct a new volatile cell wrapping the given value. + /// + /// The returned cell allows read and write operations. Use + /// [`new_restricted`][VolatileCell::new_restricted] to create read-only + /// or write-only cells. + /// + /// Calling `VolatileCell::new(v)` is equivalent to calling + /// `VolatileCell::new_restricted(access::ReadWrite, v)`. + /// + /// ## Example + /// + /// ```rust + /// use volatile::VolatileCell; + /// + /// let mut value = VolatileCell::new(0u32); + /// assert_eq!(value.read(), 0); + /// value.write(42); + /// assert_eq!(value.read(), 42); + /// value.update(|v| v + 2 ); + /// assert_eq!(value.read(), 44); + /// ``` + pub const fn new(value: T) -> Self { + VolatileCell::new_restricted(ReadWrite, value) + } + + /// Construct a new volatile cell with restricted access, wrapping the given value. + /// + /// ## Examples + /// + /// ``` + /// use volatile::{VolatileCell, access}; + /// + /// let mut read_write = VolatileCell::new_restricted(access::ReadWrite, 0u32); + /// read_write.write(100); + /// read_write.update(|v| v / 2); + /// assert_eq!(read_write.read(), 50); + /// + /// let read_only = VolatileCell::new_restricted(access::ReadOnly, 0u32); + /// assert_eq!(read_only.read(), 0); + /// + /// let mut write_only = VolatileCell::new_restricted(access::WriteOnly, 0u32); + /// write_only.write(1); + /// ``` + /// + /// ```compile_fail + /// # use volatile::{VolatileCell, access}; + /// // reading or updating a write-only value is not allowed + /// let write_only = VolatileCell::new_restricted(access::WriteOnly, 0u32); + /// write_only.read(); // -> compile error + /// write_only.update(|v| v + 1); // -> compile error + /// ``` + /// + /// ```compile_fail + /// # use volatile::{VolatileCell, access}; + /// // writing or updating a write-only value is not allowed + /// let read_only = VolatileCell::new_restricted(access::ReadOnly, 0u32); + /// read_only.write(5); // -> compile error + /// read_only.update(|v| v + 1); // -> compile error + /// ``` + pub const fn new_restricted(access: A, value: T) -> VolatileCell + where + A: Access, + { + let _ = access; + VolatileCell { + value: UnsafeCell::new(value), + access: PhantomData, + } + } +} + +impl VolatileCell { + pub fn access(&self) -> A + where + A: Access, + { + A::default() + } + + pub fn as_ptr(&self) -> VolatilePtr { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { VolatilePtr::new_restricted(ReadOnly, NonNull::new_unchecked(self.value.get())) } + } + + pub fn as_mut_ptr(&mut self) -> VolatilePtr + where + A: Access, + { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { + VolatilePtr::new_restricted(A::default(), NonNull::new_unchecked(self.value.get())) + } + } + + /// Performs a volatile read of the contained value, returning a copy + /// of the read value. Volatile reads are guaranteed not to be optimized + /// away by the compiler, but by themselves do not have atomic ordering + /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper type. + /// + /// ```rust + /// use volatile::VolatileCell; + /// + /// let value = VolatileCell::new(42u32); + /// assert_eq!(value.read(), 42u32); + /// ``` + pub fn read(&self) -> T + where + A: Readable, + T: Copy, + { + self.as_ptr().read() + } + + /// Performs a volatile write, setting the contained value to the given value `value`. Volatile + /// writes are guaranteed to not be optimized away by the compiler, but by themselves do not + /// have atomic ordering guarantees. To also get atomicity, consider looking at the `Atomic` + /// wrapper type. + /// + /// ```rust + /// use volatile::VolatileCell; + /// + /// let mut value = VolatileCell::new(0u32); + /// value.write(42u32); + /// assert_eq!(value.read(), 42u32); + /// ``` + pub fn write(&mut self, value: T) + where + A: Writable, + T: Copy, + { + self.as_mut_ptr().write(value) + } + + /// Performs a volatile read of the contained value, passes a mutable reference to it to the + /// function `f`, and then performs a volatile write of the (potentially updated) value back to + /// the contained value. + /// + /// ```rust + /// use volatile::VolatileCell; + /// + /// let mut value = VolatileCell::new(21u32); + /// value.update(|val| val * 2); + /// assert_eq!(value.read(), 42u32); + /// ``` + pub fn update(&mut self, f: F) + where + F: FnOnce(T) -> T, + A: Readable + Writable, + T: Copy, + { + let new = f(self.read()); + self.write(new); + } +} + +/// Create a clone of the `VolatileCell`. +/// +/// A `VolatileCell` is clonable only if the cell is marked as readable. +/// +/// Note that using a `VolatileCell` only makes sense if the backing memory is +/// actually volatile. Stack memory is not volatile normally, so this clone +/// implementation is not needed in most situations. Instead, it is recommended +/// to read out the wrapped value instead. +/// +/// Cloning a `VolatileCell` is equivalent to: +/// +/// ```rust +/// # use volatile::VolatileCell; +/// # let volatile_cell = VolatileCell::new(0u32); +/// VolatileCell::new_restricted(volatile_cell.access(), volatile_cell.read()) +/// # ; +/// ``` +impl Clone for VolatileCell +where + T: Copy, + A: Readable, +{ + fn clone(&self) -> Self { + VolatileCell::new_restricted(self.access(), self.read()) + } +} + +/// This `Debug` implementation only applies to cells that are [`Readable`] +/// because it includes the wrapped value. +impl fmt::Debug for VolatileCell +where + T: Copy + fmt::Debug, + A: Readable, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("VolatileCell").field(&self.read()).finish() + } +} + +#[cfg(test)] +mod tests { + use super::VolatileCell; + + #[test] + fn test_read() { + assert_eq!(VolatileCell::new(42).read(), 42); + } + + #[test] + fn test_write() { + let mut volatile = VolatileCell::new(42); + volatile.write(50); + assert_eq!(*volatile.value.get_mut(), 50); + } + + #[test] + fn test_update() { + let mut volatile = VolatileCell::new(42); + volatile.update(|v| v + 1); + assert_eq!(volatile.read(), 43); + } + + #[test] + fn test_pointer_recast() { + let mut target_value = 0u32; + + let target_ptr: *mut u32 = &mut target_value; + let volatile_ptr = target_ptr as *mut VolatileCell; + + // UNSAFE: Safe, as we know the value exists on the stack. + unsafe { + (*volatile_ptr).write(42u32); + } + + assert_eq!(target_value, 42u32); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1f96cd8..17837ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,10 +13,12 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] +pub use cell::VolatileCell; pub use ptr_copy::VolatilePtrCopy; pub use ptr_send::VolatilePtr; pub mod access; +mod cell; mod macros; mod ptr_copy; mod ptr_send; From 024ac780a7fa480ed5b034dfeb604e947f7b566b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 20 Jan 2023 18:46:27 +0100 Subject: [PATCH 2/3] Use `UnsafeCell::raw_get` instead of `get` --- src/cell.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/cell.rs b/src/cell.rs index 704b670..586183e 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -128,7 +128,12 @@ impl VolatileCell { pub fn as_ptr(&self) -> VolatilePtr { // UNSAFE: Safe, as we know that our internal value exists. - unsafe { VolatilePtr::new_restricted(ReadOnly, NonNull::new_unchecked(self.value.get())) } + unsafe { + VolatilePtr::new_restricted( + ReadOnly, + NonNull::new_unchecked(UnsafeCell::raw_get(&self.value)), + ) + } } pub fn as_mut_ptr(&mut self) -> VolatilePtr @@ -137,7 +142,10 @@ impl VolatileCell { { // UNSAFE: Safe, as we know that our internal value exists. unsafe { - VolatilePtr::new_restricted(A::default(), NonNull::new_unchecked(self.value.get())) + VolatilePtr::new_restricted( + A::default(), + NonNull::new_unchecked(UnsafeCell::raw_get(&self.value)), + ) } } From 2141a874dd13a6b360fe44f40c8d7dc7c75b1ca3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 21 Jan 2023 10:56:19 +0100 Subject: [PATCH 3/3] Take `&self` instead of `&mut self` in write methods Taking `&mut self` is not possible because the compiler will make too many guarantees to LLVM in this case, e.g. that spurious reads of the wrapped value are permitted. Because of this change, the `VolatileCell` type must be `!Sync`. However, it can still be `Send`, so it can still be used in statics when it's wrapped in a `Mutex`. --- src/cell.rs | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/cell.rs b/src/cell.rs index 586183e..bc34d06 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -20,7 +20,7 @@ // ! of the volatile wrapper types are the same size as their contained values. use crate::{ - access::{Access, ReadOnly, ReadWrite, Readable, Writable}, + access::{Access, ReadWrite, Readable, Writable}, ptr_send::VolatilePtr, }; use core::{cell::UnsafeCell, fmt, marker::PhantomData, ptr::NonNull}; @@ -39,6 +39,26 @@ use core::{cell::UnsafeCell, fmt, marker::PhantomData, ptr::NonNull}; /// use the [`core::sync::atomic`] module. /// /// The size of this struct is the same as the size of the contained type. +/// +/// ## Examples +/// +/// `VolatileCell` allows writes through shared references, so it's not safe to share +/// references across threads. For this reason, `VolatileCell` does not implement the +/// [`Sync`] trait. As a result, you cannot use the type in statics without synchronization: +/// +/// ```compile_fail +/// # use volatile::VolatileCell; +/// // error: VolatileCell is not Sync +/// static FOO: &mut VolatileCell = todo!(); +/// ``` +/// +/// To use `VolatileCell` in a `static`, wrap it in some type that provides mutual exclusion: +/// +/// ``` +/// use volatile::VolatileCell; +/// use std::sync::Mutex; +/// static FOO: Mutex>> = Mutex::new(None); +/// ``` #[derive(Default)] #[repr(transparent)] pub struct VolatileCell { @@ -126,17 +146,7 @@ impl VolatileCell { A::default() } - pub fn as_ptr(&self) -> VolatilePtr { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { - VolatilePtr::new_restricted( - ReadOnly, - NonNull::new_unchecked(UnsafeCell::raw_get(&self.value)), - ) - } - } - - pub fn as_mut_ptr(&mut self) -> VolatilePtr + pub fn as_ptr(&self) -> VolatilePtr where A: Access, { @@ -180,12 +190,12 @@ impl VolatileCell { /// value.write(42u32); /// assert_eq!(value.read(), 42u32); /// ``` - pub fn write(&mut self, value: T) + pub fn write(&self, value: T) where A: Writable, T: Copy, { - self.as_mut_ptr().write(value) + self.as_ptr().write(value) } /// Performs a volatile read of the contained value, passes a mutable reference to it to the @@ -199,7 +209,7 @@ impl VolatileCell { /// value.update(|val| val * 2); /// assert_eq!(value.read(), 42u32); /// ``` - pub fn update(&mut self, f: F) + pub fn update(&self, f: F) where F: FnOnce(T) -> T, A: Readable + Writable, @@ -267,7 +277,7 @@ mod tests { #[test] fn test_update() { - let mut volatile = VolatileCell::new(42); + let volatile = VolatileCell::new(42); volatile.update(|v| v + 1); assert_eq!(volatile.read(), 43); }