forked from rust-osdev/volatile
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvolatile_ref.rs
348 lines (323 loc) · 11.9 KB
/
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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
use crate::{
access::{Access, Copyable, ReadOnly, ReadWrite, RestrictAccess, WriteOnly},
volatile_ptr::VolatilePtr,
};
use core::{cmp::Ordering, fmt, hash, 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`
///
/// However, trait implementations like [`fmt::Debug`] and [`Eq`] behave like they do on pointer
/// types and don't access the referenced value.
///
/// 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<T>,
reference: PhantomData<&'a T>,
access: PhantomData<A>,
}
/// 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) }
}
/// 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>(pointer: NonNull<T>) -> VolatileRef<'a, T, A> {
VolatileRef {
pointer,
reference: PhantomData,
access: PhantomData,
}
}
}
impl<'a, T, A> VolatileRef<'a, T, A>
where
T: ?Sized,
{
/// Immutably borrows from this `VolatileRef`.
///
/// This method creates a `VolatileRef` tied to the lifetime of the `&VolatileRef` it is created from.
/// This is useful for providing a volatile reference without moving the original `VolatileRef`.
/// In comparison with creating a `&VolatileRef<'a, T>`, this avoids the additional indirection and lifetime.
pub fn borrow(&self) -> VolatileRef<'_, T, A::Restricted>
where
A: RestrictAccess<ReadOnly>,
{
unsafe { VolatileRef::new_restricted(Default::default(), self.pointer) }
}
/// Mutably borrows from this `VolatileRef`.
///
/// This method creates a `VolatileRef` tied to the lifetime of the `&mut VolatileRef` it is created from.
/// This is useful for providing a volatile reference without moving the original `VolatileRef`.
/// In comparison with creating a `&mut VolatileRef<'a, T>`, this avoids the additional indirection and lifetime.
pub fn borrow_mut(&mut self) -> VolatileRef<'_, T, A>
where
A: Access,
{
unsafe { VolatileRef::new_restricted(Default::default(), self.pointer) }
}
/// 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::Restricted>
where
A: RestrictAccess<ReadOnly>,
{
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, A> VolatileRef<'a, T, A>
where
T: ?Sized,
{
/// Restricts access permissions to `A`.
///
/// ## Example
///
/// ```
/// use volatile::access::{ReadOnly, WriteOnly};
/// use volatile::VolatileRef;
///
/// let mut value: i16 = -4;
/// let volatile = VolatileRef::from_mut_ref(&mut value);
///
/// let read_only = volatile.restrict::<ReadOnly>();
/// assert_eq!(read_only.as_ptr().read(), -4);
/// // read_only.as_ptr().write(10); // compile-time error
///
/// let no_access = read_only.restrict::<WriteOnly>();
/// // no_access.read(); // compile-time error
/// // no_access.write(10); // compile-time error
/// ```
pub fn restrict<To>(self) -> VolatileRef<'a, T, A::Restricted>
where
A: RestrictAccess<To>,
{
unsafe { VolatileRef::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;
///
/// let mut value: i16 = -4;
/// let 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> {
self.restrict()
}
/// Restricts access permissions to write-only.
///
/// ## Example
///
/// Creating a write-only reference to a struct field:
///
/// ```
/// use volatile::{VolatileRef};
///
/// #[derive(Clone, Copy)]
/// struct Example { field_1: u32, field_2: u8, }
/// let mut value = Example { field_1: 15, field_2: 255 };
/// let 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> {
self.restrict()
}
}
impl<'a, T, A> Clone for VolatileRef<'a, T, A>
where
T: ?Sized,
A: Access + Copyable,
{
fn clone(&self) -> Self {
*self
}
}
impl<'a, T, A> Copy for VolatileRef<'a, T, A>
where
T: ?Sized,
A: Access + Copyable,
{
}
unsafe impl<T, A> Send for VolatileRef<'_, T, A> where T: Sync + ?Sized {}
unsafe impl<T, A> Sync for VolatileRef<'_, T, A> where T: Sync + ?Sized {}
impl<T, A> fmt::Debug for VolatileRef<'_, T, A>
where
T: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.pointer.as_ptr(), f)
}
}
impl<T, A> fmt::Pointer for VolatileRef<'_, T, A>
where
T: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.pointer.as_ptr(), f)
}
}
impl<T, A> PartialEq for VolatileRef<'_, T, A>
where
T: ?Sized,
{
fn eq(&self, other: &Self) -> bool {
core::ptr::eq(self.pointer.as_ptr(), other.pointer.as_ptr())
}
}
impl<T, A> Eq for VolatileRef<'_, T, A> where T: ?Sized {}
impl<T, A> PartialOrd for VolatileRef<'_, T, A>
where
T: ?Sized,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T, A> Ord for VolatileRef<'_, T, A>
where
T: ?Sized,
{
fn cmp(&self, other: &Self) -> Ordering {
#[allow(ambiguous_wide_pointer_comparisons)]
Ord::cmp(&self.pointer.as_ptr(), &other.pointer.as_ptr())
}
}
impl<T, A> hash::Hash for VolatileRef<'_, T, A>
where
T: ?Sized,
{
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.pointer.as_ptr().hash(state);
}
}