diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 709f91d3..4caccd92 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -357,7 +357,9 @@
- [Bitflags](bare-metal/aps/better-uart/bitflags.md)
- [Multiple Registers](bare-metal/aps/better-uart/registers.md)
- [Driver](bare-metal/aps/better-uart/driver.md)
- - [Using It](bare-metal/aps/better-uart/using.md)
+ - [safe-mmio](bare-metal/aps/safemmio/registers.md)
+ - [Driver](bare-metal/aps/safemmio/driver.md)
+ - [Using It](bare-metal/aps/safemmio/using.md)
- [Logging](bare-metal/aps/logging.md)
- [Using It](bare-metal/aps/logging/using.md)
- [Exceptions](bare-metal/aps/exceptions.md)
diff --git a/src/bare-metal/aps/better-uart/bitflags.md b/src/bare-metal/aps/better-uart/bitflags.md
index ab54412b..9c689914 100644
--- a/src/bare-metal/aps/better-uart/bitflags.md
+++ b/src/bare-metal/aps/better-uart/bitflags.md
@@ -4,14 +4,12 @@ The [`bitflags`](https://crates.io/crates/bitflags) crate is useful for working
with bitflags.
```rust,editable,compile_fail
-{{#include ../examples/src/pl011.rs:Flags}}
+{{#include ../examples/src/pl011_struct.rs:Flags}}
```
-- The `bitflags!` macro creates a newtype something like `Flags(u16)`, along
- with a bunch of method implementations to get and set flags.
-- We need to derive `FromBytes` and `IntoBytes` for use with `safe-mmio`, which
- we'll see on the next page.
+- The `bitflags!` macro creates a newtype something like `struct Flags(u16)`,
+ along with a bunch of method implementations to get and set flags.
diff --git a/src/bare-metal/aps/better-uart/driver.md b/src/bare-metal/aps/better-uart/driver.md
index 72e7635d..04b65825 100644
--- a/src/bare-metal/aps/better-uart/driver.md
+++ b/src/bare-metal/aps/better-uart/driver.md
@@ -3,22 +3,15 @@
Now let's use the new `Registers` struct in our driver.
```rust,editable,compile_fail
-{{#include ../examples/src/pl011.rs:Uart}}
+{{#include ../examples/src/pl011_struct.rs:Uart}}
```
-- `UniqueMmioPointer` is a wrapper around a raw pointer to an MMIO device or
- register. The caller of `UniqueMmioPointer::new` promises that it is valid and
- unique for the given lifetime, so it can provide safe methods to read and
- write fields.
-- Note that `Uart::new` is now safe; `UniqueMmioPointer::new` is unsafe instead.
-- These MMIO accesses are generally a wrapper around `read_volatile` and
- `write_volatile`, though on aarch64 they are instead implemented in assembly
- to work around a bug where the compiler can emit instructions that prevent
- MMIO virtualisation.
-- The `field!` and `field_shared!` macros internally use `&raw mut` and
- `&raw const` to get pointers to individual fields without creating an
- intermediate reference, which would be unsound.
+- Note the use of `&raw const` / `&raw mut` to get pointers to individual fields
+ without creating an intermediate reference, which would be unsound.
+- The example isn't included in the slides because it is very similar to the
+ `safe-mmio` example which comes next. You can run it in QEMU with `make qemu`
+ under `src/bare-metal/aps/examples` if you need to.
diff --git a/src/bare-metal/aps/better-uart/registers.md b/src/bare-metal/aps/better-uart/registers.md
index cf95b665..4ecbfaba 100644
--- a/src/bare-metal/aps/better-uart/registers.md
+++ b/src/bare-metal/aps/better-uart/registers.md
@@ -1,13 +1,11 @@
# Multiple registers
-We can use a struct to represent the memory layout of the UART's registers,
-using types from the `safe-mmio` crate to wrap ones which can be read or written
-safely.
+We can use a struct to represent the memory layout of the UART's registers.
```rust,editable,compile_fail
-{{#include ../examples/src/pl011.rs:Registers}}
+{{#include ../examples/src/pl011_struct.rs:Registers}}
```
@@ -17,12 +15,5 @@ safely.
rules as C. This is necessary for our struct to have a predictable layout, as
default Rust representation allows the compiler to (among other things)
reorder fields however it sees fit.
-- There are a number of different crates providing safe abstractions around MMIO
- operations; we recommend the `safe-mmio` crate.
-- The difference between `ReadPure` or `ReadOnly` (and likewise between
- `ReadPureWrite` and `ReadWrite`) is whether reading a register can have
- side-effects which change the state of the device. E.g. reading the data
- register pops a byte from the receive FIFO. `ReadPure` means that reads have
- no side-effects, they are purely reading data.
diff --git a/src/bare-metal/aps/examples/Cargo.toml b/src/bare-metal/aps/examples/Cargo.toml
index 16573c5a..d21b01e7 100644
--- a/src/bare-metal/aps/examples/Cargo.toml
+++ b/src/bare-metal/aps/examples/Cargo.toml
@@ -29,10 +29,14 @@ path = "src/main_logger.rs"
name = "minimal"
path = "src/main_minimal.rs"
+[[bin]]
+name = "psci"
+path = "src/main_psci.rs"
+
[[bin]]
name = "rt"
path = "src/main_rt.rs"
[[bin]]
-name = "psci"
-path = "src/main_psci.rs"
+name = "safemmio"
+path = "src/main_safemmio.rs"
diff --git a/src/bare-metal/aps/examples/Makefile b/src/bare-metal/aps/examples/Makefile
index 23f366f6..8f91d594 100644
--- a/src/bare-metal/aps/examples/Makefile
+++ b/src/bare-metal/aps/examples/Makefile
@@ -29,6 +29,8 @@ psci.bin: build
cargo objcopy --bin psci -- -O binary $@
rt.bin: build
cargo objcopy --bin rt -- -O binary $@
+safemmio.bin: build
+ cargo objcopy --bin safemmio -- -O binary $@
qemu: improved.bin
qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $< -s
@@ -40,6 +42,8 @@ qemu_psci: psci.bin
qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $< -s
qemu_rt: rt.bin
qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $< -s
+qemu_safemmio: safemmio.bin
+ qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $< -s
clean:
cargo clean
diff --git a/src/bare-metal/aps/examples/src/main_improved.rs b/src/bare-metal/aps/examples/src/main_improved.rs
index 777c7ac3..a2c2d572 100644
--- a/src/bare-metal/aps/examples/src/main_improved.rs
+++ b/src/bare-metal/aps/examples/src/main_improved.rs
@@ -18,27 +18,24 @@
mod asm;
mod exceptions;
-mod pl011;
+mod pl011_struct;
-use crate::pl011::Uart;
+use crate::pl011_struct::Uart;
use core::fmt::Write;
use core::panic::PanicInfo;
-use core::ptr::NonNull;
use log::error;
-use safe_mmio::UniqueMmioPointer;
use smccc::Hvc;
use smccc::psci::system_off;
/// Base address of the primary PL011 UART.
-const PL011_BASE_ADDRESS: NonNull =
- NonNull::new(0x900_0000 as _).unwrap();
+const PL011_BASE_ADDRESS: *mut pl011_struct::Registers = 0x900_0000 as _;
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
// SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
// nothing else accesses that address range.
- let mut uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };
+ let mut uart = unsafe { Uart::new(PL011_BASE_ADDRESS) };
writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap();
diff --git a/src/bare-metal/aps/examples/src/main_rt.rs b/src/bare-metal/aps/examples/src/main_rt.rs
index d4e7024d..c9f29f32 100644
--- a/src/bare-metal/aps/examples/src/main_rt.rs
+++ b/src/bare-metal/aps/examples/src/main_rt.rs
@@ -68,7 +68,7 @@ fn main(x0: u64, x1: u64, x2: u64, x3: u64) -> ! {
}
#[panic_handler]
-fn panic(info: &PanicInfo) -> ! {
+fn panic(_info: &PanicInfo) -> ! {
system_off::().unwrap();
loop {}
}
diff --git a/src/bare-metal/aps/examples/src/main_safemmio.rs b/src/bare-metal/aps/examples/src/main_safemmio.rs
new file mode 100644
index 00000000..3d40cadd
--- /dev/null
+++ b/src/bare-metal/aps/examples/src/main_safemmio.rs
@@ -0,0 +1,66 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ANCHOR: main
+#![no_main]
+#![no_std]
+
+mod asm;
+mod exceptions;
+mod pl011;
+
+use crate::pl011::Uart;
+use core::fmt::Write;
+use core::panic::PanicInfo;
+use core::ptr::NonNull;
+use log::error;
+use safe_mmio::UniqueMmioPointer;
+use smccc::Hvc;
+use smccc::psci::system_off;
+
+/// Base address of the primary PL011 UART.
+const PL011_BASE_ADDRESS: NonNull =
+ NonNull::new(0x900_0000 as _).unwrap();
+
+// SAFETY: There is no other global function of this name.
+#[unsafe(no_mangle)]
+extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
+ // SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
+ // nothing else accesses that address range.
+ let mut uart = Uart::new(unsafe { UniqueMmioPointer::new(PL011_BASE_ADDRESS) });
+
+ writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap();
+
+ loop {
+ if let Some(byte) = uart.read_byte() {
+ uart.write_byte(byte);
+ match byte {
+ b'\r' => uart.write_byte(b'\n'),
+ b'q' => break,
+ _ => continue,
+ }
+ }
+ }
+
+ writeln!(uart, "\n\nBye!").unwrap();
+ system_off::().unwrap();
+}
+// ANCHOR_END: main
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ error!("{info}");
+ system_off::().unwrap();
+ loop {}
+}
diff --git a/src/bare-metal/aps/examples/src/pl011_struct.rs b/src/bare-metal/aps/examples/src/pl011_struct.rs
new file mode 100644
index 00000000..4b6a5cd9
--- /dev/null
+++ b/src/bare-metal/aps/examples/src/pl011_struct.rs
@@ -0,0 +1,167 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#![allow(dead_code)]
+
+use core::fmt::{self, Write};
+
+// ANCHOR: Flags
+use bitflags::bitflags;
+
+bitflags! {
+ /// Flags from the UART flag register.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ struct Flags: u16 {
+ /// Clear to send.
+ const CTS = 1 << 0;
+ /// Data set ready.
+ const DSR = 1 << 1;
+ /// Data carrier detect.
+ const DCD = 1 << 2;
+ /// UART busy transmitting data.
+ const BUSY = 1 << 3;
+ /// Receive FIFO is empty.
+ const RXFE = 1 << 4;
+ /// Transmit FIFO is full.
+ const TXFF = 1 << 5;
+ /// Receive FIFO is full.
+ const RXFF = 1 << 6;
+ /// Transmit FIFO is empty.
+ const TXFE = 1 << 7;
+ /// Ring indicator.
+ const RI = 1 << 8;
+ }
+}
+// ANCHOR_END: Flags
+
+bitflags! {
+ /// Flags from the UART Receive Status Register / Error Clear Register.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ struct ReceiveStatus: u16 {
+ /// Framing error.
+ const FE = 1 << 0;
+ /// Parity error.
+ const PE = 1 << 1;
+ /// Break error.
+ const BE = 1 << 2;
+ /// Overrun error.
+ const OE = 1 << 3;
+ }
+}
+
+// ANCHOR: Registers
+#[repr(C, align(4))]
+pub struct Registers {
+ dr: u16,
+ _reserved0: [u8; 2],
+ rsr: ReceiveStatus,
+ _reserved1: [u8; 19],
+ fr: Flags,
+ _reserved2: [u8; 6],
+ ilpr: u8,
+ _reserved3: [u8; 3],
+ ibrd: u16,
+ _reserved4: [u8; 2],
+ fbrd: u8,
+ _reserved5: [u8; 3],
+ lcr_h: u8,
+ _reserved6: [u8; 3],
+ cr: u16,
+ _reserved7: [u8; 3],
+ ifls: u8,
+ _reserved8: [u8; 3],
+ imsc: u16,
+ _reserved9: [u8; 2],
+ ris: u16,
+ _reserved10: [u8; 2],
+ mis: u16,
+ _reserved11: [u8; 2],
+ icr: u16,
+ _reserved12: [u8; 2],
+ dmacr: u8,
+ _reserved13: [u8; 3],
+}
+// ANCHOR_END: Registers
+
+// ANCHOR: Uart
+/// Driver for a PL011 UART.
+#[derive(Debug)]
+pub struct Uart {
+ registers: *mut Registers,
+}
+
+impl Uart {
+ /// Constructs a new instance of the UART driver for a PL011 device with the
+ /// given set of registers.
+ ///
+ /// # Safety
+ ///
+ /// The given pointer must point to the 8 MMIO control registers of a PL011
+ /// device, which must be mapped into the address space of the process as
+ /// device memory and not have any other aliases.
+ pub unsafe fn new(registers: *mut Registers) -> Self {
+ Self { registers }
+ }
+
+ /// Writes a single byte to the UART.
+ pub fn write_byte(&mut self, byte: u8) {
+ // Wait until there is room in the TX buffer.
+ while self.read_flag_register().contains(Flags::TXFF) {}
+
+ // SAFETY: We know that self.registers points to the control registers
+ // of a PL011 device which is appropriately mapped.
+ unsafe {
+ // Write to the TX buffer.
+ (&raw mut (*self.registers).dr).write_volatile(byte.into());
+ }
+
+ // Wait until the UART is no longer busy.
+ while self.read_flag_register().contains(Flags::BUSY) {}
+ }
+
+ /// Reads and returns a pending byte, or `None` if nothing has been
+ /// received.
+ pub fn read_byte(&mut self) -> Option {
+ if self.read_flag_register().contains(Flags::RXFE) {
+ None
+ } else {
+ // SAFETY: We know that self.registers points to the control
+ // registers of a PL011 device which is appropriately mapped.
+ let data = unsafe { (&raw const (*self.registers).dr).read_volatile() };
+ // TODO: Check for error conditions in bits 8-11.
+ Some(data as u8)
+ }
+ }
+
+ fn read_flag_register(&self) -> Flags {
+ // SAFETY: We know that self.registers points to the control registers
+ // of a PL011 device which is appropriately mapped.
+ unsafe { (&raw const (*self.registers).fr).read_volatile() }
+ }
+}
+// ANCHOR_END: Uart
+
+impl Write for Uart {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ for c in s.as_bytes() {
+ self.write_byte(*c);
+ }
+ Ok(())
+ }
+}
+
+// Safe because it just contains a pointer to device memory, which can be
+// accessed from any context.
+unsafe impl Send for Uart {}
diff --git a/src/bare-metal/aps/safemmio/driver.md b/src/bare-metal/aps/safemmio/driver.md
new file mode 100644
index 00000000..b7949eaa
--- /dev/null
+++ b/src/bare-metal/aps/safemmio/driver.md
@@ -0,0 +1,30 @@
+# Driver
+
+Now let's use the new `Registers` struct in our driver.
+
+```rust,editable,compile_fail
+{{#include ../examples/src/pl011.rs:Uart}}
+```
+
+
+
+- The driver no longer needs any unsafe code!
+- `UniqueMmioPointer` is a wrapper around a raw pointer to an MMIO device or
+ register. The caller of `UniqueMmioPointer::new` promises that it is valid and
+ unique for the given lifetime, so it can provide safe methods to read and
+ write fields.
+- Note that `Uart::new` is now safe; `UniqueMmioPointer::new` is unsafe instead.
+- These MMIO accesses are generally a wrapper around `read_volatile` and
+ `write_volatile`, though on aarch64 they are instead implemented in assembly
+ to work around a bug where the compiler can emit instructions that prevent
+ MMIO virtualisation.
+- The `field!` and `field_shared!` macros internally use `&raw mut` and
+ `&raw const` to get pointers to individual fields without creating an
+ intermediate reference, which would be unsound.
+- `field!` needs a mutable reference to a `UniqueMmioPointer`, and returns a
+ `UniqueMmioPointer` which allows reads with side effects and writes.
+- `field_shared!` works with a shared reference to either a `UniqueMmioPointer`
+ or a `SharedMmioPointer`. It returns a `SharedMmioPointer` which only allows
+ pure reads.
+
+
diff --git a/src/bare-metal/aps/safemmio/registers.md b/src/bare-metal/aps/safemmio/registers.md
new file mode 100644
index 00000000..6e89bd16
--- /dev/null
+++ b/src/bare-metal/aps/safemmio/registers.md
@@ -0,0 +1,37 @@
+# safe-mmio
+
+The [`safe-mmio`] crate provides types to wrap registers which can be read or
+written safely.
+
+| | Can't read | Read has no side-effects | Read has side-effects |
+| ----------- | ------------- | ------------------------ | --------------------- |
+| Can't write | | [`ReadPure`] | [`ReadOnly`] |
+| Can write | [`WriteOnly`] | [`ReadPureWrite`] | [`ReadWrite`] |
+
+
+
+```rust,editable,compile_fail
+{{#include ../examples/src/pl011.rs:Registers}}
+```
+
+- Reading `dr` has a side effect: it pops a byte from the receive FIFO.
+- Reading `rsr` (and other registers) has no side-effects. It is a 'pure' read.
+
+
+
+- There are a number of different crates providing safe abstractions around MMIO
+ operations; we recommend the `safe-mmio` crate.
+- The difference between `ReadPure` or `ReadOnly` (and likewise between
+ `ReadPureWrite` and `ReadWrite`) is whether reading a register can have
+ side-effects which change the state of the device. E.g. reading the data
+ register pops a byte from the receive FIFO. `ReadPure` means that reads have
+ no side-effects, they are purely reading data.
+
+
+
+[`safe-mmio`]: https://crates.io/crates/safe-mmio
+[`ReadOnly`]: https://docs.rs/safe-mmio/latest/safe_mmio/fields/struct.ReadOnly.html
+[`ReadPure`]: https://docs.rs/safe-mmio/latest/safe_mmio/fields/struct.ReadPure.html
+[`ReadPureWrite`]: https://docs.rs/safe-mmio/latest/safe_mmio/fields/struct.ReadPureWrite.html
+[`ReadWrite`]: https://docs.rs/safe-mmio/latest/safe_mmio/fields/struct.ReadWrite.html
+[`WriteOnly`]: https://docs.rs/safe-mmio/latest/safe_mmio/fields/struct.WriteOnly.html
diff --git a/src/bare-metal/aps/better-uart/using.md b/src/bare-metal/aps/safemmio/using.md
similarity index 51%
rename from src/bare-metal/aps/better-uart/using.md
rename to src/bare-metal/aps/safemmio/using.md
index a7187208..e5329a5a 100644
--- a/src/bare-metal/aps/better-uart/using.md
+++ b/src/bare-metal/aps/safemmio/using.md
@@ -1,14 +1,15 @@
-# Using it
+# Using It
Let's write a small program using our driver to write to the serial console, and
echo incoming bytes.
```rust,editable,compile_fail
-{{#include ../examples/src/main_improved.rs:main}}
+{{#include ../examples/src/main_safemmio.rs:main}}
```
-- Run the example in QEMU with `make qemu` under `src/bare-metal/aps/examples`.
+- Run the example in QEMU with `make qemu_safemmio` under
+ `src/bare-metal/aps/examples`.