Skip to content

Commit 5e4feff

Browse files
authored
Add section about UART driver without safe-mmio back in (#2792)
Having taught the course again since the change to use safe-mmio, it seems worth keeping this as an intermediate step rather than jumping straight from pointer arithmetic to safe-mmio's abstractions.
1 parent 447b31c commit 5e4feff

File tree

13 files changed

+333
-43
lines changed

13 files changed

+333
-43
lines changed

src/SUMMARY.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,9 @@
357357
- [Bitflags](bare-metal/aps/better-uart/bitflags.md)
358358
- [Multiple Registers](bare-metal/aps/better-uart/registers.md)
359359
- [Driver](bare-metal/aps/better-uart/driver.md)
360-
- [Using It](bare-metal/aps/better-uart/using.md)
360+
- [safe-mmio](bare-metal/aps/safemmio/registers.md)
361+
- [Driver](bare-metal/aps/safemmio/driver.md)
362+
- [Using It](bare-metal/aps/safemmio/using.md)
361363
- [Logging](bare-metal/aps/logging.md)
362364
- [Using It](bare-metal/aps/logging/using.md)
363365
- [Exceptions](bare-metal/aps/exceptions.md)

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ The [`bitflags`](https://crates.io/crates/bitflags) crate is useful for working
44
with bitflags.
55

66
```rust,editable,compile_fail
7-
{{#include ../examples/src/pl011.rs:Flags}}
7+
{{#include ../examples/src/pl011_struct.rs:Flags}}
88
```
99

1010
<details>
1111

12-
- The `bitflags!` macro creates a newtype something like `Flags(u16)`, along
13-
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.
12+
- The `bitflags!` macro creates a newtype something like `struct Flags(u16)`,
13+
along with a bunch of method implementations to get and set flags.
1614

1715
</details>

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

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,15 @@
33
Now let's use the new `Registers` struct in our driver.
44

55
```rust,editable,compile_fail
6-
{{#include ../examples/src/pl011.rs:Uart}}
6+
{{#include ../examples/src/pl011_struct.rs:Uart}}
77
```
88

99
<details>
1010

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-
- Note that `Uart::new` is now safe; `UniqueMmioPointer::new` is unsafe instead.
16-
- These MMIO accesses are generally a wrapper around `read_volatile` and
17-
`write_volatile`, though on aarch64 they are instead implemented in assembly
18-
to work around a bug where the compiler can emit instructions that prevent
19-
MMIO virtualisation.
20-
- The `field!` and `field_shared!` macros internally use `&raw mut` and
21-
`&raw const` to get pointers to individual fields without creating an
22-
intermediate reference, which would be unsound.
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.
13+
- The example isn't included in the slides because it is very similar to the
14+
`safe-mmio` example which comes next. You can run it in QEMU with `make qemu`
15+
under `src/bare-metal/aps/examples` if you need to.
2316

2417
</details>
Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
# Multiple registers
22

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.
3+
We can use a struct to represent the memory layout of the UART's registers.
64

75
<!-- mdbook-xgettext: skip -->
86

97
```rust,editable,compile_fail
10-
{{#include ../examples/src/pl011.rs:Registers}}
8+
{{#include ../examples/src/pl011_struct.rs:Registers}}
119
```
1210

1311
<details>
@@ -17,12 +15,5 @@ safely.
1715
rules as C. This is necessary for our struct to have a predictable layout, as
1816
default Rust representation allows the compiler to (among other things)
1917
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.
2718

2819
</details>

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ path = "src/main_logger.rs"
2929
name = "minimal"
3030
path = "src/main_minimal.rs"
3131

32+
[[bin]]
33+
name = "psci"
34+
path = "src/main_psci.rs"
35+
3236
[[bin]]
3337
name = "rt"
3438
path = "src/main_rt.rs"
3539

3640
[[bin]]
37-
name = "psci"
38-
path = "src/main_psci.rs"
41+
name = "safemmio"
42+
path = "src/main_safemmio.rs"

src/bare-metal/aps/examples/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ psci.bin: build
2929
cargo objcopy --bin psci -- -O binary $@
3030
rt.bin: build
3131
cargo objcopy --bin rt -- -O binary $@
32+
safemmio.bin: build
33+
cargo objcopy --bin safemmio -- -O binary $@
3234

3335
qemu: improved.bin
3436
qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $< -s
@@ -40,6 +42,8 @@ qemu_psci: psci.bin
4042
qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $< -s
4143
qemu_rt: rt.bin
4244
qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $< -s
45+
qemu_safemmio: safemmio.bin
46+
qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $< -s
4347

4448
clean:
4549
cargo clean

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,24 @@
1818

1919
mod asm;
2020
mod exceptions;
21-
mod pl011;
21+
mod pl011_struct;
2222

23-
use crate::pl011::Uart;
23+
use crate::pl011_struct::Uart;
2424
use core::fmt::Write;
2525
use core::panic::PanicInfo;
26-
use core::ptr::NonNull;
2726
use log::error;
28-
use safe_mmio::UniqueMmioPointer;
2927
use smccc::Hvc;
3028
use smccc::psci::system_off;
3129

3230
/// Base address of the primary PL011 UART.
33-
const PL011_BASE_ADDRESS: NonNull<pl011::Registers> =
34-
NonNull::new(0x900_0000 as _).unwrap();
31+
const PL011_BASE_ADDRESS: *mut pl011_struct::Registers = 0x900_0000 as _;
3532

3633
// SAFETY: There is no other global function of this name.
3734
#[unsafe(no_mangle)]
3835
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
3936
// SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
4037
// nothing else accesses that address range.
41-
let mut uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };
38+
let mut uart = unsafe { Uart::new(PL011_BASE_ADDRESS) };
4239

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fn main(x0: u64, x1: u64, x2: u64, x3: u64) -> ! {
6868
}
6969

7070
#[panic_handler]
71-
fn panic(info: &PanicInfo) -> ! {
71+
fn panic(_info: &PanicInfo) -> ! {
7272
system_off::<Hvc>().unwrap();
7373
loop {}
7474
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// ANCHOR: main
16+
#![no_main]
17+
#![no_std]
18+
19+
mod asm;
20+
mod exceptions;
21+
mod pl011;
22+
23+
use crate::pl011::Uart;
24+
use core::fmt::Write;
25+
use core::panic::PanicInfo;
26+
use core::ptr::NonNull;
27+
use log::error;
28+
use safe_mmio::UniqueMmioPointer;
29+
use smccc::Hvc;
30+
use smccc::psci::system_off;
31+
32+
/// Base address of the primary PL011 UART.
33+
const PL011_BASE_ADDRESS: NonNull<pl011::Registers> =
34+
NonNull::new(0x900_0000 as _).unwrap();
35+
36+
// SAFETY: There is no other global function of this name.
37+
#[unsafe(no_mangle)]
38+
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
39+
// SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
40+
// nothing else accesses that address range.
41+
let mut uart = Uart::new(unsafe { UniqueMmioPointer::new(PL011_BASE_ADDRESS) });
42+
43+
writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap();
44+
45+
loop {
46+
if let Some(byte) = uart.read_byte() {
47+
uart.write_byte(byte);
48+
match byte {
49+
b'\r' => uart.write_byte(b'\n'),
50+
b'q' => break,
51+
_ => continue,
52+
}
53+
}
54+
}
55+
56+
writeln!(uart, "\n\nBye!").unwrap();
57+
system_off::<Hvc>().unwrap();
58+
}
59+
// ANCHOR_END: main
60+
61+
#[panic_handler]
62+
fn panic(info: &PanicInfo) -> ! {
63+
error!("{info}");
64+
system_off::<Hvc>().unwrap();
65+
loop {}
66+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
#![allow(dead_code)]
15+
16+
use core::fmt::{self, Write};
17+
18+
// ANCHOR: Flags
19+
use bitflags::bitflags;
20+
21+
bitflags! {
22+
/// Flags from the UART flag register.
23+
#[repr(transparent)]
24+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
25+
struct Flags: u16 {
26+
/// Clear to send.
27+
const CTS = 1 << 0;
28+
/// Data set ready.
29+
const DSR = 1 << 1;
30+
/// Data carrier detect.
31+
const DCD = 1 << 2;
32+
/// UART busy transmitting data.
33+
const BUSY = 1 << 3;
34+
/// Receive FIFO is empty.
35+
const RXFE = 1 << 4;
36+
/// Transmit FIFO is full.
37+
const TXFF = 1 << 5;
38+
/// Receive FIFO is full.
39+
const RXFF = 1 << 6;
40+
/// Transmit FIFO is empty.
41+
const TXFE = 1 << 7;
42+
/// Ring indicator.
43+
const RI = 1 << 8;
44+
}
45+
}
46+
// ANCHOR_END: Flags
47+
48+
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 {
53+
/// Framing error.
54+
const FE = 1 << 0;
55+
/// Parity error.
56+
const PE = 1 << 1;
57+
/// Break error.
58+
const BE = 1 << 2;
59+
/// Overrun error.
60+
const OE = 1 << 3;
61+
}
62+
}
63+
64+
// ANCHOR: Registers
65+
#[repr(C, align(4))]
66+
pub struct Registers {
67+
dr: u16,
68+
_reserved0: [u8; 2],
69+
rsr: ReceiveStatus,
70+
_reserved1: [u8; 19],
71+
fr: Flags,
72+
_reserved2: [u8; 6],
73+
ilpr: u8,
74+
_reserved3: [u8; 3],
75+
ibrd: u16,
76+
_reserved4: [u8; 2],
77+
fbrd: u8,
78+
_reserved5: [u8; 3],
79+
lcr_h: u8,
80+
_reserved6: [u8; 3],
81+
cr: u16,
82+
_reserved7: [u8; 3],
83+
ifls: u8,
84+
_reserved8: [u8; 3],
85+
imsc: u16,
86+
_reserved9: [u8; 2],
87+
ris: u16,
88+
_reserved10: [u8; 2],
89+
mis: u16,
90+
_reserved11: [u8; 2],
91+
icr: u16,
92+
_reserved12: [u8; 2],
93+
dmacr: u8,
94+
_reserved13: [u8; 3],
95+
}
96+
// ANCHOR_END: Registers
97+
98+
// ANCHOR: Uart
99+
/// Driver for a PL011 UART.
100+
#[derive(Debug)]
101+
pub struct Uart {
102+
registers: *mut Registers,
103+
}
104+
105+
impl Uart {
106+
/// Constructs a new instance of the UART driver for a PL011 device with the
107+
/// given set of registers.
108+
///
109+
/// # Safety
110+
///
111+
/// The given pointer must point to the 8 MMIO control registers of a PL011
112+
/// device, which must be mapped into the address space of the process as
113+
/// device memory and not have any other aliases.
114+
pub unsafe fn new(registers: *mut Registers) -> Self {
115+
Self { registers }
116+
}
117+
118+
/// Writes a single byte to the UART.
119+
pub fn write_byte(&mut self, byte: u8) {
120+
// Wait until there is room in the TX buffer.
121+
while self.read_flag_register().contains(Flags::TXFF) {}
122+
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+
}
129+
130+
// Wait until the UART is no longer busy.
131+
while self.read_flag_register().contains(Flags::BUSY) {}
132+
}
133+
134+
/// Reads and returns a pending byte, or `None` if nothing has been
135+
/// received.
136+
pub fn read_byte(&mut self) -> Option<u8> {
137+
if self.read_flag_register().contains(Flags::RXFE) {
138+
None
139+
} 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() };
143+
// TODO: Check for error conditions in bits 8-11.
144+
Some(data as u8)
145+
}
146+
}
147+
148+
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() }
152+
}
153+
}
154+
// ANCHOR_END: Uart
155+
156+
impl Write for Uart {
157+
fn write_str(&mut self, s: &str) -> fmt::Result {
158+
for c in s.as_bytes() {
159+
self.write_byte(*c);
160+
}
161+
Ok(())
162+
}
163+
}
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)