derive_mmio/lib.rs
1/*!
2# `derive-mmio` - turning structures into MMIO access objects
3
4## Rationale
5
6In C it is very common to create structures that refer to Memory-Mapped I/O
7(MMIO) peripherals:
8
9```c
10typedef volatile struct uart_t {
11 uint32_t data;
12 const uint32_t status;
13} uart_t;
14
15uart_t* p_uart = (uart_t*) 0x40008000;
16```
17
18In Rust, we have some issues:
19
201. There are no volatile types, only volatile pointer reads/writes. So we
21 cannot mark a type as 'volatile' and have all accesses to its fields
22 performed a volatile operations. And given that MMIO registers have
23 side-effects (like writing to a FIFO), it is important that those
24 accesses are volatile.
252. We must never construct a reference to an MMIO peripheral, because
26 references are, well, dereferenceable, and LLVM is free to dereference
27 them whenever it likes. This might cause unexpected reads of the MMIO
28 peripheral and is considered UB.
293. Accessing a field of a struct without constructing a pointer to it used
30 to be quite tricky, although as of Rust 1.51 we have
31 [`core::ptr::addr_of_mut`] and as of Rust 1.84 we have `&raw mut`.
32
33The usual solution to these problems is to auto-generate code based on some
34machine-readable (but non-Rust) description of the MMIO peripheral. This
35code will contain functions to get a 'handle' to a peripheral, and that
36handle has methods to get a handle to each register within it, and those
37handles have methods for reading, writing or modifying the register
38contents. Unfortunately, this requires having a machine-readable (typically
39SVD XML) description of the peripherals and those are either not always
40available, or cover an entire System-on-Chip when a driver is in fact only
41aiming to work with one common MMIO peripheral (e.g. the Arm PL011 UART that has
42been licensed and copy-pasted in dozens of System-on-Chip designs).
43
44## How this crate works
45
46This crate presents an alternative solution.
47
48Consider the code:
49
50```rust
51#[derive(derive_mmio::Mmio)]
52#[repr(C)]
53struct Uart {
54 data: u32,
55 #[mmio(Read)]
56 status: u32,
57 control: u32,
58}
59```
60
61Note that your struct must be `repr(C)` and we will check this.
62
63The `derive_mmio::Mmio` derive-macro will generate some new methods and types
64for you. You can see this for yourself with `cargo doc` (or `cargo expand` if
65you have installed `cargo-expand`), but our example will expand to something
66like this (simplified):
67
68```rust
69// this is your type, unchanged
70#[repr(C)]
71struct Uart {
72 data: u32,
73 status: u32,
74 control: u32
75}
76// this is a new 'handle' type
77struct MmioUart {
78 ptr: *mut Uart,
79}
80// some methods on the 'handle' type
81impl MmioUart {
82 pub fn pointer_to_data(&self) -> *mut u32 {
83 unsafe { &raw mut (*self.ptr).data }
84 }
85 pub fn read_data(&self) -> u32 {
86 let addr = unsafe { core::ptr::addr_of!((*self.ptr).data) };
87 unsafe {
88 addr.read_volatile()
89 }
90 }
91 pub fn write_data(&mut self, value: u32) {
92 let addr = self.pointer_to_data();
93 unsafe { addr.write_volatile(value) }
94 }
95 pub fn modify_data<F>(&mut self, f: F)
96 where
97 F: FnOnce(u32) -> u32,
98 {
99 let value = self.read_data();
100 let new_value = f(value);
101 self.write_data(new_value);
102 }
103
104 // but you can only read the status register
105 pub fn pointer_to_status(&mut self) -> *mut u32 {
106 unsafe { &raw mut (*self.ptr).status }
107 }
108 pub fn read_status(&mut self) -> u32 {
109 let addr = self.pointer_to_status();
110 unsafe { addr.read_volatile() }
111 }
112
113 // The control register methods are skipped here for brevity
114}
115// some new methods we add onto your type
116impl Uart {
117 pub const unsafe fn new_mmio(ptr: *mut Uart) -> MmioUart {
118 MmioUart { ptr }
119 }
120 pub const unsafe fn new_mmio_at(addr: usize) -> MmioUart {
121 MmioUart {
122 ptr: addr as *mut Uart,
123 }
124 }
125}
126```
127
128OK, that was a lot! Let's unpack it.
129
130## The MMIO Handle
131
132```rust,ignore
133struct MmioUart {
134 ptr: *mut Uart,
135}
136```
137
138This structure, called `Mmio${StructName}` is a handle that proxies access
139to that particular peripheral. You create as many as you need by unsafely
140calling one of these methods we added to your struct type.
141
142```rust,ignore
143impl Uart {
144 pub const unsafe fn new_mmio(ptr: *mut Uart) -> MmioUart {
145 MmioUart { ptr }
146 }
147 pub const unsafe fn new_mmio_at(addr: usize) -> MmioUart {
148 MmioUart {
149 ptr: addr as *mut Uart,
150 }
151 }
152}
153```
154
155One is for when you have a pointer, and the other is for when you only have
156the address (typically as a literal integer you read from the System-on-Chip's
157datasheet, like `0x4008_1000`).
158
159It is unsafe to create these - you must verify that you are passing a valid
160address or pointer, and that if you are creating multiple MMIO handles for one
161peripheral at the same same that you use them in a way that complies with the
162peripheral's rules around concurrent access. For example, don't create two
163handles and use them to both do a read-modify-write on the *same* register
164at the same time - that's a race hazard and the results won't be reliable. But
165you could create two and use them to read-modify-write *different* registers -
166probably. It depends on whether the registers affect each other or operate
167in isolation.
168
169The constructors shown above will be generated by default. You might want to
170implement custom constructors, for example if your peripheral is only valid for
171one specific address, or a specific set of addresses. You can disable the
172generation of these constructors by adding the `#[mmio(no_ctors)]` attribute
173annotation to your peripheral block structure.
174
175## MMIO Methods
176
177The MMIO handle has methods to access each of the fields in the underlying
178struct.
179
180You can read (which performs a volatile read):
181
182```rust,ignore
183println!("data = {}", mmio_uart.read_data());
184```
185
186You can write (which performs a volatile write):
187
188```rust,ignore
189mmio_uart.write_data(0x00);
190```
191
192And you can perform a read-modify-write (which requires exclusive access and
193you should not use if any other code might modify this register
194concurrently).
195
196```rust,ignore
197mmio_uart.modify_control(|mut r| {
198 r &= 0xF000_0000;
199 r |= 1 << 31;
200 r
201});
202```
203
204If you need a pointer to a register, for example if you want to have a DMA
205engine write to a register on your peripheral, you can use this method:
206
207```rust,ignore
208let p: *mut u32 = mmio_uart.pointer_to_data();
209```
210
211### Inner Fields
212
213If you have a field that is annotated with `#[mmio(Inner)]`, the derive macro
214will generate getters for that field. Note that the type of such 'inner' fields
215must be annotated with `#[derive(Mmio)]`.
216
217The getter will have the same name as the field name of your peripheral block
218and will have a lifetime tied to the outer MMIO structure.
219
220```rust,ignore
221// Given
222#[derive(Mmio)]
223struct Peripheral {
224 #[mmio(Inner)]
225 some_inner: InnerType
226}
227
228// You get a method like this:
229impl MmioPeripheral {
230 pub fn some_inner(&mut self) -> MmioInnerType<'_> {
231 /// ...
232 }
233}
234```
235
236The macro will also generate an `unsafe` `steal_${inner_field}` method
237which has a static lifetime, which in turn allows you to create an arbitrary
238number of owned inner MMIO objects:
239
240```rust,ignore
241// Given
242#[derive(Mmio)]
243struct Peripheral {
244 #[mmio(Inner)]
245 some_inner: InnerType
246}
247
248// You get a method like this:
249impl MmioPeripheral {
250 pub unsafe fn steal_some_inner(&mut self) -> MmioInnerType<'static> {
251 /// ...
252 }
253}
254```
255
256If you want to access your inner field through only a shared reference, access
257is granted through the [`SharedInner`] wrapper type. This ensures you only have
258access to non-mutable methods on the inner field.
259
260```rust,ignore
261// Given
262#[derive(Mmio)]
263struct Peripheral {
264 #[mmio(Inner)]
265 some_inner: InnerType
266}
267
268// You get methods like this:
269impl MmioPeripheral {
270 pub fn some_inner_shared(&self) -> SharedInner<MmioInnerType<'_>> {
271 // ...
272 }
273 pub unsafe fn steal_some_inner_shared(&self) -> SharedInner<MmioInnerType<'static>> {
274 // ...
275 }
276}
277```
278
279The [`SharedInner`] wrapper type implements [`Deref`] so it is transparent to
280the user.
281
282### Array Fields
283
284Array Fields get two kinds of function - safe ones that perform a bounds check,
285and unsafe ones which skip the bounds check.
286
287```rust,ignore
288// Given
289#[derive(Mmio)]
290struct Peripheral {
291 bank: [u32; 4]
292}
293
294// You get methods like this:
295impl MmioUart {
296 pub fn pointer_to_bank_start(&mut self) -> *mut u32 {
297 /// ...
298 }
299
300 pub fn read_bank(&self, index: usize) -> Result<u32, OutOfBoundsError> {
301 // ...
302 }
303
304 pub unsafe fn read_bank_unchecked(&self, index: usize) -> u32 {
305 // ...
306 }
307
308 pub fn write_bank(&mut self, index: usize, value: u32) -> Result<(), OutOfBoundsError> {
309 // ...
310 }
311
312 pub unsafe fn write_bank_unchecked(&mut self, index: usize, value: u32) {
313 // ...
314 }
315
316 pub fn modify_bank<F: FnOnce(u32) -> u32>(&mut self, index: usize, f: F) -> Result<(), OutOfBoundsError> {
317 // ...
318 }
319
320 pub unsafe fn modify_bank_unchecked<F: FnOnce(u32) -> u32>(&mut self, index: usize, f: F) {
321 // ...
322 }
323}
324```
325
326## Supported attributes
327
328The following attributes are supported for fields with a struct which is wrapped
329with `#[derive(Mmio)]`:
330
331### Outer attributes
332
333- `#[mmio(no_ctors)]`: Omit the generation of constructor functions like
334 `new_mmio_at` and `new_mmio`. This allows users to specify their own custom
335 constructors, for example to constrain or check the allowed base addresses.
336- `#[mmio(const_ptr)]`: Pointer getter methods for array field are `const` now.
337 Requires Rust 1.83.0 or higher.
338- `#[mmio(const_inner)]`: Const getter methods for inner MMIO blocks. Requires Rust 1.83.0 or
339 higher.
340
341### Field attributes
342
343The access permission attributes work for array fields as well.
344
345- `#[mmio(PureRead)]`: The field is read-only. The read does not have side
346 effects, and the generated reader function only requires a shared reference
347 to the MMIO handle.
348- `#[mmio(Read)]`: The field can be read, but the read has side effects. The
349 generated reader function requires a mutable reference to the MMIO handle.
350- `#[mmio(Write)]`: The field can be written to. This will generate a writer
351 function for the field.
352- `#[mmio(Modify)]`: The field can be modified. This will generate a modify
353 function for the field which performs a Read-Modify-Write operation.
354- `#[mmio(Inner)]`: The field is a register block. It must be a type which is
355 `#[derive(Mmio)]`, which will be verified using trait bounds. The derive macro
356 will generate getter functions to retrieve a handle for the inner block, with
357 the lifetime of the inner handle tied to the outer handle.
358
359If no permission access modifiers were specified, the library will default to
360`PureRead`, `Write`, `Modify` which is the default for most regular R/W
361registers.
362
363## Supported field types
364
365The following field types are supported and tested:
366
367- [`u32`]
368- Arrays of [`u32`]
369- Bitfields implemented with [`bitbybit::bitfield`]
370- Other `#[derive(Mmio)]` types, if the field is annotated with the
371 `#[mmio(Inner)]` attribute. Arrays of inner MMIO types are also allowed.
372
373[`bitbybit::bitfield`]: https://crates.io/crates/bitbybit
374
375Other `repr(transparent)` types should work, but you should be careful to ensure
376that every field corresponds 1:1 with an MMIO register and that they are the
377appropriate size for your CPU architecture.
378
379If you accidentally introduce padding (or, if the sum of the size of the
380individual fields isn't the same as the size of the overall `struct`), you will
381get a compile error.
382*/
383
384#![no_std]
385#![deny(clippy::doc_markdown)]
386#![deny(missing_docs)]
387
388use core::{fmt::Display, ops::Deref};
389
390/// The error returned when an array access method is given an index that is out
391/// of bounds for the size of the field.
392#[derive(Debug)]
393#[cfg_attr(feature = "defmt", derive(defmt::Format))]
394pub struct OutOfBoundsError(pub usize);
395
396impl Display for OutOfBoundsError {
397 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398 write!(f, "out of bounds access at index {}", self.0)
399 }
400}
401
402/// A wrapper type that only gives you shared access to the contents, not
403/// exclusive/mutable access.
404pub struct SharedInner<T>(T);
405
406impl<T> SharedInner<T> {
407 #[doc(hidden)]
408 pub const fn __new_internal(t: T) -> Self {
409 Self(t)
410 }
411
412 /// Get shared access to the value within
413 pub const fn inner(&self) -> &T {
414 &self.0
415 }
416}
417
418impl<T> Deref for SharedInner<T> {
419 type Target = T;
420
421 fn deref(&self) -> &Self::Target {
422 self.inner()
423 }
424}
425
426#[rustversion::since(1.81)]
427impl core::error::Error for OutOfBoundsError {}
428
429/// Marker trait to check whether an inner field's type has been marked with
430/// `#[derive(Mmio)]`.
431///
432/// # Safety
433///
434/// You should not implement this trait yourself. This is done by the [`Mmio`]
435/// derive macro.
436#[doc(hidden)]
437pub unsafe trait _MmioMarker {}
438
439/// Const function to check trait bounds.
440pub const fn is_mmio<M: _MmioMarker>() {}
441
442#[doc(inline)]
443pub use derive_mmio_macro::Mmio;