23 个版本

0.1.22 2024 年 1 月 14 日
0.1.21 2023 年 8 月 8 日
0.1.19 2023 年 7 月 19 日
0.1.13 2023 年 3 月 14 日
0.1.10 2022 年 11 月 26 日

#66 in 硬件支持

Download history 324/week @ 2024-04-23 224/week @ 2024-04-30 198/week @ 2024-05-07 195/week @ 2024-05-14 328/week @ 2024-05-21 305/week @ 2024-05-28 244/week @ 2024-06-04 334/week @ 2024-06-11 259/week @ 2024-06-18 295/week @ 2024-06-25 282/week @ 2024-07-02 312/week @ 2024-07-09 414/week @ 2024-07-16 351/week @ 2024-07-23 308/week @ 2024-07-30 201/week @ 2024-08-06

1,314 每月下载量
7 个 crates 中使用 (通过 utralib)

MIT/Apache

49KB
961

明确的薄寄存器抽象 (UTRA)

动机

UTRA 是一种用于访问硬件资源的寄存器抽象。它试图做到

  • 明确无误 —— 访问规则应该简洁且对具有 C 语言背景的系统程序员来说是明确的
  • 薄 —— 它应该隐藏常量,但不至于难以验证

以下是一个关于寄存器访问的模糊风格示例,来自使用 svd2rust 生成的 PAC

    // this seems clear -- as long as all the bit fields are specified
    // (they actually aren't, so some non-obvious things are happening)
    p.POWER.power.write(|w| 
       w.discharge().bit(true)
        .soc_on().bit(false)
        .kbddrive().bit(true)
        .kbdscan().bits(3)
      );

    // what should this do?
    // 1. just set the discharge bit to true and everything else to zero?
    // 2. read the register first, change only the discharge bit to true, leaving the rest unchanged?
    p.POWER.power.write(|w| 
       w.discharge().bit(true)
      );
      
    // answer: it does (1). You need to use the `modify()` function to have (2) happen.

虽然闭包链式调用语法很巧妙,但它也是模糊的。首先,链式调用是否意味着写入操作按顺序发生,还是它们同时发生?这取决于 Rust 的优化器,通常期望行为是后者,但这仍然是一种依赖于优化器的写入顺序行为,而不是语言保证的。其次,当涉及到位域时,“写入”这个词本身是模糊的:我们只写入位域,还是假设其余内容为零写入整个寄存器?这些类型的模糊性使代码审计变得困难,尤其是对于既是系统编程专家又是 Rust 专家的人来说。

为了实现明确性和薄性,我们愿意牺牲一些类型检查和类型硬化,因为我们没有充分利用 Rust 的高级语法特性。

尽管如此,在寄存器抽象中保持一定的灵活性是有益的,以帮助进行面向安全的审计。对于安全审计,了解未定义位的行为与检查定义位的行为同样重要。灵活性允许审计员快速创建针对未定义位的针对性测试。现有的基于 Rust 的访问 crate 创建了严格的类型,消除了将错误类型应用于寄存器的错误情况,但同时也使得以非正式方式修改非常困难。

API

本软件包旨在作为 svd2rust 的替代方案。它生成的软件包包含以下内容

  1. 一个库,用于执行寄存器访问
  2. 一个“头文件”库(库),它从给定的 soc.svd 文件自动生成

该库提供了一个用于 CSR 的函数模板,该模板提供了以下方法

  • .r(reg: Register) -> T - 读取。读取 CSR 的全部内容
  • .rf(field: Field) -> T - 读取字段。读取 CSR 并返回子字段掩码和移位后的值
  • .wo(reg: Register, value:T) - 只写。将 value 写入寄存器,替换其全部内容
  • .wfo(field: Field, value:T) - 只写字段。将 value 写入寄存器的字段,将其他字段清零并替换其全部内容
  • .rmwf(field: Field, value:T) - 读取-修改-写入寄存器。只替换 field 的内容,而保留其他字段。当前实现不保证原子性。

RegisterField 由库生成;Field 指的是它所属的 Register,因此不需要明确指定它。此外,当创建对象时,CSR 的基址被绑定,这使得软件包既能与物理地址一起工作,也能与虚拟地址一起工作,通过将基址替换为所需的值,根据活动寻址模式。

除了 CSR 函数模板外,该软件包还生成了 CSR 基址、任何内存基址和中断的便利常量。

此组 API 调用支持最常见的用例,即读取、写入和更新寄存器的单个字段,或一次性写入整个寄存器。

API 默认不支持同时设置两个字段。这是因为这取决于硬件实现,可能存在细微差别,例如自动重置的位字段、在读取时自动清除的寄存器或具有其他自动和隐式副作用的其他寄存器。

需要同时设置多个位字段的用户必须显式读取 CSR 值,将其绑定到临时变量,屏蔽他们想要替换的字段,然后在将其写回 CSR 之前组合这些值。

为此,还提供了以下辅助函数

  • zf(field:Field, value:T) -> T - 清零字段。取与 field 对应的位,并在 value 中将其置零,其他位保持不变
  • ms(field:Field, value:T) -> T - 掩码和移位。取值,将其掩码到字段宽度,然后移位到最终位置。

这里的思路是使用 .r(register) 方法读取整个寄存器;然后连续调用 .zf(field, value) 清除设置之前的字段。字段值与 .ms(field, value) 的结果进行或运算,以创建最终的复合寄存器值。最后,使用 .wo(value) 将最终复合寄存器值写入整个寄存器。

.ms(field,value) 也可以用于生成需要一次性写入硬件寄存器的初始寄存器值,在 .wo(value) 调用之前。

示例用法

假设您已使用 svd2utra.py 在与 svd2utra.py 相同的目录中创建了一个 utra crate,并且您已将其添加到您的 Cargo.toml 文件中。现在,在您的 lib.rs 文件中,您可能会有以下内容

use utra

fn test_fn() {
        // Audio tests

        // The audio block is a pointer to *mut 32.
        let mut audio = CSR::new(HW_AUDIO_BASE as *mut u32);

        // Read the entire contents of the RX_CTL register
        audio.r(utra::audio::RX_CTL);

        // Or read just one field
        audio.rf(utra::audio::RX_CTL_ENABLE);

        // Do a read-modify-write of the specified field
        audio.rmwf(utra::audio::RX_CTL_RESET, 1);

	// Do a multi-field operation where all fields are updated in a single write.
	// First read the field into a temp variable.
        let mut stat = audio.r(utra::audio::RX_STAT);
	// in read replica, zero EMPTY and RDCOUNT
	stat = audio.zf(utra::audio::RX_STAT_EMPTY, stat);
	stat = audio.zf(utra::audio::RX_STAT_RDCOUNT, stat);
	// in read replica, now set RDCOUNT to 0x123
	stat |= audio.ms(utra::audio::RX_STAT_RDCOUNT, 0x123);
	// commit read replica to register, updating both EMPTY and RDCOUNT in a single write
	audio.wo(utra::audio::RX_STAT, stat);

        // UART tests

        // Create the UART register as a pointer to *mut u8
        let mut uart = CSR::new(HW_UART_BASE as *mut u8);

        // Write the RXTX field of the RXTX register
        uart.wfo(utra::uart::RXTX_RXTX, b'a');

        // Or you can write the whole UART register
        uart.wo(utra::uart::RXTX, b'a');
        assert_ne!(uart.rf(pac::uart::TXFULL_TXFULL), 1);

        // Anomalies

        // This compiles but requires a cast since `audio` is a pointer to
        // u32, whereas `uart` is a pointer to u8.
        audio.wfo(utra::uart::RXTX_RXTX, b'a' as _);

        // This also compiles, despite the fact that the register offset is
        // mismatched and nonsensical
        audio.wfo(utra::uart::TXFULL_TXFULL, 1);
}

依赖关系

~1.5MB
~21K SLoC