diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 654bd68..9efeec9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,6 +67,26 @@ jobs: command: test args: --features unstable + very_unstable: + name: Test Suite (very_unstable) + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install nightly toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + + - name: Run cargo test --features very_unstable + uses: actions-rs/cargo@v1 + with: + command: test + args: --features very_unstable + lints: name: Lints diff --git a/Cargo.toml b/Cargo.toml index 19f80dd..6fa62fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,20 +4,25 @@ version = "0.4.6" authors = ["Philipp Oppermann "] license = "MIT OR Apache-2.0" keywords = ["volatile"] -description = "A simple volatile wrapper type" +description = "Volatile wrapper types for raw pointers" documentation = "https://docs.rs/volatile" repository = "https://github.com/rust-osdev/volatile" +edition = "2021" [dependencies] [features] # Enable unstable features; requires Rust nightly; might break on compiler updates unstable = [] +# Enable unstable and experimental features; requires Rust nightly; might break on compiler updates +very_unstable = ["unstable"] + +[dev-dependencies] +rand = "0.8.3" [package.metadata.release] -dev-version = false pre-release-replacements = [ - { file="Changelog.md", search="# Unreleased", replace="# Unreleased\n\n# {{version}} – {{date}}", exactly=1 }, + { file = "Changelog.md", search = "# Unreleased", replace = "# Unreleased\n\n# {{version}} – {{date}}", exactly = 1 }, ] pre-release-commit-message = "Release version {{version}}" diff --git a/Changelog.md b/Changelog.md index f6043b1..c1a5154 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,12 @@ # Unreleased +- **Breaking:** [New design based on raw pointers](https://github.com/rust-osdev/volatile/pull/29) + - The previous reference-based design was [unsound](https://github.com/rust-osdev/volatile/pull/13#issuecomment-842455552) because it allowed the compiler to insert spurious reads. + - The new design features two wrapper types for raw pointers: `VolatilePtr` and `VolatileRef` + - `VolatilePtr` provides safe read and write access to volatile values. Like raw pointers, it implements `Copy` and is `!Sync`. + - `VolatileRef` is a pointer type that respects Rust's aliasing rules. It doesn't implement `Copy`, requires a `&mut` reference for modification, and implements `Sync`. It can converted to temporary `VolatilePtr` instances through the `as_ptr`/`as_mut_ptr` methods. +- We now provide methods for volatile slice operations and a `map!` macro for struct field projection. These advanced features are gated behind a cargo feature named _"unstable"_. + # 0.4.6 – 2023-01-17 - Fix UB in slice methods when Deref returns different references ([#27](https://github.com/rust-osdev/volatile/pull/27)) diff --git a/README.md b/README.md index 813c85e..32b8ad3 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,27 @@ [![Build Status](https://github.com/rust-osdev/volatile/workflows/Build/badge.svg)](https://github.com/rust-osdev/volatile/actions?query=workflow%3ABuild) [![Docs.rs Badge](https://docs.rs/volatile/badge.svg)](https://docs.rs/volatile/) -Provides the wrapper type `Volatile`, which wraps a reference to 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. +Provides volatile wrapper types for raw pointers. -The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider looking at the `Atomic` wrapper types found in `libcore` or `libstd`. +The volatile wrapper types in this crate wrap a pointer to any [`Copy`]-able type and provide 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. + +This crate provides two different wrapper types: [`VolatilePtr`] and [`VolatileRef`]. +The difference between the two types is that the former behaves like a raw pointer, while the latter behaves like a Rust reference type. +For example, `VolatilePtr` can be freely copied, but not sent across threads because this could introduce mutable aliasing. +The `VolatileRef` type, on the other hand, requires exclusive access for mutation, so that sharing it across thread boundaries is safe. + +Both wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider looking at the `Atomic` wrapper types found in `libcore` or `libstd`. + +## Why is there no `VolatileCell`? + +Many people expressed interest in a `VolatileCell` type, i.e. a transparent wrapper type that owns the wrapped value. +Such a type would be similar to [`core::cell::Cell`], with the difference that all methods are volatile. +Unfortunately, it is not sound to implement such a `VolatileCell` type in Rust. +The reason is that Rust and LLVM consider `&` and `&mut` references as _dereferencable_. +This means that the compiler is allowed to freely access the referenced value without any restrictions. +So no matter how a `VolatileCell` type is implemented, the compiler is allowed to perform non-volatile read operations of the contained value, which can lead to unexpected (or even undefined?) behavior. +For more details, see the discussion [in our repository](https://github.com/rust-osdev/volatile/issues/31) and [in the `unsafe-code-guidelines` repository](https://github.com/rust-lang/unsafe-code-guidelines/issues/411). ## License diff --git a/src/access.rs b/src/access.rs index 438d233..42d4ec6 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,22 +1,83 @@ +//! Marker types for limiting access. + +/// Private trait that is implemented for the types in this module. +pub trait Access: Copy + Default { + /// Ensures that this trait cannot be implemented outside of this crate. + #[doc(hidden)] + fn _private() -> _Private { + _Private + } + + /// Reduced access level to safely share the corresponding value. + type RestrictShared: Access; +} + /// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. -pub trait Readable {} +pub trait Readable: Copy + Default { + /// Reduced access level to safely share the corresponding value. + type RestrictShared: Readable + Access; + + /// Ensures that this trait cannot be implemented outside of this crate. + fn _private() -> _Private { + _Private + } +} /// Helper trait that is implemented by [`ReadWrite`] and [`WriteOnly`]. -pub trait Writable {} +pub trait Writable: Access { + /// Ensures that this trait cannot be implemented outside of this crate. + fn _private() -> _Private { + _Private + } +} + +/// Implemented for access types that permit copying of `VolatileRef`. +pub trait Copyable { + /// Ensures that this trait cannot be implemented outside of this crate. + fn _private() -> _Private { + _Private + } +} + +impl Access for T +where + T: Readable + Default + Copy, +{ + type RestrictShared = ::RestrictShared; +} /// Zero-sized marker type for allowing both read and write access. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone)] pub struct ReadWrite; -impl Readable for ReadWrite {} +impl Readable for ReadWrite { + type RestrictShared = ReadOnly; +} impl Writable for ReadWrite {} /// Zero-sized marker type for allowing only read access. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone)] pub struct ReadOnly; - -impl Readable for ReadOnly {} +impl Readable for ReadOnly { + type RestrictShared = ReadOnly; +} +impl Copyable for ReadOnly {} /// Zero-sized marker type for allowing only write access. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone)] pub struct WriteOnly; +impl Access for WriteOnly { + type RestrictShared = NoAccess; +} impl Writable for WriteOnly {} + +/// Zero-sized marker type that grants no access. +#[derive(Debug, Default, Copy, Clone)] +pub struct NoAccess; +impl Access for NoAccess { + type RestrictShared = NoAccess; +} +impl Copyable for NoAccess {} + +#[non_exhaustive] +#[doc(hidden)] +pub struct _Private; diff --git a/src/lib.rs b/src/lib.rs index 5581eb0..4734945 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,874 +1,49 @@ -//! Provides the wrapper type `Volatile`, which wraps a reference to 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. +//! Provides volatile wrapper types for raw pointers. //! -//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider +//! The volatile wrapper types in this crate wrap a pointer to any [`Copy`]-able +//! type and provide 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. +//! +//! This crate provides two different wrapper types: [`VolatilePtr`] and [`VolatileRef`]. The +//! difference between the two types is that the former behaves like a raw pointer, while the +//! latter behaves like a Rust reference type. For example, `VolatilePtr` can be freely copied, +//! but not sent across threads because this could introduce mutable aliasing. The `VolatileRef` +//! type, on the other hand, requires exclusive access for mutation, so that sharing it across +//! thread boundaries is safe. +//! +//! Both wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider //! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. +//! +//! ## Why is there no `VolatileCell`? +//! +//! Many people expressed interest in a `VolatileCell` type, i.e. a transparent wrapper type that +//! owns the wrapped value. Such a type would be similar to [`core::cell::Cell`], with the +//! difference that all methods are volatile. +//! +//! Unfortunately, it is not sound to implement such a `VolatileCell` type in Rust. The reason +//! is that Rust and LLVM consider `&` and `&mut` references as _dereferencable_. This means that +//! the compiler is allowed to freely access the referenced value without any restrictions. So +//! no matter how a `VolatileCell` type is implemented, the compiler is allowed to perform +//! non-volatile read operations of the contained value, which can lead to unexpected (or even +//! undefined?) behavior. For more details, see the discussion +//! [in our repository](https://github.com/rust-osdev/volatile/issues/31) +//! and +//! [in the `unsafe-code-guidelines` repository](https://github.com/rust-lang/unsafe-code-guidelines/issues/411). #![no_std] #![cfg_attr(feature = "unstable", feature(core_intrinsics))] #![cfg_attr(feature = "unstable", feature(slice_range))] -#![cfg_attr(feature = "unstable", allow(incomplete_features))] -#![cfg_attr(all(feature = "unstable", test), feature(slice_as_chunks))] +#![cfg_attr(feature = "unstable", feature(slice_ptr_get))] +#![cfg_attr(feature = "very_unstable", feature(const_trait_impl))] +#![cfg_attr(feature = "very_unstable", feature(unboxed_closures))] +#![cfg_attr(feature = "very_unstable", feature(fn_traits))] #![warn(missing_docs)] +#![deny(unsafe_op_in_unsafe_fn)] -use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; -use core::{ - fmt, - marker::PhantomData, - ops::{Deref, DerefMut, Index, IndexMut}, - ptr, - slice::SliceIndex, -}; -#[cfg(feature = "unstable")] -use core::{ - intrinsics, - ops::{Range, RangeBounds}, - slice::range, -}; +pub use volatile_ptr::VolatilePtr; +pub use volatile_ref::VolatileRef; -/// Allows creating read-only and write-only `Volatile` values. pub mod access; - -/// Wraps a reference to make accesses to the referenced value volatile. -/// -/// Allows volatile reads and writes on the referenced value. The referenced value needs to -/// be `Copy` for reading and writing, as volatile reads and writes take and return copies -/// of the value. -/// -/// Since not all volatile resources (e.g. memory mapped device registers) are both readable -/// and writable, this type supports limiting the allowed access types through an optional second -/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults -/// to `ReadWrite`, which allows all operations. -/// -/// The size of this struct is the same as the size of the contained reference. -#[derive(Clone)] -#[repr(transparent)] -pub struct Volatile { - reference: R, - access: PhantomData, -} - -/// Constructor functions for creating new values -/// -/// These functions allow to construct a new `Volatile` instance from a reference type. While -/// the `new` function creates a `Volatile` instance with unrestricted access, there are also -/// functions for creating read-only or write-only instances. -impl Volatile { - /// Constructs a new volatile instance wrapping the given reference. - /// - /// While it is possible to construct `Volatile` instances from arbitrary values (including - /// non-reference values), most of the methods are only available when the wrapped type is - /// a reference. The only reason that we don't forbid non-reference types in the constructor - /// functions is that the Rust compiler does not support trait bounds on generic `const` - /// functions yet. When this becomes possible, we will release a new version of this library - /// with removed support for non-references. For these reasons it is recommended to use - /// the `Volatile` type only with references. - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new(&mut value); - /// volatile.write(1); - /// assert_eq!(volatile.read(), 1); - /// ``` - pub const fn new(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } - } - - /// Constructs a new read-only volatile instance wrapping the given reference. - /// - /// This is equivalent to the `new` function with the difference that the returned - /// `Volatile` instance does not permit write operations. This is for example useful - /// with memory-mapped hardware registers that are defined as read-only by the hardware. - /// - /// ## Example - /// - /// Reading is allowed: - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = 0u32; - /// - /// let volatile = Volatile::new_read_only(&value); - /// assert_eq!(volatile.read(), 0); - /// ``` - /// - /// But writing is not: - /// - /// ```compile_fail - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new_read_only(&mut value); - /// volatile.write(1); - /// //ERROR: ^^^^^ the trait `volatile::access::Writable` is not implemented - /// // for `volatile::access::ReadOnly` - /// ``` - pub const fn new_read_only(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } - } - - /// Constructs a new write-only volatile instance wrapping the given reference. - /// - /// This is equivalent to the `new` function with the difference that the returned - /// `Volatile` instance does not permit read operations. This is for example useful - /// with memory-mapped hardware registers that are defined as write-only by the hardware. - /// - /// ## Example - /// - /// Writing is allowed: - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new_write_only(&mut value); - /// volatile.write(1); - /// ``` - /// - /// But reading is not: - /// - /// ```compile_fail - /// use volatile::Volatile; - /// - /// let value = 0u32; - /// - /// let volatile = Volatile::new_write_only(&value); - /// volatile.read(); - /// //ERROR: ^^^^ the trait `volatile::access::Readable` is not implemented - /// // for `volatile::access::WriteOnly` - /// ``` - pub const fn new_write_only(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } - } -} - -/// Methods for references to `Copy` types -impl Volatile -where - R: Deref, - T: Copy, -{ - /// Performs a volatile read of the contained value. - /// - /// Returns 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 types of - /// the standard/`core` library. - /// - /// ## Examples - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = 42; - /// let shared_reference = Volatile::new(&value); - /// assert_eq!(shared_reference.read(), 42); - /// - /// let mut value = 50; - /// let mut_reference = Volatile::new(&mut value); - /// assert_eq!(mut_reference.read(), 50); - /// ``` - pub fn read(&self) -> T - where - A: Readable, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(&*self.reference) } - } - - /// Performs a volatile write, setting the contained value to the given `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 types of the standard/`core` library. - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); - /// volatile.write(50); - /// - /// assert_eq!(volatile.read(), 50); - /// ``` - pub fn write(&mut self, value: T) - where - A: Writable, - R: DerefMut, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::write_volatile(&mut *self.reference, value) }; - } - - /// Updates the contained value using the given closure and volatile instructions. - /// - /// 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::Volatile; - /// - /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); - /// volatile.update(|val| *val += 1); - /// - /// assert_eq!(volatile.read(), 43); - /// ``` - pub fn update(&mut self, f: F) - where - A: Readable + Writable, - R: DerefMut, - F: FnOnce(&mut T), - { - let mut value = self.read(); - f(&mut value); - self.write(value); - } -} - -/// Method for extracting the wrapped value. -impl Volatile { - /// Extracts the inner value stored in the wrapper type. - /// - /// This method gives direct access to the wrapped reference and thus allows - /// non-volatile access again. This is seldom what you want since there is usually - /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might - /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of - /// the standard library directly, which this method makes possible. - /// - /// Since no memory safety violation can occur when accessing the referenced value using - /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the - /// application level, so this method should be used with care. - /// - /// ## Example - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); - /// volatile.write(50); - /// let unwrapped: &mut i32 = volatile.extract_inner(); - /// - /// assert_eq!(*unwrapped, 50); // non volatile access, be careful! - /// ``` - pub fn extract_inner(self) -> R { - self.reference - } -} - -/// Transformation methods for accessing struct fields -impl Volatile -where - R: Deref, - T: ?Sized, -{ - /// Constructs a new `Volatile` reference by mapping the wrapped value. - /// - /// This method is useful for accessing individual fields of volatile structs. - /// - /// Note that this method gives temporary access to the wrapped reference, which allows - /// accessing the value in a non-volatile way. This is normally not what you want, so - /// **this method should only be used for reference-to-reference transformations**. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile reference to a field - /// let field_2 = volatile.map(|example| &example.field_2); - /// assert_eq!(field_2.read(), 255); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read of the referenced value: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 5; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // DON'T DO THIS: - /// let mut readout = 0; - /// volatile.map(|value| { - /// readout = *value; // non-volatile read, might lead to bugs - /// value - /// }); - /// ``` - pub fn map<'a, F, U>(&'a self, f: F) -> Volatile<&'a U, A> - where - F: FnOnce(&'a T) -> &'a U, - U: ?Sized, - T: 'a, - { - Volatile { - reference: f(self.reference.deref()), - access: self.access, - } - } - - /// Constructs a new mutable `Volatile` reference by mapping the wrapped value. - /// - /// This method is useful for accessing individual fields of volatile structs. - /// - /// Note that this method gives temporary access to the wrapped reference, which allows - /// accessing the value in a non-volatile way. This is normally not what you want, so - /// **this method should only be used for reference-to-reference transformations**. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile reference to a field - /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2); - /// field_2.write(128); - /// assert_eq!(field_2.read(), 128); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read or write of the referenced value: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 5; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // DON'T DO THIS: - /// volatile.map_mut(|value| { - /// *value = 10; // non-volatile write, might lead to bugs - /// value - /// }); - /// ``` - pub fn map_mut<'a, F, U>(&'a mut self, f: F) -> Volatile<&'a mut U, A> - where - F: FnOnce(&mut T) -> &mut U, - R: DerefMut, - U: ?Sized, - T: 'a, - { - Volatile { - reference: f(&mut self.reference), - access: self.access, - } - } -} - -/// Methods for volatile slices -impl Volatile -where - R: Deref, -{ - /// Applies the index operation on the wrapped slice. - /// - /// Returns a shared `Volatile` reference to the resulting subslice. - /// - /// This is a convenience method for the `map(|slice| slice.index(index))` operation, so it - /// has the same behavior as the indexing operation on slice (e.g. panic if index is - /// out-of-bounds). - /// - /// ## Examples - /// - /// Accessing a single slice element: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = Volatile::new(slice); - /// assert_eq!(volatile.index(1).read(), 2); - /// ``` - /// - /// Accessing a subslice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = Volatile::new(slice); - /// let subslice = volatile.index(1..); - /// assert_eq!(subslice.index(0).read(), 2); - /// ``` - pub fn index<'a, I>(&'a self, index: I) -> Volatile<&'a I::Output, A> - where - I: SliceIndex<[T]>, - T: 'a, - { - self.map(|slice| slice.index(index)) - } - - /// Applies the mutable index operation on the wrapped slice. - /// - /// Returns a mutable `Volatile` reference to the resulting subslice. - /// - /// This is a convenience method for the `map_mut(|slice| slice.index_mut(index))` - /// operation, so it has the same behavior as the indexing operation on slice - /// (e.g. panic if index is out-of-bounds). - /// - /// ## Examples - /// - /// Accessing a single slice element: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut array = [1, 2, 3]; - /// let slice = &mut array[..]; - /// let mut volatile = Volatile::new(slice); - /// volatile.index_mut(1).write(6); - /// assert_eq!(volatile.index(1).read(), 6); - /// ``` - /// - /// Accessing a subslice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut array = [1, 2, 3]; - /// let slice = &mut array[..]; - /// let mut volatile = Volatile::new(slice); - /// let mut subslice = volatile.index_mut(1..); - /// subslice.index_mut(0).write(6); - /// assert_eq!(subslice.index(0).read(), 6); - /// ``` - pub fn index_mut<'a, I>(&'a mut self, index: I) -> Volatile<&mut I::Output, A> - where - I: SliceIndex<[T]>, - R: DerefMut, - T: 'a, - { - self.map_mut(|slice| slice.index_mut(index)) - } - - /// Copies all elements from `self` into `dst`, using a volatile memcpy. - /// - /// The length of `dst` must be the same as `self`. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a volatile slice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2]; - /// // the `Volatile` type does not work with arrays, so convert `src` to a slice - /// let slice = &src[..]; - /// let volatile = Volatile::new(slice); - /// let mut dst = [5, 0, 0]; - /// - /// // Because the slices have to be the same length, - /// // we slice the destination slice from three elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_into_slice(&mut dst[1..]); - /// - /// assert_eq!(src, [1, 2]); - /// assert_eq!(dst, [5, 1, 2]); - /// ``` - #[cfg(feature = "unstable")] - pub fn copy_into_slice(&self, dst: &mut [T]) - where - T: Copy, - { - let src = self.reference.deref(); - assert_eq!( - src.len(), - dst.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - dst.as_mut_ptr(), - src.as_ptr(), - src.len(), - ); - } - } - - /// Copies all elements from `src` into `self`, using a volatile memcpy. - /// - /// The length of `src` must be the same as `self`. - /// - /// This method is similar to the `slice::copy_from_slice` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a slice into a volatile slice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2, 3, 4]; - /// let mut dst = [0, 0]; - /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice - /// let slice = &mut dst[..]; - /// let mut volatile = Volatile::new(slice); - /// - /// // Because the slices have to be the same length, - /// // we slice the source slice from four elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_from_slice(&src[2..]); - /// - /// assert_eq!(src, [1, 2, 3, 4]); - /// assert_eq!(dst, [3, 4]); - /// ``` - #[cfg(feature = "unstable")] - pub fn copy_from_slice(&mut self, src: &[T]) - where - T: Copy, - R: DerefMut, - { - let dest = self.reference.deref_mut(); - assert_eq!( - dest.len(), - src.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - dest.as_mut_ptr(), - src.as_ptr(), - dest.len(), - ); - } - } - - /// Copies elements from one part of the slice to another part of itself, using a - /// volatile `memmove`. - /// - /// `src` is the range within `self` to copy from. `dest` is the starting index of the - /// range within `self` to copy to, which will have the same length as `src`. The two ranges - /// may overlap. The ends of the two ranges must be less than or equal to `self.len()`. - /// - /// This method is similar to the `slice::copy_within` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if either range exceeds the end of the slice, or if the end - /// of `src` is before the start. - /// - /// ## Examples - /// - /// Copying four bytes within a slice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut byte_array = *b"Hello, World!"; - /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = Volatile::new(slice); - /// - /// volatile.copy_within(1..5, 8); - /// - /// assert_eq!(&byte_array, b"Hello, Wello!"); - #[cfg(feature = "unstable")] - pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) - where - T: Copy, - R: DerefMut, - { - let slice = self.reference.deref_mut(); - // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 - let Range { - start: src_start, - end: src_end, - } = range(src, ..slice.len()); - let count = src_end - src_start; - assert!(dest <= slice.len() - count, "dest is out of bounds"); - // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, - // as have those for `ptr::add`. - unsafe { - intrinsics::volatile_copy_memory( - slice.as_mut_ptr().add(dest), - slice.as_ptr().add(src_start), - count, - ); - } - } -} - -/// Methods for volatile byte slices -impl Volatile -where - R: Deref, -{ - /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. - /// - /// This method is similar to the `slice::fill` method of the standard library, with the - /// difference that this method performs a volatile write operation. Another difference - /// is that this method is only available for byte slices (not general `&mut [T]` slices) - /// because there currently isn't a instrinsic function that allows non-`u8` values. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut buf = Volatile::new(vec![0; 10]); - /// buf.fill(1); - /// assert_eq!(buf.extract_inner(), vec![1; 10]); - /// ``` - #[cfg(feature = "unstable")] - pub fn fill(&mut self, value: u8) - where - R: DerefMut, - { - let dest = self.reference.deref_mut(); - unsafe { - intrinsics::volatile_set_memory(dest.as_mut_ptr(), value, dest.len()); - } - } -} - -/// Methods for converting arrays to slices -impl Volatile -where - R: Deref, -{ - /// Converts an array reference to a shared slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Reading a subslice from a volatile array reference using `index`: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2, 3, 4]; - /// let volatile = Volatile::new(&src); - /// - /// // convert the `Volatile<&[i32; 4]>` array reference to a `Volatile<&[i32]>` slice - /// let volatile_slice = volatile.as_slice(); - /// // we can now use the slice methods - /// let subslice = volatile_slice.index(2..); - /// - /// assert_eq!(subslice.index(0).read(), 3); - /// assert_eq!(subslice.index(1).read(), 4); - /// ``` - pub fn as_slice(&self) -> Volatile<&[T], A> { - self.map(|array| &array[..]) - } - - /// Converts a mutable array reference to a mutable slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Writing to an index of a mutable array reference: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut dst = [0, 0]; - /// let mut volatile = Volatile::new(&mut dst); - /// - /// // convert the `Volatile<&mut [i32; 2]>` array reference to a `Volatile<&mut [i32]>` slice - /// let mut volatile_slice = volatile.as_mut_slice(); - /// // we can now use the slice methods - /// volatile_slice.index_mut(1).write(1); - /// - /// assert_eq!(dst, [0, 1]); - /// ``` - pub fn as_mut_slice(&mut self) -> Volatile<&mut [T], A> - where - R: DerefMut, - { - self.map_mut(|array| &mut array[..]) - } -} - -/// Methods for restricting access. -impl Volatile { - /// Restricts access permissions to read-only. - /// - /// ## Example - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value: i16 = -4; - /// let mut volatile = Volatile::new(&mut value); - /// - /// let read_only = volatile.read_only(); - /// assert_eq!(read_only.read(), -4); - /// // read_only.write(10); // compile-time error - /// ``` - pub fn read_only(self) -> Volatile { - Volatile { - reference: self.reference, - access: PhantomData, - } - } - - /// Restricts access permissions to write-only. - /// - /// ## Example - /// - /// Creating a write-only reference to a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2).write_only(); - /// field_2.write(14); - /// // field_2.read(); // compile-time error - /// ``` - pub fn write_only(self) -> Volatile { - Volatile { - reference: self.reference, - access: PhantomData, - } - } -} - -impl fmt::Debug for Volatile -where - R: Deref, - T: Copy + fmt::Debug, - A: Readable, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&self.read()).finish() - } -} - -impl fmt::Debug for Volatile -where - R: Deref, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&"[write-only]").finish() - } -} - -#[cfg(test)] -mod tests { - use super::Volatile; - - #[test] - fn test_read() { - let val = 42; - assert_eq!(Volatile::new(&val).read(), 42); - } - - #[test] - fn test_write() { - let mut val = 50; - let mut volatile = Volatile::new(&mut val); - volatile.write(50); - assert_eq!(val, 50); - } - - #[test] - fn test_update() { - let mut val = 42; - let mut volatile = Volatile::new(&mut val); - volatile.update(|v| *v += 1); - assert_eq!(val, 43); - } - - #[test] - fn test_slice() { - let mut val = [1, 2, 3]; - let mut volatile = Volatile::new(&mut val[..]); - volatile.index_mut(0).update(|v| *v += 1); - assert_eq!(val, [2, 2, 3]); - } - - #[test] - fn test_struct() { - struct S { - field_1: u32, - field_2: bool, - } - - let mut val = S { - field_1: 60, - field_2: true, - }; - let mut volatile = Volatile::new(&mut val); - volatile.map_mut(|s| &mut s.field_1).update(|v| *v += 1); - let mut field_2 = volatile.map_mut(|s| &mut s.field_2); - assert!(field_2.read()); - field_2.write(false); - assert_eq!(volatile.map(|s| &s.field_1).read(), 61); - assert_eq!(volatile.map(|s| &s.field_2).read(), false); - } - - #[cfg(feature = "unstable")] - #[test] - fn test_chunks() { - let mut val = [1, 2, 3, 4, 5, 6]; - let mut volatile = Volatile::new(&mut val[..]); - let mut chunks = volatile.map_mut(|s| s.as_chunks_mut().0); - chunks.index_mut(1).write([10, 11, 12]); - assert_eq!(chunks.index(0).read(), [1, 2, 3]); - assert_eq!(chunks.index(1).read(), [10, 11, 12]); - } -} +mod volatile_ptr; +mod volatile_ref; diff --git a/src/volatile_ptr/macros.rs b/src/volatile_ptr/macros.rs new file mode 100644 index 0000000..6259b38 --- /dev/null +++ b/src/volatile_ptr/macros.rs @@ -0,0 +1,49 @@ +/// TODO +/// +/// ## Examples +/// +/// Accessing a struct field: +/// +/// ``` +/// use volatile::{VolatilePtr, map_field}; +/// use core::ptr::NonNull; +/// +/// struct Example { field_1: u32, field_2: u8, } +/// let mut value = Example { field_1: 15, field_2: 255 }; +/// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; +/// +/// // construct a volatile reference to a field +/// let field_2 = map_field!(volatile.field_2); +/// assert_eq!(field_2.read(), 255); +/// ``` +/// +/// Creating `VolatilePtr`s to unaligned field in packed structs is not allowed: +/// ```compile_fail +/// use volatile::{VolatilePtr, map_field}; +/// use core::ptr::NonNull; +/// +/// #[repr(packed)] +/// struct Example { field_1: u8, field_2: usize, } +/// let mut value = Example { field_1: 15, field_2: 255 }; +/// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; +/// +/// // Constructing a volatile reference to an unaligned field doesn't compile. +/// let field_2 = map_field!(volatile.field_2); +/// ``` +#[macro_export] +macro_rules! map_field { + ($volatile:ident.$place:ident) => {{ + // Simulate creating a reference to the field. This is done to make + // sure that the field is not potentially unaligned. The body of the + // if statement will never be executed, so it can never cause any UB. + if false { + let _ref_to_field = &(unsafe { &*$volatile.as_raw_ptr().as_ptr() }).$place; + } + + unsafe { + $volatile.map(|ptr| { + core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() + }) + } + }}; +} diff --git a/src/volatile_ptr/mod.rs b/src/volatile_ptr/mod.rs new file mode 100644 index 0000000..32e8012 --- /dev/null +++ b/src/volatile_ptr/mod.rs @@ -0,0 +1,58 @@ +use core::{fmt, marker::PhantomData, ptr::NonNull}; + +use crate::access::ReadWrite; + +mod macros; +mod operations; + +#[cfg(test)] +mod tests; +#[cfg(feature = "unstable")] +mod unstable; +#[cfg(feature = "very_unstable")] +mod very_unstable; + +/// Wraps a pointer to make accesses to the referenced value volatile. +/// +/// Allows volatile reads and writes on the referenced value. The referenced value needs to +/// be `Copy` for reading and writing, as volatile reads and writes take and return copies +/// of the value. +/// +/// Since not all volatile resources (e.g. memory mapped device registers) are both readable +/// and writable, this type supports limiting the allowed access types through an optional second +/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults +/// to `ReadWrite`, which allows all operations. +/// +/// The size of this struct is the same as the size of the contained reference. +#[repr(transparent)] +pub struct VolatilePtr<'a, T, A = ReadWrite> +where + T: ?Sized, +{ + pointer: NonNull, + reference: PhantomData<&'a T>, + access: PhantomData, +} + +impl<'a, T, A> Copy for VolatilePtr<'a, T, A> where T: ?Sized {} + +impl Clone for VolatilePtr<'_, T, A> +where + T: ?Sized, +{ + fn clone(&self) -> Self { + *self + } +} + +impl fmt::Debug for VolatilePtr<'_, T, A> +where + T: Copy + fmt::Debug + ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("VolatilePtr") + .field("pointer", &self.pointer) + .field("access", &self.access) + .finish() + } +} diff --git a/src/volatile_ptr/operations.rs b/src/volatile_ptr/operations.rs new file mode 100644 index 0000000..6539982 --- /dev/null +++ b/src/volatile_ptr/operations.rs @@ -0,0 +1,263 @@ +use core::{ + marker::PhantomData, + ptr::{self, NonNull}, +}; + +use crate::{ + access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}, + VolatilePtr, +}; + +/// Constructor functions. +/// +/// These functions construct new `VolatilePtr` values. While the `new` +/// function creates a `VolatilePtr` instance with unrestricted access, there +/// are also functions for creating read-only or write-only instances. +impl<'a, T> VolatilePtr<'a, T> +where + T: ?Sized, +{ + /// Turns the given pointer into a `VolatilePtr`. + /// + /// ## Safety + /// + /// - The given pointer must be valid. + /// - No other thread must have access to the given pointer. This must remain true + /// for the whole lifetime of the `VolatilePtr`. + pub unsafe fn new(pointer: NonNull) -> VolatilePtr<'a, T, ReadWrite> { + unsafe { VolatilePtr::new_restricted(ReadWrite, pointer) } + } + + /// Creates a new read-only volatile pointer from the given raw pointer. + /// + /// ## Safety + /// + /// The requirements for [`Self::new`] apply to this function too. + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { Self::new_restricted(ReadOnly, pointer) } + } + + /// Creates a new volatile pointer with restricted access from the given raw pointer. + /// + /// ## Safety + /// + /// The requirements for [`Self::new`] apply to this function too. + pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> + where + A: Access, + { + let _ = access; + unsafe { Self::new_generic(pointer) } + } + + pub(super) const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { + VolatilePtr { + pointer, + reference: PhantomData, + access: PhantomData, + } + } +} + +impl<'a, T, A> VolatilePtr<'a, T, A> +where + T: ?Sized, +{ + /// Performs a volatile read of the contained value. + /// + /// Returns 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 types of + /// the standard/`core` library. + /// + /// ## Examples + /// + /// ```rust + /// use volatile::{VolatilePtr, access}; + /// use core::ptr::NonNull; + /// + /// let value = 42; + /// let pointer = unsafe { + /// VolatilePtr::new_restricted(access::ReadOnly, NonNull::from(&value)) + /// }; + /// assert_eq!(pointer.read(), 42); + /// ``` + pub fn read(self) -> T + where + T: Copy, + A: Readable, + { + unsafe { ptr::read_volatile(self.pointer.as_ptr()) } + } + + /// Performs a volatile write, setting the contained value to the given `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 types of the standard/`core` library. + /// + /// ## Example + /// + /// ```rust + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// volatile.write(50); + /// + /// assert_eq!(volatile.read(), 50); + /// ``` + pub fn write(self, value: T) + where + T: Copy, + A: Writable, + { + unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; + } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// Performs a volatile read of the contained value, passes it to the + /// function `f`, and then performs a volatile write of the returned value back to + /// the target. + /// + /// ```rust + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// volatile.update(|val| val + 1); + /// + /// assert_eq!(volatile.read(), 43); + /// ``` + pub fn update(self, f: F) + where + T: Copy, + A: Readable + Writable, + F: FnOnce(T) -> T, + { + let new = f(self.read()); + self.write(new); + } + + /// Extracts the wrapped raw pointer. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// volatile.write(50); + /// let unwrapped: *mut i32 = volatile.as_raw_ptr().as_ptr(); + /// + /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! + /// ``` + pub fn as_raw_ptr(self) -> NonNull { + self.pointer + } + + /// Constructs a new `VolatilePtr` by mapping the wrapped pointer. + /// + /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or + /// a struct field. For struct field access, there is also the safe + /// [`map_field`][crate::map_field] macro that wraps this function. + /// + /// ## Examples + /// + /// Accessing a struct field: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// + /// // construct a volatile pointer to a field + /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; + /// assert_eq!(field_2.read(), 255); + /// ``` + /// + /// Don't misuse this method to do a non-volatile read of the referenced value: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 5; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// + /// // DON'T DO THIS: + /// let mut readout = 0; + /// unsafe { volatile.map(|value| { + /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs + /// value + /// })}; + /// ``` + /// + /// ## Safety + /// + /// The pointer returned by `f` must satisfy the requirements of [`Self::new`]. + pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A> + where + F: FnOnce(NonNull) -> NonNull, + A: Access, + U: ?Sized, + { + unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } + } +} + +/// Methods for restricting access. +impl<'a, T> VolatilePtr<'a, T, ReadWrite> +where + T: ?Sized, +{ + /// Restricts access permissions to read-only. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value: i16 = -4; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// + /// let read_only = volatile.read_only(); + /// assert_eq!(read_only.read(), -4); + /// // read_only.write(10); // compile-time error + /// ``` + pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } + } + + /// Restricts access permissions to write-only. + /// + /// ## Example + /// + /// Creating a write-only pointer to a struct field: + /// + /// ``` + /// use volatile::{VolatilePtr, map_field}; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// + /// // construct a volatile write-only pointer to `field_2` + /// let mut field_2 = map_field!(volatile.field_2).write_only(); + /// field_2.write(14); + /// // field_2.read(); // compile-time error + /// ``` + pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { + unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } + } +} diff --git a/src/volatile_ptr/tests.rs b/src/volatile_ptr/tests.rs new file mode 100644 index 0000000..479bec3 --- /dev/null +++ b/src/volatile_ptr/tests.rs @@ -0,0 +1,179 @@ +use crate::{ + access::{ReadOnly, ReadWrite, WriteOnly}, + map_field, VolatilePtr, +}; +use core::ptr::NonNull; + +#[test] +fn test_read() { + let val = 42; + assert_eq!( + unsafe { VolatilePtr::new_read_only(NonNull::from(&val)) }.read(), + 42 + ); +} + +#[test] +fn test_write() { + let mut val = 50; + let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + volatile.write(50); + assert_eq!(val, 50); +} + +#[test] +fn test_update() { + let mut val = 42; + let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + volatile.update(|v| v + 1); + assert_eq!(val, 43); +} + +#[test] +fn test_access() { + let mut val: i64 = 42; + + // ReadWrite + assert_eq!( + unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.read(), + 42 + ); + unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.write(50); + assert_eq!(val, 50); + unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.update(|i| i + 1); + assert_eq!(val, 51); + + // ReadOnly and WriteOnly + assert_eq!( + unsafe { VolatilePtr::new_restricted(ReadOnly, NonNull::from(&mut val)) }.read(), + 51 + ); + unsafe { VolatilePtr::new_restricted(WriteOnly, NonNull::from(&mut val)) }.write(12); + assert_eq!(val, 12); +} + +#[test] +fn test_struct() { + #[derive(Debug, PartialEq)] + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + unsafe { + volatile.map(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) + } + .update(|v| v + 1); + let field_2 = unsafe { + volatile.map(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_2)).unwrap()) + }; + assert!(field_2.read()); + field_2.write(false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); +} + +#[test] +fn test_struct_macro() { + #[derive(Debug, PartialEq)] + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + let field_1 = map_field!(volatile.field_1); + field_1.update(|v| v + 1); + let field_2 = map_field!(volatile.field_2); + assert!(field_2.read()); + field_2.write(false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); +} + +#[cfg(feature = "unstable")] +#[test] +fn test_slice() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + volatile.index(0).update(|v| v + 1); + + let mut dst = [0; 3]; + volatile.copy_into_slice(&mut dst); + assert_eq!(dst, [2, 2, 3]); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_1() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + volatile.index(3); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_2() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + #[allow(clippy::reversed_empty_ranges)] + volatile.index(2..1); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_3() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + volatile.index(4..); // `3..` is is still ok (see next test) +} + +#[cfg(feature = "unstable")] +#[test] +fn test_bounds_check_4() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + assert_eq!(volatile.index(3..).len(), 0); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_5() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + volatile.index(..4); +} + +#[cfg(feature = "unstable")] +#[test] +fn test_chunks() { + let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + let chunks = volatile.as_chunks().0; + chunks.index(1).write([10, 11, 12]); + assert_eq!(chunks.index(0).read(), [1, 2, 3]); + assert_eq!(chunks.index(1).read(), [10, 11, 12]); +} diff --git a/src/volatile_ptr/unstable.rs b/src/volatile_ptr/unstable.rs new file mode 100644 index 0000000..9009bc9 --- /dev/null +++ b/src/volatile_ptr/unstable.rs @@ -0,0 +1,409 @@ +use core::{ + intrinsics, + ops::{Range, RangeBounds}, + ptr::{self, NonNull}, + slice::{range, SliceIndex}, +}; + +use crate::{ + access::{Access, Readable, Writable}, + VolatilePtr, +}; + +impl<'a, T, A> VolatilePtr<'a, [T], A> { + /// Returns the length of the slice. + pub fn len(self) -> usize { + self.pointer.len() + } + + /// Returns whether the slice is empty. + pub fn is_empty(self) -> bool { + self.pointer.len() == 0 + } + + /// Applies the index operation on the wrapped slice. + /// + /// Returns a shared `Volatile` reference to the resulting subslice. + /// + /// This is a convenience method for the `map(|slice| slice.index(index))` operation, so it + /// has the same behavior as the indexing operation on slice (e.g. panic if index is + /// out-of-bounds). + /// + /// ## Examples + /// + /// Accessing a single slice element: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let array = [1, 2, 3]; + /// let slice = &array[..]; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; + /// assert_eq!(volatile.index(1).read(), 2); + /// ``` + /// + /// Accessing a subslice: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let array = [1, 2, 3]; + /// let slice = &array[..]; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; + /// let subslice = volatile.index(1..); + /// assert_eq!(subslice.index(0).read(), 2); + /// ``` + pub fn index(self, index: I) -> VolatilePtr<'a, >::Output, A> + where + I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, + A: Access, + { + bounds_check(self.pointer.len(), index.clone()); + + unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } + } + + /// Returns an iterator over the slice. + pub fn iter(self) -> impl Iterator> + where + A: Access, + { + let ptr = self.as_raw_ptr().as_ptr() as *mut T; + let len = self.len(); + (0..len) + .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) + } + + /// Copies all elements from `self` into `dst`, using a volatile memcpy. + /// + /// The length of `dst` must be the same as `self`. + /// + /// The method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// ## Examples + /// + /// Copying two elements from a volatile slice: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2]; + /// // the `Volatile` type does not work with arrays, so convert `src` to a slice + /// let slice = &src[..]; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; + /// let mut dst = [5, 0, 0]; + /// + /// // Because the slices have to be the same length, + /// // we slice the destination slice from three elements + /// // to two. It will panic if we don't do this. + /// volatile.copy_into_slice(&mut dst[1..]); + /// + /// assert_eq!(src, [1, 2]); + /// assert_eq!(dst, [5, 1, 2]); + /// ``` + pub fn copy_into_slice(self, dst: &mut [T]) + where + T: Copy, + A: Readable, + { + let len = self.pointer.len(); + assert_eq!( + len, + dst.len(), + "destination and source slices have different lengths" + ); + unsafe { + intrinsics::volatile_copy_nonoverlapping_memory( + dst.as_mut_ptr(), + self.pointer.as_mut_ptr(), + len, + ); + } + } + + /// Copies all elements from `src` into `self`, using a volatile memcpy. + /// + /// The length of `src` must be the same as `self`. + /// + /// This method is similar to the `slice::copy_from_slice` method of the standard library. The + /// difference is that this method performs a volatile copy. + /// + /// The method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// ## Examples + /// + /// Copying two elements from a slice into a volatile slice: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2, 3, 4]; + /// let mut dst = [0, 0]; + /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice + /// let slice = &mut dst[..]; + /// let mut volatile = unsafe { VolatilePtr::new(NonNull::from(slice)) }; + /// // Because the slices have to be the same length, + /// // we slice the source slice from four elements + /// // to two. It will panic if we don't do this. + /// volatile.copy_from_slice(&src[2..]); + /// + /// assert_eq!(src, [1, 2, 3, 4]); + /// assert_eq!(dst, [3, 4]); + /// ``` + pub fn copy_from_slice(self, src: &[T]) + where + T: Copy, + A: Writable, + { + let len = self.pointer.len(); + assert_eq!( + len, + src.len(), + "destination and source slices have different lengths" + ); + unsafe { + intrinsics::volatile_copy_nonoverlapping_memory( + self.pointer.as_mut_ptr(), + src.as_ptr(), + len, + ); + } + } + + /// Copies elements from one part of the slice to another part of itself, using a + /// volatile `memmove`. + /// + /// `src` is the range within `self` to copy from. `dest` is the starting index of the + /// range within `self` to copy to, which will have the same length as `src`. The two ranges + /// may overlap. The ends of the two ranges must be less than or equal to `self.len()`. + /// + /// This method is similar to the `slice::copy_within` method of the standard library. The + /// difference is that this method performs a volatile copy. + /// + /// This method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if either range exceeds the end of the slice, or if the end + /// of `src` is before the start. + /// + /// ## Examples + /// + /// Copying four bytes within a slice: + /// + /// ``` + /// extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut byte_array = *b"Hello, World!"; + /// let mut slice: &mut [u8] = &mut byte_array[..]; + /// let mut volatile = unsafe { VolatilePtr::new(NonNull::from(slice)) }; + /// volatile.copy_within(1..5, 8); + /// + /// assert_eq!(&byte_array, b"Hello, Wello!"); + pub fn copy_within(self, src: impl RangeBounds, dest: usize) + where + T: Copy, + A: Readable + Writable, + { + let len = self.pointer.len(); + // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 + let Range { + start: src_start, + end: src_end, + } = range(src, ..len); + let count = src_end - src_start; + assert!(dest <= len - count, "dest is out of bounds"); + // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, + // as have those for `ptr::add`. + unsafe { + intrinsics::volatile_copy_memory( + self.pointer.as_mut_ptr().add(dest), + self.pointer.as_mut_ptr().add(src_start), + count, + ); + } + } + + /// Divides one slice into two at an index. + /// + /// The first will contain all indices from `[0, mid)` (excluding + /// the index `mid` itself) and the second will contain all + /// indices from `[mid, len)` (excluding the index `len` itself). + /// + /// # Panics + /// + /// Panics if `mid > len`. + /// + pub fn split_at(self, mid: usize) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) + where + A: Access, + { + assert!(mid <= self.pointer.len()); + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `from_raw_parts_mut`. + unsafe { self.split_at_unchecked(mid) } + } + + unsafe fn split_at_unchecked( + self, + mid: usize, + ) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) + where + A: Access, + { + // SAFETY: Caller has to check that `0 <= mid <= self.len()` + unsafe { + ( + VolatilePtr::new_generic((self.pointer).get_unchecked_mut(..mid)), + VolatilePtr::new_generic((self.pointer).get_unchecked_mut(mid..)), + ) + } + } + + /// Splits the slice into a slice of `N`-element arrays, + /// starting at the beginning of the slice, + /// and a remainder slice with length strictly less than `N`. + /// + /// # Panics + /// + /// Panics if `N` is 0. + #[allow(clippy::type_complexity)] + pub fn as_chunks( + self, + ) -> (VolatilePtr<'a, [[T; N]], A>, VolatilePtr<'a, [T], A>) + where + A: Access, + { + assert_ne!(N, 0); + let len = self.pointer.len() / N; + let (multiple_of_n, remainder) = self.split_at(len * N); + // SAFETY: We already panicked for zero, and ensured by construction + // that the length of the subslice is a multiple of N. + let array_slice = unsafe { multiple_of_n.as_chunks_unchecked() }; + (array_slice, remainder) + } + + /// Splits the slice into a slice of `N`-element arrays, + /// assuming that there's no remainder. + /// + /// # Safety + /// + /// This may only be called when + /// - The slice splits exactly into `N`-element chunks (aka `self.len() % N == 0`). + /// - `N != 0`. + pub unsafe fn as_chunks_unchecked(self) -> VolatilePtr<'a, [[T; N]], A> + where + A: Access, + { + debug_assert_ne!(N, 0); + debug_assert_eq!(self.pointer.len() % N, 0); + let new_len = + // SAFETY: Our precondition is exactly what's needed to call this + unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; + // SAFETY: We cast a slice of `new_len * N` elements into + // a slice of `new_len` many `N` elements chunks. + let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( + self.pointer.as_mut_ptr().cast(), + new_len, + )) + .unwrap(); + unsafe { VolatilePtr::new_generic(pointer) } + } +} + +/// Methods for volatile byte slices +impl VolatilePtr<'_, [u8], A> { + /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. + /// + /// This method is similar to the `slice::fill` method of the standard library, with the + /// difference that this method performs a volatile write operation. Another difference + /// is that this method is only available for byte slices (not general `&mut [T]` slices) + /// because there currently isn't a instrinsic function that allows non-`u8` values. + /// + /// This method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Example + /// + /// ```rust + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut vec = vec![0; 10]; + /// let mut buf = unsafe { VolatilePtr::new(NonNull::from(vec.as_mut_slice())) }; + /// buf.fill(1); + /// assert_eq!(unsafe { buf.as_raw_ptr().as_mut() }, &mut vec![1; 10]); + /// ``` + pub fn fill(self, value: u8) + where + A: Writable, + { + unsafe { + intrinsics::volatile_set_memory(self.pointer.as_mut_ptr(), value, self.pointer.len()); + } + } +} + +/// Methods for converting arrays to slices +/// +/// These methods are only available with the `unstable` feature enabled (requires a nightly +/// Rust compiler). +impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { + /// Converts an array pointer to a slice pointer. + /// + /// This makes it possible to use the methods defined on slices. + /// + /// ## Example + /// + /// Copying two elements from a volatile array reference using `copy_into_slice`: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2]; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(&src)) }; + /// let mut dst = [0, 0]; + /// + /// // convert the `Volatile<&[i32; 2]>` array reference to a `Volatile<&[i32]>` slice + /// let volatile_slice = volatile.as_slice(); + /// // we can now use the slice methods + /// volatile_slice.copy_into_slice(&mut dst); + /// + /// assert_eq!(dst, [1, 2]); + /// ``` + pub fn as_slice(self) -> VolatilePtr<'a, [T], A> + where + A: Access, + { + unsafe { + self.map(|array| { + NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() + }) + } + } +} + +fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { + const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; + + let bound_check_slice = &MAX_ARRAY[..len]; + let _ = &bound_check_slice[index]; +} diff --git a/src/volatile_ptr/very_unstable.rs b/src/volatile_ptr/very_unstable.rs new file mode 100644 index 0000000..3935928 --- /dev/null +++ b/src/volatile_ptr/very_unstable.rs @@ -0,0 +1,49 @@ +use core::ptr::NonNull; + +use crate::VolatilePtr; + +impl<'a, T, A> VolatilePtr<'a, T, A> +where + T: ?Sized, +{ + /// Compile-time evaluable variant of [`Self::map`]. + /// + /// This function is a copy of [`Self::map`] that uses unstable compiler functions + /// to be callable from `const` contexts. + /// + /// ## Safety + /// + /// The safety requirements of [`Self::map`] apply to this method too. + pub const unsafe fn map_const(self, f: F) -> VolatilePtr<'a, U, A> + where + F: ~const FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtr::new_generic(f(self.pointer)) } + } +} + +/// Methods for volatile slices +#[cfg(feature = "unstable")] +impl<'a, T, A> VolatilePtr<'a, [T], A> { + /// Compile-time evaluable variant of [`Self::index`]. + /// + /// This function is a copy of [`Self::index`] that uses unstable compiler functions + /// to be callable from `const` contexts. + pub const fn index_const(self, index: usize) -> VolatilePtr<'a, T, A> { + assert!(index < self.pointer.len(), "index out of bounds"); + + struct Mapper { + index: usize, + } + impl const FnOnce<(NonNull<[T]>,)> for Mapper { + type Output = NonNull; + + extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { + unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } + } + } + + unsafe { self.map_const(Mapper { index }) } + } +} diff --git a/src/volatile_ref.rs b/src/volatile_ref.rs new file mode 100644 index 0000000..0dbe697 --- /dev/null +++ b/src/volatile_ref.rs @@ -0,0 +1,250 @@ +use crate::{ + access::{Access, Copyable, ReadOnly, ReadWrite, WriteOnly}, + volatile_ptr::VolatilePtr, +}; +use core::{fmt, marker::PhantomData, ptr::NonNull}; + +/// Volatile pointer type that respects Rust's aliasing rules. +/// +/// This pointer type behaves similar to Rust's reference types: +/// +/// - it requires exclusive `&mut self` access for mutability +/// - only read-only types implement [`Clone`] and [`Copy`] +/// - [`Send`] and [`Sync`] are implemented if `T: Sync` +/// +/// To perform volatile operations on `VolatileRef` types, use the [`as_ptr`][Self::as_ptr] +/// or [`as_mut_ptr`](Self::as_mut_ptr) methods to create a temporary +/// [`VolatilePtr`][crate::VolatilePtr] instance. +/// +/// Since not all volatile resources (e.g. memory mapped device registers) are both readable +/// and writable, this type supports limiting the allowed access types through an optional second +/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults +/// to `ReadWrite`, which allows all operations. +/// +/// The size of this struct is the same as the size of the contained reference. +#[repr(transparent)] +pub struct VolatileRef<'a, T, A = ReadWrite> +where + T: ?Sized, +{ + pointer: NonNull, + reference: PhantomData<&'a T>, + access: PhantomData, +} + +/// Constructor functions. +/// +/// These functions construct new `VolatileRef` values. While the `new` +/// function creates a `VolatileRef` instance with unrestricted access, there +/// are also functions for creating read-only or write-only instances. +impl<'a, T> VolatileRef<'a, T> +where + T: ?Sized, +{ + /// Turns the given pointer into a `VolatileRef`. + /// + /// ## Safety + /// + /// - The pointer must be properly aligned. + /// - It must be “dereferenceable” in the sense defined in the [`core::ptr`] documentation. + /// - The pointer must point to an initialized instance of T. + /// - You must enforce Rust’s aliasing rules, since the returned lifetime 'a is arbitrarily + /// chosen and does not necessarily reflect the actual lifetime of the data. In particular, + /// while this `VolatileRef` exists, the memory the pointer points to must not get accessed + /// (_read or written_) through any other pointer. + pub unsafe fn new(pointer: NonNull) -> Self { + unsafe { VolatileRef::new_restricted(ReadWrite, pointer) } + } + + /// Turns the given pointer into a read-only `VolatileRef`. + /// + /// ## Safety + /// + /// - The pointer must be properly aligned. + /// - It must be “dereferenceable” in the sense defined in the [`core::ptr`] documentation. + /// - The pointer must point to an initialized instance of T. + /// - You must enforce Rust’s aliasing rules, since the returned lifetime 'a is arbitrarily + /// chosen and does not necessarily reflect the actual lifetime of the data. In particular, + /// while this `VolatileRef` exists, the memory the pointer points to _must not get mutated_. + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatileRef<'a, T, ReadOnly> { + unsafe { Self::new_restricted(ReadOnly, pointer) } + } + + /// Turns the given pointer into a `VolatileRef` instance with the given access. + /// + /// ## Safety + /// + /// - The pointer must be properly aligned. + /// - It must be “dereferenceable” in the sense defined in the [`core::ptr`] documentation. + /// - The pointer must point to an initialized instance of T. + /// - You must enforce Rust’s aliasing rules, since the returned lifetime 'a is arbitrarily + /// chosen and does not necessarily reflect the actual lifetime of the data. In particular, + /// while this `VolatileRef` exists, the memory the pointer points to _must not get mutated_. + /// If the given `access` parameter allows write access, the pointer _must not get read + /// either_ while this `VolatileRef` exists. + pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatileRef<'a, T, A> + where + A: Access, + { + let _ = access; + unsafe { Self::new_generic(pointer) } + } + + /// Creates a `VolatileRef` from the given shared reference. + /// + /// **Note:** This function is only intended for testing, not for accessing real volatile + /// data. The reason is that the `&mut T` argument is considered _dereferenceable_ by Rust, + /// so the compiler is allowed to insert non-volatile reads. This might lead to undesired + /// (or even undefined?) behavior when accessing volatile data. So to be safe, only create + /// raw pointers to volatile data and use the [`Self::new`] constructor instead. + pub fn from_ref(reference: &'a T) -> VolatileRef<'a, T, ReadOnly> + where + T: 'a, + { + unsafe { VolatileRef::new_restricted(ReadOnly, reference.into()) } + } + + /// Creates a `VolatileRef` from the given mutable reference. + /// + /// **Note:** This function is only intended for testing, not for accessing real volatile + /// data. The reason is that the `&mut T` argument is considered _dereferenceable_ by Rust, + /// so the compiler is allowed to insert non-volatile reads. This might lead to undesired + /// (or even undefined?) behavior when accessing volatile data. So to be safe, only create + /// raw pointers to volatile data and use the [`Self::new`] constructor instead. + pub fn from_mut_ref(reference: &'a mut T) -> Self + where + T: 'a, + { + unsafe { VolatileRef::new(reference.into()) } + } + + const unsafe fn new_generic(pointer: NonNull) -> VolatileRef<'a, T, A> { + VolatileRef { + pointer, + reference: PhantomData, + access: PhantomData, + } + } +} + +impl<'a, T, A> VolatileRef<'a, T, A> +where + T: ?Sized, +{ + /// Borrows this `VolatileRef` as a read-only [`VolatilePtr`]. + /// + /// Use this method to do (partial) volatile reads of the referenced data. + pub fn as_ptr(&self) -> VolatilePtr<'_, T, A::RestrictShared> + where + A: Access, + { + unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } + } + + /// Borrows this `VolatileRef` as a mutable [`VolatilePtr`]. + /// + /// Use this method to do (partial) volatile reads or writes of the referenced data. + pub fn as_mut_ptr(&mut self) -> VolatilePtr<'_, T, A> + where + A: Access, + { + unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } + } + + /// Converts this `VolatileRef` into a [`VolatilePtr`] with full access without shortening + /// the lifetime. + /// + /// Use this method when you need a [`VolatilePtr`] instance that lives for the full + /// lifetime `'a`. + /// + /// This method consumes the `VolatileRef`. + pub fn into_ptr(self) -> VolatilePtr<'a, T, A> + where + A: Access, + { + unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } + } +} + +/// Methods for restricting access. +impl<'a, T> VolatileRef<'a, T, ReadWrite> +where + T: ?Sized, +{ + /// Restricts access permissions to read-only. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatileRef; + /// use core::ptr::NonNull; + /// + /// let mut value: i16 = -4; + /// let mut volatile = VolatileRef::from_mut_ref(&mut value); + /// + /// let read_only = volatile.read_only(); + /// assert_eq!(read_only.as_ptr().read(), -4); + /// // read_only.as_ptr().write(10); // compile-time error + /// ``` + pub fn read_only(self) -> VolatileRef<'a, T, ReadOnly> { + unsafe { VolatileRef::new_restricted(ReadOnly, self.pointer) } + } + + /// Restricts access permissions to write-only. + /// + /// ## Example + /// + /// Creating a write-only reference to a struct field: + /// + /// ``` + /// use volatile::{VolatileRef}; + /// use core::ptr::NonNull; + /// + /// #[derive(Clone, Copy)] + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = VolatileRef::from_mut_ref(&mut value); + /// + /// let write_only = volatile.write_only(); + /// // write_only.as_ptr().read(); // compile-time error + /// ``` + pub fn write_only(self) -> VolatileRef<'a, T, WriteOnly> { + unsafe { VolatileRef::new_restricted(WriteOnly, self.pointer) } + } +} + +impl<'a, T, A> Clone for VolatileRef<'a, T, A> +where + T: ?Sized, + A: Access + Copyable, +{ + fn clone(&self) -> Self { + Self { + pointer: self.pointer, + reference: self.reference, + access: self.access, + } + } +} + +impl<'a, T, A> Copy for VolatileRef<'a, T, A> +where + T: ?Sized, + A: Access + Copyable, +{ +} + +unsafe impl Send for VolatileRef<'_, T, A> where T: Sync {} +unsafe impl Sync for VolatileRef<'_, T, A> where T: Sync {} + +impl fmt::Debug for VolatileRef<'_, T, A> +where + T: Copy + fmt::Debug + ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("VolatileRef") + .field("pointer", &self.pointer) + .field("access", &self.access) + .finish() + } +}