本指南將說明如何在 DFv2 驅動程式庫中設定及使用相容性裝置伺服器,以便與 DFv1 驅動程式通訊。
相容性裝置伺服器可協助 DFv2 驅動程式在遷移過程中與 DFv1 驅動程式維持相容性。相容性裝置伺服器會提供 fuchsia_driver_compat::Device 介面 (請參閱 compat.fidl)。這個介面可讓 DFv2 驅動程式將資源提供給後代 DFv1 驅動程式。
相容性裝置伺服器的主要功能如下:
- 資源共用:從 DFv2 驅動程式庫順暢地提供資源給子項 DFv1 驅動程式。 
- Banjo 服務:為 DFv1 驅動程式提供在 DFv2 驅動程式中實作的 Banjo 通訊協定。 
- 中繼資料處理:轉發、新增及剖析 DFv1 中繼資料,以便 DFv2 和 DFv1 驅動程式之間進行通訊。 
本指南提供逐步操作說明和範例,協助您完成下列工作:
- 設定相容性裝置伺服器:在 DFv2 驅動程式庫中設定相容性裝置伺服器,包括初始化 (同步或非同步) 並提供給子節點。 
- 為後代 DFv1 驅動程式提供 Banjo 服務:為後代 DFv1 驅動程式設定及共用 Banjo 通訊協定。 
- 轉送、新增及剖析 DFv1 中繼資料:新增、轉送及擷取中繼資料,確保 DFv2 和 DFv1 驅動程式之間能順暢交換資訊。 
設定相容性裝置伺服器
本節將根據 DFv2 簡易驅動程式庫範例提供相容性裝置伺服器設定操作說明。
步驟如下:
1. 將相容性裝置伺服器指定為依附元件
如要將相容性裝置伺服器指定為 DFv2 驅動程式庫中的依附元件,請執行下列操作:
- 在 - BUILD.gn中,將下列依附元件新增至- fuchsia_driver目標:- "//sdk/lib/driver/compat/cpp",
- 在驅動程式庫的來源檔案中加入 - device_server.h標頭:- #include <lib/driver/compat/cpp/device_server.h>
- 在驅動程式庫的元件資訊清單 ( - .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.Service的- use、- capabilities和- expose欄位。
有了這項設定,DFv2 驅動程式庫現在可以為子節點建立 DeviceServer 物件。每個 DeviceServer 物件只能指定一個節點。
2. 同步或非同步初始化相容性裝置伺服器
同步或非同步初始化 DeviceServer 物件。根據預設,建議您使用同步而非非同步的初始化。
不過,如果您符合下列任一條件,建議您使用非同步初始化:
- 不允許在目前調度器上進行同步或阻斷呼叫。 
- DFv1 驅動程式庫已針對非同步程式碼進行結構化,您需要非同步行為帶來的效能提升。 
同步初始化
如要進行同步初始化,請執行下列操作:
- 將 - SyncInitializedDeviceServer物件新增至類別,例如:- class SimpleDriver : public fdf::DriverBase { ... compat::SyncInitializedDeviceServer compat_server_; ... }
- 在驅動程式庫實作中,呼叫此 - 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);- 請參閱下方函式參數: - 您可以呼叫 - DriverBase的同名存取子,傳遞- incoming、- outgoing和- node_name的值 (請參閱下方範例)。
- child_node_name是這個- DeviceServer物件指定的子節點名稱。- 不過,如果 DFv2 驅動程式庫程式節點和目標子項節點之間有任何中介節點,您就需要將 - child_additional_path設為以- /分隔的節點之間的拓樸路徑。舉例來說,如果在目標子節點之前有- node-a,然後是- node-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(); } }
非同步初始化
如要進行非同步初始化,請執行下列操作:
- 將 - AsyncInitializedDeviceServer物件新增至類別,例如:- class SimpleDriver : public fdf::DriverBase { ... compat::AsyncInitializedDeviceServer compat_server_; ... }
- 在驅動程式庫實作中,呼叫此 - 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::StartCompleter completer) { async_completer_.emplace(std::move(completer)); device_server_.Begin(incoming(), outgoing(), node_name(), child_name, fit::bind_member<&MyDriver::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 物件的優惠,再設定 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::DriverBase,
                                   public ddk::MiscProtocol<ParentBanjoTransportDriver> {
...
(本節的範例是以 Banjo 轉譯器範例為基礎)。
如要將 Misc Banjo 通訊協定新增至相容性裝置伺服器,請按照下列步驟操作:
- 在驅動程式庫的來源檔案中加入下列標頭: - #include <lib/driver/compat/cpp/banjo_server.h>
- 為 Banjo 通訊協定建立 - compat::BanjoServer物件:- compat::BanjoServer banjo_server_{ZX_PROTOCOL_<NAME>, this, &<name>_protocol_ops_};- 在上述範本中,將 - NAME替換為大寫的通訊協定名稱,將- name替換為小寫的名稱。因此,對於- MiscBanjo 通訊協定,物件會如下所示:- class ParentBanjoTransportDriver : public fdf::DriverBase, public ddk::MiscProtocol<ParentBanjoTransportDriver> { ... private: compat::BanjoServer banjo_server_{ZX_PROTOCOL_MISC, this, &misc_protocol_ops_}; ... }
- 建立 - BanjoConfig物件,並將通訊協定回呼設為 Banjo 伺服器的回呼,例如:- compat::DeviceServer::BanjoConfig banjo_config; banjo_config.callbacks[ZX_PROTOCOL_MISC] = banjo_server_.callback();- 此設定會將 Banjo 設定資訊從 - BanjoServer物件傳遞至相容性裝置伺服器。
- 初始化相容性裝置伺服器時,請將 - 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 類型,您必須先將中繼資料新增至 AddMetadata() 函式,再讓 fidl::Persist() 呼叫持續存在,例如:
fuchsia_hardware_i2c_businfo::wire::I2CChannel local_channel(channel);
fit::result metadata = fidl::Persist(local_channel);
if (!metadata.is_ok()) {
  FDF_LOG(ERROR, "Failed to fidl-encode channel: %s",
          metadata.error_value().FormatDescription().data());
  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(),例如:- zx::result<> result = compat_server_.Initialize( incoming(), outgoing(), node_name(), child_name, compat::ForwardMetadata::All());
- 如果您只想轉送部分中繼資料,請使用 - ForwardMetadata Some(std::unordered_set<MetadataKey> filter)建立- ForwardMetadata物件,然後將此物件傳遞至參數。- 以下範例只會使用 - DEVICE_METADATA_GPT_INFO鍵轉送中繼資料:- zx::result<> result = compat_server_.Initialize( incoming(), outgoing(), node_name(), child_name, compat::ForwardMetadata::Some({DEVICE_METADATA_GPT_INFO}));
- 如果您不想轉送中繼資料,請將參數設為 - ForwardMetadata::None(),例如:- zx::result<> result = compat_server_.Initialize( incoming(), outgoing(), node_name(), child_name, compat::ForwardMetadata::None());
擷取中繼資料
相容性裝置伺服器的中繼資料程式庫 (metadata.h) 提供輔助函式,可從驅動程式擷取中繼資料。
如要使用這個中繼資料程式庫,請按照下列步驟操作:
- 在 - BUILD.gn的- fuchsia_driver目標中加入下列依附元件:- "//sdk/lib/driver/compat/cpp",
- 在驅動程式庫的來源檔案中加入下列標頭: - #include <lib/driver/compat/cpp/metadata.h>
- 如要從驅動程式的相容裝置伺服器擷取中繼資料,請使用 - 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_LOG(ERROR, "Failed to get metadata %s", metadata.status_string()); return completer(metadata.take_error()); }- 如需中繼資料鍵和類型的完整清單,請參閱這個檔案。 
- (選用) 如果驅動程式庫是複合型,您必須傳遞父項名稱,以指定中繼資料來自哪個父項節點,例如: - 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 的中繼資料,而 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_LOG(ERROR, "%s: Failed to get voltage table metadata");
   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");