Skip to content

Commit c5a9052

Browse files
committed
Use safe-mmio crate for PL011 UART driver example.
1 parent fc6e5c7 commit c5a9052

File tree

9 files changed

+88
-64
lines changed

9 files changed

+88
-64
lines changed

src/bare-metal/aps/better-uart/bitflags.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ with bitflags.
1111

1212
- The `bitflags!` macro creates a newtype something like `Flags(u16)`, along
1313
with a bunch of method implementations to get and set flags.
14+
- We need to derive `FromBytes` and `IntoBytes` for use with `safe-mmio`, which
15+
we'll see on the next page.
1416

1517
</details>

src/bare-metal/aps/better-uart/driver.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@ Now let's use the new `Registers` struct in our driver.
88

99
<details>
1010

11-
- Note the use of `&raw const` / `&raw mut` to get pointers to individual fields
12-
without creating an intermediate reference, which would be unsound.
11+
- `UniqueMmioPointer` is a wrapper around a raw pointer to an MMIO device or
12+
register. The caller of `UniqueMmioPointer::new` promises that it is valid and
13+
unique for the given lifetime, so it can provide safe methods to read and
14+
write fields.
15+
- These MMIO accesses are generally a wrapper around `read_volatile` and
16+
`write_volatile`, though on aarch64 they are instead implemented in assembly
17+
to work around a bug where the compiler can emit instructions that prevent
18+
MMIO virtualisation.
19+
- The `field!` and `field_shared!` macros internally use `&raw mut` and
20+
`&raw const` to get pointers to individual fields without creating an
21+
intermediate reference, which would be unsound.
1322

1423
</details>

src/bare-metal/aps/better-uart/registers.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Multiple registers
22

3-
We can use a struct to represent the memory layout of the UART's registers.
3+
We can use a struct to represent the memory layout of the UART's registers,
4+
using types from the `safe-mmio` crate to wrap ones which can be read or written
5+
safely.
46

57
<!-- mdbook-xgettext: skip -->
68

@@ -15,5 +17,12 @@ We can use a struct to represent the memory layout of the UART's registers.
1517
rules as C. This is necessary for our struct to have a predictable layout, as
1618
default Rust representation allows the compiler to (among other things)
1719
reorder fields however it sees fit.
20+
- There are a number of different crates providing safe abstractions around MMIO
21+
operations; we recommend the `safe-mmio` crate.
22+
- The difference between `ReadPure` or `ReadOnly` (and likewise between
23+
`ReadPureWrite` and `ReadWrite`) is whether reading a register can have
24+
side-effects which change the state of the device. E.g. reading the data
25+
register pops a byte from the receive FIFO. `ReadPure` means that reads have
26+
no side-effects, they are purely reading data.
1827

1928
</details>

src/bare-metal/aps/examples/Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bare-metal/aps/examples/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ aarch64-rt = "0.1.3"
1212
arm-pl011-uart = "0.3.1"
1313
bitflags = "2.9.0"
1414
log = "0.4.27"
15+
safe-mmio = "0.2.5"
1516
smccc = "0.2.0"
1617
spin = "0.10.0"
18+
zerocopy = "0.8.25"
1719

1820
[[bin]]
1921
name = "improved"

src/bare-metal/aps/examples/src/logger.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use spin::mutex::SpinMutex;
2121
static LOGGER: Logger = Logger { uart: SpinMutex::new(None) };
2222

2323
struct Logger {
24-
uart: SpinMutex<Option<Uart>>,
24+
uart: SpinMutex<Option<Uart<'static>>>,
2525
}
2626

2727
impl Log for Logger {
@@ -43,7 +43,10 @@ impl Log for Logger {
4343
}
4444

4545
/// Initialises UART logger.
46-
pub fn init(uart: Uart, max_level: LevelFilter) -> Result<(), SetLoggerError> {
46+
pub fn init(
47+
uart: Uart<'static>,
48+
max_level: LevelFilter,
49+
) -> Result<(), SetLoggerError> {
4750
LOGGER.uart.lock().replace(uart);
4851

4952
log::set_logger(&LOGGER)?;

src/bare-metal/aps/examples/src/main_improved.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,22 @@ mod pl011;
2222
use crate::pl011::Uart;
2323
use core::fmt::Write;
2424
use core::panic::PanicInfo;
25+
use core::ptr::NonNull;
2526
use log::error;
27+
use safe_mmio::UniqueMmioPointer;
2628
use smccc::Hvc;
2729
use smccc::psci::system_off;
2830

2931
/// Base address of the primary PL011 UART.
30-
const PL011_BASE_ADDRESS: *mut u32 = 0x900_0000 as _;
32+
const PL011_BASE_ADDRESS: NonNull<pl011::Registers> =
33+
NonNull::new(0x900_0000 as _).unwrap();
3134

3235
// SAFETY: There is no other global function of this name.
3336
#[unsafe(no_mangle)]
3437
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
3538
// SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
3639
// nothing else accesses that address range.
37-
let mut uart = unsafe { Uart::new(PL011_BASE_ADDRESS) };
40+
let mut uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };
3841

3942
writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap();
4043

src/bare-metal/aps/examples/src/main_logger.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,22 @@ mod pl011;
2222

2323
use crate::pl011::Uart;
2424
use core::panic::PanicInfo;
25+
use core::ptr::NonNull;
2526
use log::{LevelFilter, error, info};
27+
use safe_mmio::UniqueMmioPointer;
2628
use smccc::Hvc;
2729
use smccc::psci::system_off;
2830

2931
/// Base address of the primary PL011 UART.
30-
const PL011_BASE_ADDRESS: *mut u32 = 0x900_0000 as _;
32+
const PL011_BASE_ADDRESS: NonNull<pl011::Registers> =
33+
NonNull::new(0x900_0000 as _).unwrap();
3134

3235
// SAFETY: There is no other global function of this name.
3336
#[unsafe(no_mangle)]
3437
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
3538
// SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
3639
// nothing else accesses that address range.
37-
let uart = unsafe { Uart::new(PL011_BASE_ADDRESS) };
40+
let uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };
3841
logger::init(uart, LevelFilter::Trace).unwrap();
3942

4043
info!("main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})");

src/bare-metal/aps/examples/src/pl011.rs

Lines changed: 46 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ use core::fmt::{self, Write};
1717

1818
// ANCHOR: Flags
1919
use bitflags::bitflags;
20+
use zerocopy::{FromBytes, IntoBytes};
21+
22+
/// Flags from the UART flag register.
23+
#[repr(transparent)]
24+
#[derive(Copy, Clone, Debug, Eq, FromBytes, IntoBytes, PartialEq)]
25+
struct Flags(u16);
2026

2127
bitflags! {
22-
/// Flags from the UART flag register.
23-
#[repr(transparent)]
24-
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
25-
struct Flags: u16 {
28+
impl Flags: u16 {
2629
/// Clear to send.
2730
const CTS = 1 << 0;
2831
/// Data set ready.
@@ -45,11 +48,13 @@ bitflags! {
4548
}
4649
// ANCHOR_END: Flags
4750

51+
/// Flags from the UART Receive Status Register / Error Clear Register.
52+
#[repr(transparent)]
53+
#[derive(Copy, Clone, Debug, Eq, FromBytes, IntoBytes, PartialEq)]
54+
struct ReceiveStatus(u16);
55+
4856
bitflags! {
49-
/// Flags from the UART Receive Status Register / Error Clear Register.
50-
#[repr(transparent)]
51-
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
52-
struct ReceiveStatus: u16 {
57+
impl ReceiveStatus: u16 {
5358
/// Framing error.
5459
const FE = 1 << 0;
5560
/// Parity error.
@@ -62,106 +67,92 @@ bitflags! {
6267
}
6368

6469
// ANCHOR: Registers
70+
use safe_mmio::fields::{ReadPure, ReadPureWrite, ReadWrite, WriteOnly};
71+
6572
#[repr(C, align(4))]
66-
struct Registers {
67-
dr: u16,
73+
pub struct Registers {
74+
dr: ReadWrite<u16>,
6875
_reserved0: [u8; 2],
69-
rsr: ReceiveStatus,
76+
rsr: ReadPure<ReceiveStatus>,
7077
_reserved1: [u8; 19],
71-
fr: Flags,
78+
fr: ReadPure<Flags>,
7279
_reserved2: [u8; 6],
73-
ilpr: u8,
80+
ilpr: ReadPureWrite<u8>,
7481
_reserved3: [u8; 3],
75-
ibrd: u16,
82+
ibrd: ReadPureWrite<u16>,
7683
_reserved4: [u8; 2],
77-
fbrd: u8,
84+
fbrd: ReadPureWrite<u8>,
7885
_reserved5: [u8; 3],
79-
lcr_h: u8,
86+
lcr_h: ReadPureWrite<u8>,
8087
_reserved6: [u8; 3],
81-
cr: u16,
88+
cr: ReadPureWrite<u16>,
8289
_reserved7: [u8; 3],
83-
ifls: u8,
90+
ifls: ReadPureWrite<u8>,
8491
_reserved8: [u8; 3],
85-
imsc: u16,
92+
imsc: ReadPureWrite<u16>,
8693
_reserved9: [u8; 2],
87-
ris: u16,
94+
ris: ReadPure<u16>,
8895
_reserved10: [u8; 2],
89-
mis: u16,
96+
mis: ReadPure<u16>,
9097
_reserved11: [u8; 2],
91-
icr: u16,
98+
icr: WriteOnly<u16>,
9299
_reserved12: [u8; 2],
93-
dmacr: u8,
100+
dmacr: ReadPureWrite<u8>,
94101
_reserved13: [u8; 3],
95102
}
96103
// ANCHOR_END: Registers
97104

98105
// ANCHOR: Uart
106+
use safe_mmio::{UniqueMmioPointer, field, field_shared};
107+
99108
/// Driver for a PL011 UART.
100109
#[derive(Debug)]
101-
pub struct Uart {
102-
registers: *mut Registers,
110+
pub struct Uart<'a> {
111+
registers: UniqueMmioPointer<'a, Registers>,
103112
}
104113

105-
impl Uart {
106-
/// Constructs a new instance of the UART driver for a PL011 device at the
107-
/// given base address.
108-
///
109-
/// # Safety
110-
///
111-
/// The given base address must point to the 8 MMIO control registers of a
112-
/// PL011 device, which must be mapped into the address space of the process
113-
/// as device memory and not have any other aliases.
114-
pub unsafe fn new(base_address: *mut u32) -> Self {
115-
Self { registers: base_address as *mut Registers }
114+
impl<'a> Uart<'a> {
115+
/// Constructs a new instance of the UART driver for a PL011 device with the
116+
/// given set of registers.
117+
pub fn new(registers: UniqueMmioPointer<'a, Registers>) -> Self {
118+
Self { registers }
116119
}
117120

118121
/// Writes a single byte to the UART.
119-
pub fn write_byte(&self, byte: u8) {
122+
pub fn write_byte(&mut self, byte: u8) {
120123
// Wait until there is room in the TX buffer.
121124
while self.read_flag_register().contains(Flags::TXFF) {}
122125

123-
// SAFETY: We know that self.registers points to the control registers
124-
// of a PL011 device which is appropriately mapped.
125-
unsafe {
126-
// Write to the TX buffer.
127-
(&raw mut (*self.registers).dr).write_volatile(byte.into());
128-
}
126+
// Write to the TX buffer.
127+
field!(self.registers, dr).write(byte.into());
129128

130129
// Wait until the UART is no longer busy.
131130
while self.read_flag_register().contains(Flags::BUSY) {}
132131
}
133132

134133
/// Reads and returns a pending byte, or `None` if nothing has been
135134
/// received.
136-
pub fn read_byte(&self) -> Option<u8> {
135+
pub fn read_byte(&mut self) -> Option<u8> {
137136
if self.read_flag_register().contains(Flags::RXFE) {
138137
None
139138
} else {
140-
// SAFETY: We know that self.registers points to the control
141-
// registers of a PL011 device which is appropriately mapped.
142-
let data = unsafe { (&raw const (*self.registers).dr).read_volatile() };
139+
let data = field!(self.registers, dr).read();
143140
// TODO: Check for error conditions in bits 8-11.
144141
Some(data as u8)
145142
}
146143
}
147144

148145
fn read_flag_register(&self) -> Flags {
149-
// SAFETY: We know that self.registers points to the control registers
150-
// of a PL011 device which is appropriately mapped.
151-
unsafe { (&raw const (*self.registers).fr).read_volatile() }
146+
field_shared!(self.registers, fr).read()
152147
}
153148
}
154149
// ANCHOR_END: Uart
155150

156-
impl Write for Uart {
151+
impl Write for Uart<'_> {
157152
fn write_str(&mut self, s: &str) -> fmt::Result {
158153
for c in s.as_bytes() {
159154
self.write_byte(*c);
160155
}
161156
Ok(())
162157
}
163158
}
164-
165-
// Safe because it just contains a pointer to device memory, which can be
166-
// accessed from any context.
167-
unsafe impl Send for Uart {}

0 commit comments

Comments
 (0)