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
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
/*!
# `derive-mmio` - turning structures into MMIO access objects
## Rationale
In C it is very common to create structures that refer to Memory-Mapped I/O
(MMIO) peripherals:
```c
typedef volatile struct uart_t {
uint32_t data;
const uint32_t status;
} uart_t;
uart_t* p_uart = (uart_t*) 0x40008000;
```
In Rust, we have some issues:
1. There are no volatile types, only volatile pointer reads/writes. So we
cannot mark a type as 'volatile' and have all accesses to its fields
performed a volatile operations. And given that MMIO registers have
side-effects (like writing to a FIFO), it is important that those
accesses are volatile.
2. We must never construct a reference to an MMIO peripheral, because
references are, well, dereferenceable, and LLVM is free to dereference
them whenever it likes. This might cause unexpected reads of the MMIO
peripheral and is considered UB.
3. Accessing a field of a struct without constructing a pointer to it used
to be quite tricky, although as of Rust 1.51 we have
[`core::ptr::addr_of_mut`] and as of Rust 1.84 we have `&raw mut`.
The usual solution to these problems is to auto-generate code based on some
machine-readable (but non-Rust) description of the MMIO peripheral. This
code will contain functions to get a 'handle' to a peripheral, and that
handle has methods to get a handle to each register within it, and those
handles have methods for reading, writing or modifying the register
contents. Unfortunately, this requires having a machine-readable (typically
SVD XML) description of the peripherals and those are either not always
available, or cover an entire System-on-Chip when a driver is in fact only
aiming to work with one common MMIO peripheral (e.g. the Arm PL011 UART that has
been licensed and copy-pasted in dozens of System-on-Chip designs).
## How this crate works
This crate presents an alternative solution.
Consider the code:
```rust
#[derive(derive_mmio::Mmio)]
#[repr(C)]
struct Uart {
data: u32,
#[mmio(Read)]
status: u32,
control: u32,
}
```
Note that your struct must be `repr(C)` and we will check this.
The `derive_mmio::Mmio` derive-macro will generate some new methods and types
for you. You can see this for yourself with `cargo doc` (or `cargo expand` if
you have installed `cargo-expand`), but our example will expand to something
like this (simplified):
```rust
// this is your type, unchanged
#[repr(C)]
struct Uart {
data: u32,
status: u32,
control: u32
}
// this is a new 'handle' type
struct MmioUart {
ptr: *mut Uart,
}
// some methods on the 'handle' type
impl MmioUart {
pub fn pointer_to_data(&self) -> *mut u32 {
unsafe { &raw mut (*self.ptr).data }
}
pub fn read_data(&self) -> u32 {
let addr = unsafe { core::ptr::addr_of!((*self.ptr).data) };
unsafe {
addr.read_volatile()
}
}
pub fn write_data(&mut self, value: u32) {
let addr = self.pointer_to_data();
unsafe { addr.write_volatile(value) }
}
pub fn modify_data<F>(&mut self, f: F)
where
F: FnOnce(u32) -> u32,
{
let value = self.read_data();
let new_value = f(value);
self.write_data(new_value);
}
// but you can only read the status register
pub fn pointer_to_status(&mut self) -> *mut u32 {
unsafe { &raw mut (*self.ptr).status }
}
pub fn read_status(&mut self) -> u32 {
let addr = self.pointer_to_status();
unsafe { addr.read_volatile() }
}
// The control register methods are skipped here for brevity
}
// some new methods we add onto your type
impl Uart {
pub const unsafe fn new_mmio(ptr: *mut Uart) -> MmioUart {
MmioUart { ptr }
}
pub const unsafe fn new_mmio_at(addr: usize) -> MmioUart {
MmioUart {
ptr: addr as *mut Uart,
}
}
}
```
OK, that was a lot! Let's unpack it.
## The MMIO Handle
```rust,ignore
struct MmioUart {
ptr: *mut Uart,
}
```
This structure, called `Mmio${StructName}` is a handle that proxies access
to that particular peripheral. You create as many as you need by unsafely
calling one of these methods we added to your struct type.
```rust,ignore
impl Uart {
pub const unsafe fn new_mmio(ptr: *mut Uart) -> MmioUart {
MmioUart { ptr }
}
pub const unsafe fn new_mmio_at(addr: usize) -> MmioUart {
MmioUart {
ptr: addr as *mut Uart,
}
}
}
```
One is for when you have a pointer, and the other is for when you only have
the address (typically as a literal integer you read from the System-on-Chip's
datasheet, like `0x4008_1000`).
It is unsafe to create these - you must verify that you are passing a valid
address or pointer, and that if you are creating multiple MMIO handles for one
peripheral at the same same that you use them in a way that complies with the
peripheral's rules around concurrent access. For example, don't create two
handles and use them to both do a read-modify-write on the *same* register
at the same time - that's a race hazard and the results won't be reliable. But
you could create two and use them to read-modify-write *different* registers -
probably. It depends on whether the registers affect each other or operate
in isolation.
The constructors shown above will be generated by default. You might want to
implement custom constructors, for example if your peripheral is only valid for
one specific address, or a specific set of addresses. You can disable the
generation of these constructors by adding the `#[mmio(no_ctors)]` attribute
annotation to your peripheral block structure.
## MMIO Methods
The MMIO handle has methods to access each of the fields in the underlying
struct.
You can read (which performs a volatile read):
```rust,ignore
println!("data = {}", mmio_uart.read_data());
```
You can write (which performs a volatile write):
```rust,ignore
mmio_uart.write_data(0x00);
```
And you can perform a read-modify-write (which requires exclusive access and
you should not use if any other code might modify this register
concurrently).
```rust,ignore
mmio_uart.modify_control(|mut r| {
r &= 0xF000_0000;
r |= 1 << 31;
r
});
```
If you need a pointer to a register, for example if you want to have a DMA
engine write to a register on your peripheral, you can use this method:
```rust,ignore
let p: *mut u32 = mmio_uart.pointer_to_data();
```
### Inner Fields
If you have a field that is annotated with `#[mmio(Inner)]`, the derive macro
will generate getters for that field. Note that the type of such 'inner' fields
must be annotated with `#[derive(Mmio)]`.
The getter will have the same name as the field name of your peripheral block
and will have a lifetime tied to the outer MMIO structure.
```rust,ignore
// Given
#[derive(Mmio)]
struct Peripheral {
#[mmio(Inner)]
some_inner: InnerType
}
// You get a method like this:
impl MmioPeripheral {
pub fn some_inner(&mut self) -> MmioInnerType<'_> {
/// ...
}
}
```
The macro will also generate an `unsafe` `steal_${inner_field}` method
which has a static lifetime, which in turn allows you to create an arbitrary
number of owned inner MMIO objects:
```rust,ignore
// Given
#[derive(Mmio)]
struct Peripheral {
#[mmio(Inner)]
some_inner: InnerType
}
// You get a method like this:
impl MmioPeripheral {
pub unsafe fn steal_some_inner(&mut self) -> MmioInnerType<'static> {
/// ...
}
}
```
If you want to access your inner field through only a shared reference, access
is granted through the [`SharedInner`] wrapper type. This ensures you only have
access to non-mutable methods on the inner field.
```rust,ignore
// Given
#[derive(Mmio)]
struct Peripheral {
#[mmio(Inner)]
some_inner: InnerType
}
// You get methods like this:
impl MmioPeripheral {
pub fn some_inner_shared(&self) -> SharedInner<MmioInnerType<'_>> {
// ...
}
pub unsafe fn steal_some_inner_shared(&self) -> SharedInner<MmioInnerType<'static>> {
// ...
}
}
```
The [`SharedInner`] wrapper type implements [`Deref`] so it is transparent to
the user.
### Array Fields
Array Fields get two kinds of function - safe ones that perform a bounds check,
and unsafe ones which skip the bounds check.
```rust,ignore
// Given
#[derive(Mmio)]
struct Peripheral {
bank: [u32; 4]
}
// You get methods like this:
impl MmioUart {
pub fn pointer_to_bank_start(&mut self) -> *mut u32 {
/// ...
}
pub fn read_bank(&self, index: usize) -> Result<u32, OutOfBoundsError> {
// ...
}
pub unsafe fn read_bank_unchecked(&self, index: usize) -> u32 {
// ...
}
pub fn write_bank(&mut self, index: usize, value: u32) -> Result<(), OutOfBoundsError> {
// ...
}
pub unsafe fn write_bank_unchecked(&mut self, index: usize, value: u32) {
// ...
}
pub fn modify_bank<F: FnOnce(u32) -> u32>(&mut self, index: usize, f: F) -> Result<(), OutOfBoundsError> {
// ...
}
pub unsafe fn modify_bank_unchecked<F: FnOnce(u32) -> u32>(&mut self, index: usize, f: F) {
// ...
}
}
```
## Supported attributes
The following attributes are supported for fields with a struct which is wrapped
with `#[derive(Mmio)]`:
### Outer attributes
- `#[mmio(no_ctors)]`: Omit the generation of constructor functions like
`new_mmio_at` and `new_mmio`. This allows users to specify their own custom
constructors, for example to constrain or check the allowed base addresses.
- `#[mmio(const_ptr)]`: Pointer getter methods for array field are `const` now.
Requires Rust 1.83.0 or higher.
- `#[mmio(const_inner)]`: Const getter methods for inner MMIO blocks. Requires Rust 1.83.0 or
higher.
### Field attributes
The access permission attributes work for array fields as well.
- `#[mmio(PureRead)]`: The field is read-only. The read does not have side
effects, and the generated reader function only requires a shared reference
to the MMIO handle.
- `#[mmio(Read)]`: The field can be read, but the read has side effects. The
generated reader function requires a mutable reference to the MMIO handle.
- `#[mmio(Write)]`: The field can be written to. This will generate a writer
function for the field.
- `#[mmio(Modify)]`: The field can be modified. This will generate a modify
function for the field which performs a Read-Modify-Write operation.
- `#[mmio(Inner)]`: The field is a register block. It must be a type which is
`#[derive(Mmio)]`, which will be verified using trait bounds. The derive macro
will generate getter functions to retrieve a handle for the inner block, with
the lifetime of the inner handle tied to the outer handle.
If no permission access modifiers were specified, the library will default to
`PureRead`, `Write`, `Modify` which is the default for most regular R/W
registers.
## Supported field types
The following field types are supported and tested:
- [`u32`]
- Arrays of [`u32`]
- Bitfields implemented with [`bitbybit::bitfield`]
- Other `#[derive(Mmio)]` types, if the field is annotated with the
`#[mmio(Inner)]` attribute. Arrays of inner MMIO types are also allowed.
[`bitbybit::bitfield`]: https://crates.io/crates/bitbybit
Other `repr(transparent)` types should work, but you should be careful to ensure
that every field corresponds 1:1 with an MMIO register and that they are the
appropriate size for your CPU architecture.
If you accidentally introduce padding (or, if the sum of the size of the
individual fields isn't the same as the size of the overall `struct`), you will
get a compile error.
*/
use ;
/// The error returned when an array access method is given an index that is out
/// of bounds for the size of the field.
;
/// A wrapper type that only gives you shared access to the contents, not
/// exclusive/mutable access.
;
/// Marker trait to check whether an inner field's type has been marked with
/// `#[derive(Mmio)]`.
///
/// # Safety
///
/// You should not implement this trait yourself. This is done by the [`Mmio`]
/// derive macro.
pub unsafe
/// Const function to check trait bounds.
pub const
pub use Mmio;