Skip to main content

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;