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