註冊總覽

如果多個驅動程式可能會存取暫存器,則應使用「暫存器驅動程式」

概念

驅動程式通常會透過以暫存器為基礎的介面與硬體通訊。 從概念上來說,暫存器是 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_id 識別的 MMIO 開頭開始的偏移量,這些位元欄位會定義該偏移量。
      • 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。在這一連串事件中,暫存器現在保存的值是 0xFFFFF9FF,這是驅動程式庫 B 最後寫入的值。不過,當驅動程式庫 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,如要瞭解為新增重設暫存器片段而進行的變更,請參閱 nelson-usb - aml_usb_phy 裝置。aml-usb-phyAmlUsbPhy::InitPhy() 會使用 FIDL 用戶端寫入重設暫存器。

Power Registers

與上述重設暫存器類似,AO_RTI_GEN_PWR_SLEEP0 (S905D3 表 6-17) 和 AO_RTI_GEN_PWR_ISO0 (S905D3 表 6-18) 由多個驅動程式共用,包括螢幕、USB 和 ML,且應由暫存器驅動程式庫程式管理,因為這些暫存器可寫入且需要協調。這項遷移作業正在進行中。

另一方面,雖然 AO_RTI_GEN_PWR_ACK0 (S905D3 表 6-19) 會由多個驅動程式 (PCIE、USB、螢幕等) 共用,但它是唯讀的。您可以同時存取這個暫存器中的資料,不會有任何競爭條件的風險,因此不需要使用暫存器驅動程式庫進行同步處理。如有需要,我們仍可使用暫存器驅動程式庫來隔離資源。