註冊總覽

註冊驅動程式應用於可能由多個驅動程式存取的註冊裝置。

概念

驅動程式通常會透過註冊式介面與硬體通訊。從概念上來說,註冊是一組 8/16/32/64 位元,可透過位址空間讀取或寫入。MMIO 暫存器可透過 CPU 的記憶體位址空間存取,I2C 暫存器則可透過 I2C 匯流排存取。

多數硬體僅支援不可部分完成的暫存器寫入作業。因此,如果驅動程式需要變更暫存器的幾個位元,就必須透過讀取/修改/寫入作業進行。舉例來說,如果是 32 位元暫存器,如果要同時將兩者都變更為 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. 宣告 Bitfields

    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_id 定義的 MMIO 開頭的起始偏移值,這些位元欄位定義了 MMIO。
      • 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 和 SD_EMMC 以 位元 12-14 控制。硬體設計促使我們在多個驅動程式之間 (包括 EMMC 驅動程式庫和 USB 驅動程式庫) 共用 RESET1_REGISTER

如要瞭解對重設登錄作業進行的主面板檔案變更,請參閱 vim3-registervim3-usb - aml_usb_phy 裝置,瞭解如何新增重設的登錄片段。aml-usb-phyAmlUsbPhy::InitPhy() 會使用 FIDL 用戶端寫入重設暫存器。

電源暫存器

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

另一方面,雖然 AO_RTI_GEN_PWR_ACK0 (S905D3 表 6-19) 會由多個驅動程式 (PCIE、USB、螢幕等) 共用,但屬於唯讀性質。這個登錄的資料可以並行存取,不會造成競爭風險,因此不需要使用註冊驅動程式庫進行同步處理。如有需要,我們仍可透過註冊驅動程式庫隔離資源。