peakrdl_rust/io.rs
1//! Traits for customizing the register I/O implementation
2use crate::{
3 access::{Read, Write},
4 endian::Endian,
5 reg::{RegInt, Register},
6};
7use core::cell::RefCell;
8use num_traits::{AsPrimitive, Bounded, ConstZero};
9
10/// Raw register I/O trait
11///
12/// Types that implement this trait also automatically implement [`RegisterIO`].
13pub trait RawRegisterIO {
14 /// The error type this register transport returns. For infallible transports
15 /// (e.g., direct volatile pointer accesses), this should be [`core::convert::Infallible`]
16 /// so that the [`Reg`][crate::reg::Reg] type can provide an infallible API in addition
17 /// to the `try_*` API.
18 type Error;
19
20 /// Try to read a primitive integer from memory.
21 ///
22 /// The returned value is in the register's native endianness (not necessarily
23 /// the host's endianness).
24 ///
25 /// # Safety
26 ///
27 /// This method may dereference raw pointer. The caller must ensure the pointer
28 /// is valid and points to a valid memory location.
29 #[allow(clippy::missing_errors_doc)]
30 unsafe fn try_read<T: RegInt>(&self, ptr: *const T) -> Result<T, Self::Error>;
31
32 /// Try to write primitive integer to memory
33 ///
34 /// The provided value is in the register's native endianness (not necessarily
35 /// the host's endianness).
36 ///
37 /// # Safety
38 ///
39 /// This method may dereference a raw pointer. The caller must ensure the pointer
40 /// is valid and points to a valid writeable memory location.
41 #[allow(clippy::missing_errors_doc)]
42 unsafe fn try_write<T: RegInt>(&self, ptr: *mut T, value: T) -> Result<(), Self::Error>;
43}
44
45/// Register I/O
46///
47/// Register accesses are performed through implementers of this trait. This trait's
48/// methods handle details like endianness, multi-word writes, etc.
49///
50/// Most user I/O interfaces should simply implement the [`RawRegisterIO`] trait, since a
51/// blanket implementation exists for all implementers of [`RawRegisterIO`].
52pub trait RegisterIO {
53 type Error;
54
55 /// Read a register value
56 ///
57 /// This method must respect the register's endianness and accesswidth,
58 /// as encoded in the generic [`Register`]'s associated types.
59 ///
60 /// # Safety
61 ///
62 /// This method may dereference a raw pointer. The caller must ensure the pointer
63 /// is valid and points to a valid memory location.
64 #[allow(clippy::missing_errors_doc)]
65 unsafe fn try_read_register<R: Register>(
66 &self,
67 ptr: *const R::Regwidth,
68 ) -> Result<R, Self::Error>
69 where
70 R::Access: Read;
71
72 /// Write a register value
73 ///
74 /// This method must respect the register's endianness and accesswidth,
75 /// as encoded in the generic [`Register`]'s associated types.
76 ///
77 /// # Safety
78 ///
79 /// This method may dereference a raw pointer. The caller must ensure the pointer
80 /// is valid and points to a valid writeable memory location.
81 #[allow(clippy::missing_errors_doc)]
82 unsafe fn try_write_register<R: Register>(
83 &self,
84 ptr: *mut R::Regwidth,
85 value: R,
86 ) -> Result<(), Self::Error>
87 where
88 R::Access: Write;
89}
90
91impl<T> RegisterIO for T
92where
93 T: RawRegisterIO,
94{
95 type Error = T::Error;
96
97 unsafe fn try_read_register<R: Register>(
98 &self,
99 ptr: *const R::Regwidth,
100 ) -> Result<R, Self::Error>
101 where
102 R::Access: Read,
103 {
104 let ptr = ptr.cast::<R::Accesswidth>();
105
106 let accesswidth = 8 * core::mem::size_of::<R::Accesswidth>();
107 let regwidth = 8 * core::mem::size_of::<R::Regwidth>();
108 let num_subwords = regwidth / accesswidth;
109
110 // Fast path: a single-word register is one volatile load. `num_subwords`
111 // is a compile-time constant (derived from `size_of`), so for single-word
112 // registers the multi-word loop below is dropped entirely and the access
113 // folds to a single load at the call site.
114 if num_subwords == 1 {
115 // SAFETY: accesswidth == regwidth here, so this reads exactly the
116 // register's bounds (same guarantee the loop relies on).
117 let subword = unsafe { self.try_read::<R::Accesswidth>(ptr)? };
118 let subword = R::ByteEndian::from_register_endian(subword);
119 // SAFETY: value just read directly from hardware.
120 return unsafe { Ok(R::from_raw(subword.as_())) };
121 }
122
123 // read one subword at a time, starting at the lowest address
124 let raw_value = (0..num_subwords)
125 .map(|i| {
126 // SAFETY: SystemRDL guarantees accesswidth <= regwidth, so we won't
127 // read outside the bounds of the original pointer.
128 unsafe { (i, self.try_read(ptr.wrapping_add(i))) }
129 })
130 .try_fold(R::Regwidth::ZERO, |reg, (i, subword)| {
131 let significance = R::WordEndian::address_order_to_significance(i, num_subwords);
132 let subword = R::ByteEndian::from_register_endian(subword?);
133 Ok(reg | (subword.as_() << (significance * accesswidth)))
134 })?;
135 // SAFETY: The value was just read directly from hardware, and should
136 // therefore be a valid register value.
137 unsafe { Ok(R::from_raw(raw_value)) }
138 }
139
140 unsafe fn try_write_register<R: Register>(
141 &self,
142 ptr: *mut R::Regwidth,
143 value: R,
144 ) -> Result<(), Self::Error>
145 where
146 R::Access: Write,
147 {
148 let ptr = ptr.cast::<R::Accesswidth>();
149 let value = value.to_raw();
150
151 let accesswidth = 8 * core::mem::size_of::<R::Accesswidth>();
152 let regwidth = 8 * core::mem::size_of::<R::Regwidth>();
153 let num_subwords = regwidth / accesswidth;
154 let mask = R::Accesswidth::max_value().as_();
155
156 // Fast path: a single-word register is one volatile store. `num_subwords`
157 // is a compile-time constant, so for single-word registers the loop below
158 // is dropped entirely and the access folds to a single store at the call site.
159 if num_subwords == 1 {
160 let subword = R::ByteEndian::to_register_endian(value.as_());
161 // SAFETY: accesswidth == regwidth here, so this writes exactly the
162 // register's bounds (same guarantee the loop relies on).
163 return unsafe { self.try_write::<R::Accesswidth>(ptr, subword) };
164 }
165
166 // write one subword at a time, starting at the lowest address
167 for i in 0..num_subwords {
168 let significance = R::WordEndian::address_order_to_significance(i, num_subwords);
169 let subword = (value >> (significance * accesswidth)) & mask;
170 let subword = R::ByteEndian::to_register_endian(subword.as_());
171 // SAFETY: SystemRDL guarantees accesswidth <= regwidth, so we won't
172 // write outside the bounds of the original pointer.
173 unsafe {
174 self.try_write(ptr.wrapping_add(i), subword)?;
175 }
176 }
177 Ok(())
178 }
179}
180
181/// Default [`RegisterIO`] implementation.
182///
183/// Provides infallible register access through volatile pointer reads
184/// and writes.
185pub struct PtrIO;
186
187impl RawRegisterIO for PtrIO {
188 type Error = core::convert::Infallible;
189
190 unsafe fn try_read<T: RegInt>(&self, ptr: *const T) -> Result<T, Self::Error> {
191 Ok(unsafe { ptr.read_volatile() })
192 }
193
194 unsafe fn try_write<T: RegInt>(&self, ptr: *mut T, value: T) -> Result<(), Self::Error> {
195 unsafe { ptr.write_volatile(value) };
196 Ok(())
197 }
198}
199
200/// Mocked [`RegisterIO`] implementation.
201///
202/// Implemented as an array of N bytes, register writes and reads
203/// simply write to/from the internal array.
204pub struct MockIO<const N: usize>(RefCell<[u8; N]>);
205
206impl<const N: usize> MockIO<N> {
207 /// Construct a new zeroed instance of the mocked register memory.
208 #[must_use]
209 pub fn new_zeroed() -> Self {
210 Self(RefCell::new([0; N]))
211 }
212
213 /// Get the base register address of the instance (always 0).
214 pub fn base_ptr(&self) -> *mut () {
215 0 as _
216 }
217}
218
219impl<const N: usize> RawRegisterIO for MockIO<N> {
220 type Error = core::convert::Infallible;
221
222 unsafe fn try_read<T: RegInt>(&self, ptr: *const T) -> Result<T, Self::Error> {
223 let addr = ptr.addr();
224 let size = core::mem::size_of::<T>();
225 let data = self.0.borrow();
226 let bytes = &data[addr..addr + size];
227 Ok(T::from_ne_bytes(
228 &bytes.try_into().expect("Incorrect slice length"),
229 ))
230 }
231
232 unsafe fn try_write<T: RegInt>(&self, ptr: *mut T, value: T) -> Result<(), Self::Error> {
233 let addr = ptr.addr();
234 let size = core::mem::size_of::<T>();
235 let mut data = self.0.borrow_mut();
236 let bytes = &mut data[addr..addr + size];
237 bytes.copy_from_slice(value.to_ne_bytes().as_ref());
238 Ok(())
239 }
240}