在 DFv2 驅動程式庫中設定 Compat 裝置伺服器

本指南提供相關操作說明,說明如何在 DFv2 驅動程式庫中設定及使用相容裝置伺服器,與 DFv1 驅動程式通訊。

在遷移過程中,相容裝置伺服器可協助 DFv2 驅動程式維持與 DFv1 驅動程式的相容性。相容性裝置伺服器提供 fuchsia_driver_compat::Device 介面 (請參閱 compat.fidl)。 這個介面可讓 DFv2 驅動程式將資源提供給子項 DFv1 驅動程式。

相容裝置伺服器的主要功能如下:

  • 資源共用:從 DFv2 驅動程式庫提供資源給後代 DFv1 驅動程式,過程順暢無礙。

  • Banjo 服務:在 DFv2 驅動程式庫中實作 Banjo 通訊協定,並提供給 DFv1 驅動程式。

  • 中繼資料處理:轉送、新增及剖析 DFv1 中繼資料,以便在 DFv2 和 DFv1 驅動程式之間通訊。

本指南提供逐步說明和範例,協助您完成下列工作:

設定相容裝置伺服器

本節提供相容裝置伺服器設定操作說明,以 DFv2 Simple 驅動程式庫範例為基礎。

步驟如下:

  1. 將相容裝置伺服器指定為依附元件
  2. 同步或非同步初始化相容裝置伺服器
  3. 將相容裝置伺服器提供給目標子節點

1. 將相容性裝置伺服器指定為依附元件

如要在 DFv2 驅動程式庫中將相容裝置伺服器指定為依附元件,請執行下列操作:

  1. BUILD.gn 中,將下列依附元件新增至 fuchsia_driver 目標:

    "//sdk/lib/driver/compat/cpp",
    
  2. 在驅動程式庫的來源檔案中加入 device_server.h 標頭:

    #include <lib/driver/compat/cpp/device_server.h>
    
  3. 在驅動程式庫的元件資訊清單 (.cml) 中加入 driver_component/driver.shard.cml 分片,例如:

     include: [
         "driver_component/driver.shard.cml",
         "inspect/client.shard.cml",
         "syslog/client.shard.cml",
     ],
    

    (來源:simple_driver.cml)

    這個分片會定義 fuchsia.driver.compat.Serviceusecapabilitiesexpose 欄位。

完成這項設定後,DFv2 驅動程式庫現在可以為子代節點建立 DeviceServer 物件。每個 DeviceServer 物件只能有一個目標節點。

2. 同步或非同步初始化相容裝置伺服器

同步或非同步初始化 DeviceServer 物件。根據預設,建議使用同步而非非同步初始化。

不過,如果符合下列任一條件,建議使用非同步初始化

  • 不允許在目前的調度器上進行同步或封鎖呼叫。

  • 您的 DFv1 驅動程式庫已建構為非同步程式碼,且您需要非同步行為帶來的效能提升。

同步初始化

如要進行同步初始化,請執行下列步驟:

  1. SyncInitializedDeviceServer 物件新增至類別,例如:

    class SimpleDriver : public fdf::DriverBase2 {
     ...
        compat::SyncInitializedDeviceServer compat_server_;
     ...
    }
    
  2. 在驅動程式庫實作中,呼叫這個 DeviceServer 物件的 Initialize() 函式:

    zx::result<> Initialize(const std::shared_ptr<fdf::Namespace>& incoming,
                              const std::shared_ptr<fdf::OutgoingDirectory>& outgoing,
                              const std::optional<std::string>& node_name,
                              std::string_view child_node_name,
                              const ForwardMetadata& forward_metadata = ForwardMetadata::None(),
                              std::optional<DeviceServer::BanjoConfig> banjo_config = std::nullopt,
                              const std::optional<std::string>& child_additional_path = std::nullopt);
    

    這個函式的參數如下:

    • Start 期間,您可以擷取並轉換 DriverContext 的傳入命名空間,藉此傳遞 incoming 的值。您可以呼叫 DriverBase2outgoing() 存取子,傳遞 outgoing 的值。您可以叫用 DriverContextnode_name() 存取子,傳遞 node_name 的值。

    • child_node_name 是這個 DeviceServer 物件的目標子節點名稱。

      不過,如果 DFv2 驅動程式庫程式節點與目標子項節點之間有任何中介節點,您必須將 child_additional_path 設為以 / 分隔的節點間拓撲路徑。舉例來說,如果目標子節點之前有 node-anode-b,則 child_additional_path 的值必須為 node-a/node-b/

    • forward_metadata 包含要從父節點轉送的中繼資料相關資訊。(詳情請參閱「轉送中繼資料」一文)。

    • banjo_config 包含將 Banjo 通訊協定提供給目標子節點的資訊。不過,如果裝置伺服器未提供通訊協定,您可以將參數設為 std::nullopt。(詳情請參閱「為衍生 DFv1 驅動程式提供 Banjo 服務」一文)。

    以下範例會初始化同步相容裝置伺服器物件:

    // Initialize our compat server.
      {
        zx::result<> result = compat_server_.Initialize(
           incoming(), outgoing(), node_name(), child_name);
        if (result.is_error()) {
          return result.take_error();
        }
      }
    

非同步初始化

如要進行非同步初始化,請執行下列步驟:

  1. AsyncInitializedDeviceServer 物件新增至類別,例如:

    class SimpleDriver : public fdf::DriverBase2 {
     ...
        compat::AsyncInitializedDeviceServer compat_server_;
     ...
    }
    
  2. 在驅動程式庫實作中,呼叫這個 DeviceServer 物件的 Begin() 函式:

    void Begin(const std::shared_ptr<fdf::Namespace>& incoming,
                 const std::shared_ptr<fdf::OutgoingDirectory>& outgoing,
                 const std::optional<std::string>& node_name, std::string_view child_node_name,
                 fit::callback<void(zx::result<>)> callback,
                 const ForwardMetadata& forward_metadata = ForwardMetadata::None(),
                 std::optional<DeviceServer::BanjoConfig> banjo_config = std::nullopt,
                 const std::optional<std::string>& child_additional_path = std::nullopt);
    

    除了 callback 欄位,這些參數與上述同步 Initialize() 函式幾乎完全相同。

    DeviceServer 物件完成初始化並可供存取時,系統會叫用回呼函式。建議您使用這個回呼函式新增子節點。其他參數值的填入方式與同步初始化相同。

    以下範例會初始化非同步相容裝置伺服器物件:

    void SimpleDriver::OnDeviceServerInitialized(zx::result<> device_server_init_result) {
       // Add the child nodes here
       async_completer_.value()(zx::ok());
       async_completer_.reset();
    }
    
    void SimpleDriver::Start(fdf::DriverContext context, fdf::StartCompleter completer) {
       auto incoming = std::shared_ptr<fdf::Namespace>(context.take_incoming());
       async_completer_.emplace(std::move(completer));
       device_server_.Begin(incoming, outgoing(), context.node_name(), child_name,
       fit::bind_member<&SimpleDriver::OnDeviceServerInitialized>(this),
       compat::ForwardMetadata::Some({DEVICE_METADATA_PRIVATE}));
    }
    

3. 將相容裝置伺服器提供給目標子節點

初始化相容裝置伺服器後,將其提案傳遞至目標子節點。

不過,如果目標節點不是直接子項,您需要將提案傳遞至後代鏈中最接近的下一個子項。舉例來說,節點拓撲會顯示 A -> B -> C -> D,如果節點 A 是目前的節點,而節點 D 是目標節點,則必須將提案傳遞至節點 B,也就是鏈結中最接近的子項節點。

使用 DeviceServer 物件的 CreateOffers2() 函式,在 NodeAddArgs 結構體中設定 offers2 欄位,例如:

auto properties = std::vector{fdf::MakeProperty(BIND_PROTOCOL, ZX_PROTOCOL_SERIAL_IMPL_ASYNC)};
zx::result child_result = AddChild(child_name, properties, compat_server_.CreateOffers2());
if (child_result.is_error()) {
  return child_result.take_error();
}

如有其他優惠,請先將這些優惠新增至 DeviceServer 物件的 offers,然後再設定 NodeAddArgs 結構中的 offers2 欄位,例如:

auto offers = compat_server_.CreateOffers2();
offers.push_back(fdf::MakeOffer2<fuchsia_hardware_serialimpl::Service>(child_name));

auto properties = std::vector{fdf::MakeProperty(BIND_PROTOCOL, ZX_PROTOCOL_SERIAL_IMPL_ASYNC)};
zx::result child_result = AddChild(child_name, properties, std::move(offers));
if (child_result.is_error()) {
  return child_result.take_error();
}

如果相容裝置伺服器是同步初始化,您需要在呼叫 SyncInitializedDeviceServer::Initialize() 函式後執行這項作業。否則,您需要在傳遞至 AsyncInitializedDeviceServer::Begin() 呼叫的回呼函式中執行這項操作。

為後代 DFv1 驅動程式提供 Banjo 服務

如果 DFv2 驅動程式庫實作 Banjo 通訊協定,並想提供給目標子節點,您需要將通訊協定新增至相容裝置伺服器。

假設您的驅動程式庫實作了 Misc Banjo 通訊協定,例如:

class ParentBanjoTransportDriver : public fdf::DriverBase2,
                                   public ddk::MiscProtocol<ParentBanjoTransportDriver> {
...

(本節範例是以 Banjo 傳輸範例為基礎)。

如要將 Misc Banjo 通訊協定新增至相容裝置伺服器,請按照下列步驟操作:

  1. 在驅動程式庫的來源檔案中加入下列標頭:

    #include <lib/driver/compat/cpp/banjo_server.h>
    
  2. 為 Banjo 通訊協定建立 compat::BanjoServer 物件:

    compat::BanjoServer banjo_server_{ZX_PROTOCOL_<NAME>, this, &<name>_protocol_ops_};
    

    在上述範本中,請將 NAME 替換為大寫的通訊協定名稱,並將 name 替換為小寫的通訊協定名稱。因此,對於 Misc Banjo 通訊協定,物件看起來如下所示:

    class ParentBanjoTransportDriver : public fdf::DriverBase2,
                                       public ddk::MiscProtocol<ParentBanjoTransportDriver> {
     ...
    
     private:
      compat::BanjoServer banjo_server_{ZX_PROTOCOL_MISC, this, &misc_protocol_ops_};
     ...
    }
    
  3. 建立 BanjoConfig 物件,並將通訊協定回呼設為 Banjo 伺服器的回呼,例如:

    compat::DeviceServer::BanjoConfig banjo_config;
    banjo_config.callbacks[ZX_PROTOCOL_MISC] = banjo_server_.callback();
    

    這項設定會將 Banjo 設定資訊從 BanjoServer 物件傳遞至相容裝置伺服器。

  4. 初始化相容性裝置伺服器時,請將 Initialize() 函式的 banjo_config 欄位設為 BanjoConfig 物件,例如:

    // Initialize our compat server.
    {
        zx::result<> result = compat_server_.Initialize(
           incoming(), outgoing(), node_name(), child_name, ForwardMetadata::None(),
            std::move(banjo_config));
        if (result.is_error()) {
          return result.take_error();
        }
    }
    

如要瞭解如何將 Banjo 通訊協定提供給子項 DFv1 驅動程式,請參閱「在 DFv2 驅動程式中提供 Banjo 通訊協定」指南。

轉送、新增及剖析 DFv1 中繼資料

許多現有的 DFv1 驅動程式會使用中繼資料,在父項和子項之間轉送資訊。在 DFv2 驅動程式中,您可以使用相容性裝置伺服器執行下列作業:

新增及傳送中繼資料

中繼資料會透過驅動程式庫的相容裝置伺服器傳遞至子節點。如要新增及傳送中繼資料,驅動程式庫必須建立相容裝置伺服器,並呼叫其 AddMetadata() 函式:

zx_status_t AddMetadata(MetadataKey type, const void* data, size_t size);

(來源:device_server.h)

以下範例使用 AddMetadata() 函式新增中繼資料:

const uint64_t metadata = 0xAABBCCDDEEFF0011;
zx_status_t status = compat_device_server.AddMetadata(
    DEVICE_METADATA_PRIVATE, &metadata, sizeof(metadata));

不過,如果中繼資料是 FIDL 型別,您需要先透過 fidl::Persist() 呼叫將中繼資料新增至 AddMetadata() 函式,例如:

fuchsia_hardware_i2c_businfo::wire::I2CChannel local_channel(channel);
fit::result metadata = fidl::Persist(local_channel);
if (!metadata.is_ok()) {
  fdf::error("Failed to fidl-encode channel: {}", metadata.error_value().FormatDescription());
  return zx::error(metadata.error_value().status());
}
compat_server->AddMetadata(DEVICE_METADATA_PRIVATE, metadata.value().data(),
                           metadata.value().size());

轉送中繼資料

如果 DFv2 驅動程式庫從父項節點接收中繼資料,且需要將部分或所有中繼資料傳遞至子項節點,您可以使用相容性裝置伺服器轉送中繼資料。

如要轉送中繼資料,請在初始化驅動程式庫 DeviceServer 物件時設定 forward_metadata 參數:

  • 如要轉送所有中繼資料,請將參數設為 ForwardMetadata::All(),例如:

    // In Start(fdf::DriverContext context, fdf::StartCompleter completer):
    // auto incoming = std::shared_ptr<fdf::Namespace>(context.take_incoming());
    //
    zx::result<> result = compat_server_.Initialize(
           incoming, outgoing(), context.node_name(), child_name,
           compat::ForwardMetadata::All());
    
  • 如要只轉送部分中繼資料,請使用 ForwardMetadata Some(std::unordered_set<MetadataKey> filter) 建立 ForwardMetadata 物件,並將這個物件傳遞至參數。

    以下範例只會轉送含有 DEVICE_METADATA_GPT_INFO 鍵的中繼資料:

    // In Start(fdf::DriverContext context, fdf::StartCompleter completer):
    // auto incoming = std::shared_ptr<fdf::Namespace>(context.take_incoming());
    //
    zx::result<> result = compat_server_.Initialize(
           incoming, outgoing(), context.node_name(), child_name,
           compat::ForwardMetadata::Some({DEVICE_METADATA_GPT_INFO}));
    
  • 如不想轉送中繼資料,請將參數設為 ForwardMetadata::None(),例如:

    // In Start(fdf::DriverContext context, fdf::StartCompleter completer):
    // auto incoming = std::shared_ptr<fdf::Namespace>(context.take_incoming());
    //
    zx::result<> result = compat_server_.Initialize(
           incoming, outgoing(), context.node_name(), child_name,
                                  compat::ForwardMetadata::None());
    

擷取中繼資料

相容裝置伺服器的中繼資料程式庫 (metadata.h) 提供輔助函式,可從驅動程式擷取中繼資料。

如要使用這個中繼資料程式庫,請按照下列步驟操作:

  1. BUILD.gn 中,將下列依附元件新增至 fuchsia_driver 目標:

    "//sdk/lib/driver/compat/cpp",
    
  2. 在驅動程式庫的來源檔案中加入下列標頭:

    #include <lib/driver/compat/cpp/metadata.h>
    
  3. 如要從驅動程式的相容裝置伺服器擷取中繼資料,請使用 compat::GetMetadata<T>() 方法,並將 T 替換為中繼資料類型。

    以下範例會擷取具有 DEVICE_METADATA_VREG 鍵和 fuchsia_hardware_vreg::wire::Metadata 類型的中繼資料:

    fidl::Arena arena;
    zx::result<fuchsia_hardware_vreg::wire::Metadata> metadata = compat::GetMetadata<fuchsia_hardware_vreg::wire::Metadata>(
              incoming, arena, DEVICE_METADATA_VREG);
    if (metadata.is_error()) {
      fdf::error("Failed to get metadata: {}", metadata);
      return completer(metadata.take_error());
    }
    

    如需中繼資料鍵和類型的完整清單,請參閱這個檔案

  4. (選用) 如果驅動程式庫是複合式,您需要傳遞父項的名稱,指定中繼資料來自哪個父項節點,例如:

    zx::result<fuchsia_hardware_vreg::wire::Metadata> metadata = compat::GetMetadata<fuchsia_hardware_vreg::wire::Metadata>(
         incoming, arena, DEVICE_METADATA_VREG, "pdev");
    

不過,如果中繼資料類型是動態大小的陣列,請使用 compat::GetMetadataArray<T>(),並將 T 替換為陣列的類型。

假設我們需要擷取 DEVICE_METADATA_AML_VOLTAGE_TABLE 的中繼資料,這是 aml_voltage_table_t 結構體的陣列:

#define DEVICE_METADATA_AML_VOLTAGE_TABLE (0x41565400 | (DEVICE_METADATA_PRIVATE))  // AVTp

接著,您需要將 T 替換為 aml_voltage_table_t,並擷取中繼資料,如下所示:

zx::result<std::vector<aml_voltage_table_t>>  voltage_table =
     compat::GetMetadataArray<aml_voltage_table_t>(
      incoming, DEVICE_METADATA_AML_VOLTAGE_TABLE);
 if (voltage_table.is_error()) {
   fdf::error("Failed to get voltage table metadata: {}", voltage_table);
   return zx::error(voltage_table.take_error());
 }

同樣地,如果驅動程式庫是複合式,請傳遞父項的名稱,指定中繼資料來自哪個父項節點,例如:

zx::result<std::vector<aml_voltage_table_t>> voltage_table =
     compat::GetMetadataArray<aml_voltage_table_t>(
      incoming, DEVICE_METADATA_AML_VOLTAGE_TABLE, "pdev");