资讯动态

comprehensive-rust 裸金属 AP 教程:用 safe-mmio 把 PL011 UART 驱动写成纯 safe 代码

发布时间:2026/9/10 7:32:12 来源:尧图企业网站定制
comprehensive-rust 裸金属 AP 教程用 safe-mmio 把 PL011 UART 驱动写成纯 safe 代码【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust本文围绕 safemmio/driver.md 的核心内容展开在 comprehensive-rust 课程的裸金属bare-metal AP章节中讲解如何利用safe-mmiocrate 提供的UniqueMmioPointer、field!与field_shared!机制把此前依赖unsafe易变访问的 PL011 UART 驱动重写为除构造边界外完全无unsafe的 safe 代码。读完本文你能掌握 MMIO 寄存器访问的安全抽象模型、驱动中unsafe的收口位置以及通过 QEMU 实际构建运行该驱动的完整流程。问题背景手写 UART 驱动的 unsafe 之痛在 mmio.md 一节中课程给出了裸金属访问 MMIO 寄存器的手动方案直接使用pointer::read_volatile与pointer::write_volatile并明确要求永远不要持有正在被易变访问的内存位置的引用因为 Rust 可能在任意时刻读取对mut则写入一个引用用raw从结构体指针获取字段指针而不产生中间引用。手动方案的问题在于每一个字段的读、写、类型、副作用语义都需要程序员逐次保证而且驱动内部会遍布unsafe块编译器无法替你拦截任何一次多读一次或少读一次。safe-mmio的做法是把这块地址有效且唯一的承诺一次性上移到构造点其余访问全部由类型系统约束。safe-mmio 的寄存器类型模型在写驱动之前registers.md 一节定义了驱动所依赖的核心概念safe-mmiocrate 提供了一组包装寄存器访问的类型按读是否有副作用、能否写划分为五种不能读读无副作用读有副作用不能写ReadPureReadOnly能写WriteOnlyReadPureWriteReadWriteReadPure与ReadOnly以及ReadPureWrite与ReadWrite的区别在于读操作是否会改变设备状态。课程以 PL011 为例说明读dr数据寄存器有副作用——它会从接收 FIFO 弹出一个字节而读rsr接收状态寄存器等其他寄存器没有副作用是纯读。仓库中 examples/Cargo.toml 固定使用safe-mmio 0.3.0依赖。基于这个模型PL011 的寄存器布局被声明为一个普通的 Rust 结构体完整定义见 pl011.rs其中Registers定义位于 L73-L103#[repr(C, align(4))] pub struct Registers { dr: ReadWriteu16, _reserved0: [u8; 2], rsr: ReadPureReceiveStatus, _reserved1: [u8; 19], fr: ReadPureFlags, _reserved2: [u8; 6], ilpr: ReadPureWriteu8, _reserved3: [u8; 3], ibrd: ReadPureWriteu16, _reserved4: [u8; 2], fbrd: ReadPureWriteu8, _reserved5: [u8; 3], lcr_h: ReadPureWriteu8, _reserved6: [u8; 3], cr: ReadPureWriteu16, _reserved7: [u8; 3], ifls: ReadPureWriteu8, _reserved8: [u8; 3], imsc: ReadPureWriteu16, _reserved9: [u8; 2], ris: ReadPureu16, _reserved10: [u8; 2], mis: ReadPureu16, _reserved11: [u8; 2], icr: WriteOnlyu16, _reserved12: [u8; 2], dmacr: ReadPureWriteu8, _reserved13: [u8; 3], }几个值得注意的类型学细节#[repr(C, align(4))]保证字段偏移与硬件寄存器布局一一对应_reservedN数组占据保留字节fr标志寄存器的类型不是裸u16而是ReadPureFlags其中Flags是用bitflags宏定义的位标志结构CTS、RXFE、TXFF、BUSY等 9 个位见 pl011.rs L19-L50并通过zerocopy的FromBytes/IntoBytes完成内存到类型的安全转换rsr使用ReadPureReceiveStatus表示读它不会清空错误是纯状态查询icr中断路由寄存器写以清除中断标记为WriteOnlyu16——类型上根本不允许读它从根上杜绝了误读导致状态改变这类硬件错误。Driver 主体一个没有任何 unsafe 的 UART 驱动现在使用上面的Registers结构体编写驱动driver.md 的核心代码完整实现见 pl011.rs L106-L159 的Uart锚点use safe_mmio::{UniqueMmioPointer, field, field_shared}; /// Driver for a PL011 UART. #[derive(Debug)] pub struct Uarta { registers: UniqueMmioPointera, Registers, } impla Uarta { /// Constructs a new instance of the UART driver for a PL011 device with the /// given set of registers. pub fn new(registers: UniqueMmioPointera, 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) {} // Write to the TX buffer. field!(self.registers, dr).write(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) - Optionu8 { if self.read_flag_register().contains(Flags::RXFE) { None } else { let data field!(self.registers, dr).read(); // TODO: Check for error conditions in bits 8-11. Some(data as u8) } } fn read_flag_register(self) - Flags { field_shared!(self.registers, fr).read() } }此外驱动还实现了core::fmt::Writepl011.rs L152-L159使writeln!(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(()) } }对照 driver.md 中的讲解这里有几个关键设计点驱动不再需要任何unsafe代码。整个Uart实现中找不到一个unsafe块所有对寄存器的读、写、等待都通过 safe API 完成。UniqueMmioPointer是对 MMIO 设备或寄存器裸指针的包装。UniqueMmioPointer::new的调用者承诺该地址在给定生命周期内有效且唯一正因这个一次性承诺它才能提供安全的字段读写方法。Uart::new是 safe 的unsafe 转移到了UniqueMmioPointer::new。这是本驱动unsafe 收口的核心危险承诺只在一处发生且必须写在unsafe { ... }里并附带 SAFETY 注释。底层访问语义。这些 MMIO 访问通常是对read_volatile/write_volatile的封装但在 aarch64 上它们用汇编实现以规避一个编译器可能发出破坏 MMIO 虚拟化指令的 bug。field!与field_shared!的内部机制。两个宏内部使用raw mut与raw const直接取得指向单个字段的指针而不创建中间引用——因为对易变 MMIO 位置持有中间引用是不健全的这正是 mmio.md 中警告的问题。两个宏的能力边界不同field!需要UniqueMmioPointer的可变引用返回一个UniqueMmioPointer允许有副作用的读如读dr弹出 FIFO 字节与写field_shared!接受UniqueMmioPointer或SharedMmioPointer的共享引用返回一个SharedMmioPointer只允许纯读。从源码结构看这种能力分级在Uart中体现得很清晰write_byte/read_byte拿mut self因为要操作dr读写、有副作用而read_flag_register只需要self因为fr是纯读的ReadPureFlags用field_shared!即可。也就是说能不能共享借用直接由寄存器类型决定调用者无法通过误用破坏设备状态。unsafe 的唯一入口构造 UniqueMmioPointer驱动侧完全 safe 之后唯一的unsafe出现在使用侧。main_safemmio.rs 展示了一个完整程序写串口控制台并回显收到的字节/// Base address of the primary PL011 UART. const PL011_BASE_ADDRESS: NonNullpl011::Registers 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), bq break, _ continue, } } } writeln!(uart, \n\nBye!).unwrap(); system_off::Hvc().unwrap(); }这里的 SAFETY 注释精确对应了UniqueMmioPointer::new的两条承诺地址确实映射了一个 PL011 设备QEMUvirt机器的主串口基址为0x9000000且程序中没有任何其他代码访问该地址区间。Uart结构体携带的生命周期参数a则把独占访问约束在编译器层面延续到后续所有write_byte/read_byte调用。构建与运行在 QEMU 中验证驱动示例工程 examples/Cargo.toml 中定义了名为safemmio的二进制目标path src/main_safemmio.rs其依赖还包括smccc用于system_off::Hvc()关机与log。构建与运行方式见 examples/Makefilesafemmio.bin: build cargo objcopy --bin safemmio -- -O binary $ qemu_safemmio: safemmio.bin qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $ -s即在src/bare-metal/aps/examples目录下执行make qemu_safemmio先cargo build再用cargo objcopy把safemmio目标转成裸二进制safemmio.bin最后交给 QEMU 以virt机器、maxCPU 启动串口直连标准输入输出-serial mon:stdio。运行后程序会打印 QEMU 传入的x0~x3寄存器值然后进入回显循环按q退出并打印 Bye!再通过 PSCIsystem_off关机。前置条件是安装 Rust 工具链含aarch64-unknown-none-softfloat目标与qemu-system-aarch64。小结driver.md 一节展示了裸金属驱动开发的理想形态安全承诺在UniqueMmioPointer::new处一次性完成寄存器语义纯读 / 有副作用读 / 只写固化在Registers类型中日常驱动逻辑全部是 safe 代码。配套材料可继续沿 registers.md类型模型与 using.md程序示例深入完整源码则集中在 pl011.rs 与 main_safemmio.rs。相比手写read_volatile的方案这套模式把不要持有中间引用不要误读有副作用的寄存器这类纪律从人的记忆转移到了类型系统与宏的强制约束中是no_std环境下编写可维护硬件驱动的推荐实践。【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价