寄存器概览

对于可能被多个驱动程序访问的寄存器,应使用寄存器驱动程序

概念

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

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

同步

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

当寄存器由驱动程序“独占”时,驱动程序应同步其对寄存器的访问,以避免出现数据争用。(仍存在发生竞态条件的可能性,但不在寄存器驱动程序的范围内)。

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

隔离

寄存器驱动程序不仅通过提供同步来防止竞争,还提供位字段隔离。寄存器驱动程序将一个寄存器拆分为多个资源,并确保每个驱动程序仅有权访问这些位。驱动程序不得意外读取或写入其不拥有的位。

运行原理

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

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

使用方法

目前,只有重置寄存器迁移到使用寄存器驱动程序。本部分将以它们为例,介绍如何使用寄存器驱动程序。

  1. 板级驱动程序变更

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

    在主板驱动程序(即 nelson-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 辅助函数可用于声明位字段:

      
      zx::result metadata = fidl_metadata::registers::RegistersMetadataToFidl(kRegisters);
      if (!metadata.is_ok()) {
        zxlogf(ERROR, "Failed to convert registers to metadata %s", metadata.status_string());
        return metadata.error_value();
      }
    
      fit::result persisted_metadata = fidl::Persist(metadata.value());
      if (!persisted_metadata.is_ok()) {
        zxlogf(ERROR, "Failed to persist registers metadata: %s",
              persisted_metadata.error_value().FormatDescription().c_str());
        return persisted_metadata.error_value().status();
      }
    
      std::vector<fpbus::Metadata> registers_metadata{
          {{
                  .id = fuchsia_hardware_registers::Metadata::kSerializableName,
                  .data = std::move(persisted_metadata.value()),
              }},
      };
      
    

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

      static const fidl_metadata::registers::Register<uint32_t> kRegisters[]{
          {
              .name = bind_fuchsia_amlogic_platform::NAME_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,
                      },
                  },
          },
          ...
      };
    
    • name:每个位字段定义的唯一名称,用于在绑定期间标识该位字段。
    • 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::NAME == name

  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。在此事件序列中,寄存器现在保存的是驱动程序 B 上次写入的值 0xFFFFF9FF。不过,当驱动程序 A 下次读取此寄存器的位 0-1 时,它将无法获得之前写入的预期值。

AMLogic SoC

本部分提供了一些示例,说明了注册驱动程序应在哪些情况下使用,以及不应在哪些情况下使用。当需要多个驱动程序之间进行同步时,肯定应使用寄存器驱动程序,并且可能仅用于隔离资源,也可能不用于隔离资源。请注意,以下并非 AMLogic SoC 中共享寄存器的完整列表。

重置寄存器

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

如需了解为重置寄存器而进行的板文件更改,请参阅 nelson-registers;如需了解为添加重置寄存器 fragment 而进行的板文件更改,请参阅 nelson-usb - aml_usb_phy 设备。aml-usb-phyAmlUsbPhy::InitPhy() 使用 FIDL 客户端写入重置寄存器。

功率寄存器

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

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