volatile/
volatile_ref.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
use crate::{
    access::{Access, Copyable, ReadOnly, ReadWrite, WriteOnly},
    ops::{Ops, VolatileOps},
    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, O = VolatileOps>
where
    T: ?Sized,
{
    pointer: NonNull<T>,
    reference: PhantomData<&'a T>,
    access: PhantomData<A>,
    ops: PhantomData<O>,
}

/// 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<T>) -> 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<T>) -> 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<A>(access: A, pointer: NonNull<T>) -> VolatileRef<'a, T, A>
    where
        A: Access,
    {
        let _ = access;
        unsafe { Self::new_generic(pointer) }
    }

    #[allow(missing_docs)]
    pub const unsafe fn new_restricted_with_ops<A, O>(
        access: A,
        ops: O,
        pointer: NonNull<T>,
    ) -> VolatileRef<'a, T, A, O>
    where
        A: Access,
        O: Ops,
    {
        let _ = access;
        let _ = ops;
        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<A, O>(pointer: NonNull<T>) -> VolatileRef<'a, T, A, O> {
        VolatileRef {
            pointer,
            reference: PhantomData,
            access: PhantomData,
            ops: PhantomData,
        }
    }
}

impl<'a, T, A, O> VolatileRef<'a, T, A, O>
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, O>
    where
        A: Access,
    {
        unsafe { VolatilePtr::new_generic(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, O>
    where
        A: Access,
    {
        unsafe { VolatilePtr::new_generic(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, O> Clone for VolatileRef<'a, T, A, O>
where
    T: ?Sized,
    A: Access + Copyable,
    O: Ops,
{
    fn clone(&self) -> Self {
        Self {
            pointer: self.pointer,
            reference: self.reference,
            access: self.access,
            ops: self.ops,
        }
    }
}

impl<'a, T, A, O> Copy for VolatileRef<'a, T, A, O>
where
    T: ?Sized,
    A: Access + Copyable,
    O: Ops,
{
}

unsafe impl<T, A, O> Send for VolatileRef<'_, T, A, O> where T: Sync + ?Sized {}
unsafe impl<T, A, O> Sync for VolatileRef<'_, T, A, O> where T: Sync + ?Sized {}

impl<T, A, O> fmt::Debug for VolatileRef<'_, T, A, O>
where
    T: ?Sized,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("VolatileRef")
            .field("pointer", &self.pointer)
            .field("access", &self.access)
            .finish()
    }
}