寄存器概览

寄存器驱动程序应用于可由多个驱动程序访问的寄存器。

概念

驱动程序通常通过基于寄存器的接口与硬件进行通信。从概念上讲,寄存器是一组可通过地址空间读取或写入的 8/16/32/64 位。MMIO 寄存器通过 CPU 的内存地址空间访问,而 I2C 寄存器通过 I2C 总线访问。

大多数硬件仅支持原子寄存器写入。因此,想要更改寄存器中的几个位的驱动程序必须通过读取/修改/写入操作来进行更改。例如,对于 32 位寄存器,如果要将两个第 0 位都更改为 0,则需要读取该寄存器的全部 32 位(比如 0xFFFFFFFF),然后回写到该寄存器 0xFFFFFFFC

同步

读取/修改/写入操作可能会导致数据争用。典型的交易示例“从银行账户中提现 10 美元”实际上是一个读取/修改/写入操作。如需查看具体说明,请参阅示例部分中的数据争用

当寄存器为驱动程序的“专有”时,驱动程序应同步其对寄存器的访问,以避免数据争用。(可能仍然存在竞态,但这不在寄存器驱动程序的范围之内。)

当寄存器被多个车手“共享”(可能被访问)时,我们需要一个全局协调器来避免争用。当每个驱动程序只需要访问寄存器位的不相交子集时,寄存器驱动程序可以充当此全局协调器。全局协调器将在寄存器的读/写之间提供所需的同步。

隔离

寄存器驱动程序不仅可以通过提供同步来防止争用,还可以隔离位字段。寄存器驱动程序将寄存器拆分为多个资源,并确保每个驱动程序只能访问这些位。驱动程序可能不会意外地对不归您所有的位执行读取或写入操作。

运算理论

板级驱动程序中,可由多个驱动程序访问的寄存器声明了其要公开的不同位字段。寄存器驱动程序会为每个位字段创建设备。希望访问这些位字段的驱动程序需要绑定到相应的设备。

在寄存器驱动程序中,每个寄存器的锁都用于确保一次只有一个读/写操作可以访问寄存器。寄存器驱动程序的接口由 registers-util.fidl 定义。

使用方法

目前,重置寄存器是唯一迁移为使用寄存器驱动程序的寄存器。本部分将以它们为例,说明如何使用寄存器驱动程序。

  1. 板驱动程序变更

    元数据格式在 metadata.fidl 中声明。

    在板驱动程序(即 vim3-registers)中,进行以下更改。

    a. MMIO

    确保寄存器驱动程序能够访问 MMIO。否则,请添加 MMIO 和相应的 MMIO 索引:

      enum MmioMetadataIdx {
        kResetMmio,
    
        kMmioCount,
      };
    
      static const std::vector<fpbus::Mmio> registers_mmios{
          {
            {
              .base = A311D_RESET_BASE,
              .length = A311D_RESET_LENGTH,
            },
          },
      };
    

    请注意,MmioMetadataIndex 必须与 registers_mmios 中的 MMIO 索引相对应。

    b. 声明位字段

    RegistersMetadataToFidl 辅助函数可用于声明位字段:

      auto metadata_bytes = fidl_metadata::registers::RegistersMetadataToFidl<uint32_t>(kRegisters);
      if (metadata_bytes.is_error()) {
        zxlogf(ERROR, "%s: Failed to FIDL encode registers metadata %s\n", __func__,
               metadata_bytes.status_string());
        return metadata_bytes.error_value();
      }
    
      const std::vector<fpbus::Metadata> registers_metadata{
          {
            {
              .type = DEVICE_METADATA_REGISTERS,
              .data = metadata_bytes.value(),
            },
          },
      };
    

    其中位字段在 kRegisters 字段中定义:

      static const fidl_metadata::registers::Register<uint32_t> kRegisters[]{
          {
              .bind_id = aml_registers::REGISTER_USB_PHY_V2_RESET,
              .mmio_id = kResetMmio,
              .masks =
                  {
                      {
                          .value = aml_registers::USB_RESET1_REGISTER_UNKNOWN_1_MASK |
                                   aml_registers::USB_RESET1_REGISTER_UNKNOWN_2_MASK,
                          .mmio_offset = A311D_RESET1_REGISTER,
                      },
                      {
                          .value = aml_registers::USB_RESET1_LEVEL_MASK,
                          .mmio_offset = A311D_RESET1_LEVEL,
                      },
                  },
          },
          ...
      };
    
    • bind_id:每个位字段定义的唯一 ID,用于在绑定期间标识它。
    • mmio_id:与此位字段引用的 MMIO 对应的 ID。
    • masks:描述应可通过此寄存器设备访问的特定位字段的掩码列表。
      • value:可供访问的位掩码,1 表示可访问,而 0 不可访问。例如,对于 32 位寄存器,位掩码可以是 0xFFFF0000,这意味着可通过此寄存器设备访问寄存器的高 16 位,而不能访问低 16 位。
      • mmio_offset:从 MMIO 的起始位置开始的偏移量(由这些位字段定义的 mmio_id 标识)。
      • count:此位字段掩码应用于后续 mmio_offset 的寄存器地址数量。默认为 1
      • overlap_check_on:如果为 true,寄存器驱动程序将验证这些位字段是否不与任何其他已定义的位字段重叠。否则,系统会跳过检查。默认值为 true

    这些寄存器驱动程序将创建一个设备,用于通过绑定属性提供 registers-util.fidl{:.devsite-disable-click-to-copy} bind_fuchsia_register::NAME == bind_id

  2. 绑定到寄存器驱动程序

    然后,希望访问这些位字段的驱动程序必须绑定到上面创建的设备。

  3. 使用寄存器驱动程序接口

    成功连接到寄存器设备后(例如通过 fidl::WireSyncClient<fuchsia_hardware_registers::Device> register),您可以根据 registers-util.fidl 中定义的 FIDL 接口调用相应的 FIDL 读/写。例如,对于 32 位寄存器,

    auto result =
        reset_register_->WriteRegister32(RESET1_LEVEL_OFFSET, aml_registers::USB_RESET1_LEVEL_MASK,
                                         aml_registers::USB_RESET1_LEVEL_MASK);
    if ((result.status() != ZX_OK) || result->is_error()) {
      zxlogf(ERROR, "Write failed\n");
      return ZX_ERR_INTERNAL;
    }
    
  4. 尽情享受隔离的同步寄存器吧!

示例

数据争用

假设驱动程序 A 和驱动程序 B 可以访问一个寄存器。现在,驱动程序 A 希望将 b01 写入寄存器的位 0-1,而驱动程序 B 希望将 b001 写入寄存器的位 8-10。根据定时驱动程序 A 可能会读取寄存器的原始值(例如 0xFFFFFFFF),而驱动程序 B 也可能会读取相同的值。然后,驱动程序 A 会向位 0-1 写入数据,并将其放入内存 0xFFFFFFFD 中。然后,驱动程序 B 会向内存 0xFFFFF9FF 写入数据。在这个事件序列中,寄存器现在存储了 0xFFFFF9FF 的值,驱动程序 B 最后一次写入这个值。不过,下次驱动程序 A 读取此寄存器的位 0-1 时,它将不会像之前写入时那样获得预期值。

AMLogic SoC

本部分举例说明了应该和不应该在哪些位置使用寄存器驱动程序。当需要在多个驱动程序之间进行同步时,一定要使用寄存器驱动程序,该驱动程序不一定仅用于资源隔离。请注意,以下并非 AMLogic SoC 中共享寄存器的完整列表。

重置寄存器

AMLogic SoC 具有适用于各种硬件单元的重置功能,这些单元集中在几个 32 位寄存器中。例如 S905D3 表 6-186 中的 RESET1_REGISTER。例如,USB 重置由位 2 控制,由位 12-14 控制 SD_EMMC。硬件设计迫使我们在多个驱动程序(包括 EMMC 驱动程序和 USB 驱动程序)之间共享 RESET1_REGISTER

如需了解针对重置寄存器所做的板级文件更改,请参阅 vim3-registers;有关添加重置寄存器 fragment 的 aml_usb_phy 设备,请参阅 vim3-usbaml-usb-phyAmlUsbPhy::InitPhy() 使用 FIDL 客户端写入重置寄存器。

电源寄存器

与上述重置寄存器类似,AO_RTI_GEN_PWR_SLEEP0S905D3 表 6-17)和 AO_RTI_GEN_PWR_ISO0S905D3 表 6-18)由多个驱动程序(包括显示屏、USB 和机器学习)共享,应由寄存器驱动程序管理,因为它们可写入且需要协调。此迁移目前正在进行中。

另一方面,虽然 AO_RTI_GEN_PWR_ACK0S905D3 表 6-19)将由多个驱动程序(PCIE、USB、显示屏等)共享,但它是只读的。可以并发访问此寄存器中的数据,而没有任何争用风险,因此无需使用寄存器驱动程序进行同步。如果需要,我们仍然可以使用寄存器驱动程序来隔离资源。