本頁提供更新 DFv1 驅動程式庫以開始使用 DFv2 介面的說明、最佳做法和範例。
將依附元件從 DDK 更新為 DFv2
DFv1 驅動程式會使用 DDK 程式庫 (//src/lib/ddk
)。如果是 DFv2 驅動程式,您可以放心移除此 DDK 程式庫目錄中的所有套件依附元件,並替換為下列新的程式庫,其中包含大部分的 DFv2 驅動程式的重要公用程式:
//sdk/lib/driver/component/cpp:cpp
在標頭檔案中,針對新 DFv2 驅動程式庫介面加入下列程式庫:
#include <lib/driver/component/cpp/driver_base.h>
將依附元件從 DDK 更新為 DFv2 後,您必須完成下一節「將驅動程式庫介面從 DFv1 更新為 DFv2」一節,才能編譯驅動程式庫。
將驅動程式庫介面從 DFv1 更新至 DFv2
DFv2 提供名為 DriverBase
的虛擬類別,可納入驅動程式庫的一般處理常式。針對 DFv2 驅動程式,建議您在新的驅動程式庫類別中沿用 DriverBase
,讓介面變得更加簡單。
舉例來說,假設您的 DFv1 驅動程式庫中有以下類別:
class MyExampleDriver;
using MyExampleDeviceType = ddk::Device<MyExampleDriver, ddk::Initializable,
ddk::Unbindable>;
class MyExampleDriver : public MyExampleDeviceType {
public:
void DdkInit(ddk::InitTxn txn);
void DdkUnbind(ddk::UnbindTxn txn);
void DdkRelease();
}
如果您將介面更新為沿用 DriverBase
,新類別看起來會像這樣:
class MyExampleDriver : public fdf::DriverBase {
public:
Driver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher);
zx::result<> Start() override;
void Stop() override;
}
除了啟動和停止驅動程式庫 (如上述範例所示) 以外,DriverBase
類別也提供物件,讓驅動程式庫能與其他元件 (和驅動程式) 通訊。舉例來說,您的驅動程式庫現在可以呼叫 DriverBase
類別的 outgoing()
方法,擷取傳出目錄,而不必在 DFv1 中宣告及建立自己的傳出目錄 (如「從 Banjo to FIDL」階段):
class DriverBase {
...
// Used to access the outgoing directory that the driver is serving. Can be used to add both
// zircon and driver transport outgoing services.
std::shared_ptr<OutgoingDirectory>& outgoing() { return outgoing_; }
...
}
(資料來源:driver_base.h
)
DFv2 中還有另一個實用類別是 Node
類別。以下範例顯示會連線至 Node
伺服器,並使用 AddChild()
函式新增子節點的 DFv2 驅動程式庫程式碼:
zx::result<> MyExampleDriver::Start() {
fidl::WireSyncClient<fuchsia_driver_framework::Node> node(std::move(node()));
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, “example_node”)
.Build();
auto controller_endpoints = fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();
auto result = node_->AddChild(args, std::move(controller_endpoints.server), {});
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to add child: %s", result.status_string());
return zx::error(result.status());
}
return zx::ok();
}
傳遞至 AddChild()
函式的 NodeController
端點 (在上述範例中為 controller_endpoints
) 可用於控管子節點。舉例來說,這個端點可用於移除節點拓撲中的子節點、要求驅動程式庫架構將節點繫結至特定驅動程式庫,或是在子項節點繫結時接收回呼。以下範例顯示可在關閉期間移除子項節點的 DFv2 驅動程式庫程式碼:
void MyExampleDriver::Stop() {
// controller_endpoints defined in the previous example.
fidl::WireSyncClient<fuchsia_driver_framework::NodeController>
node_controller(controller_endpoints.client);
auto status = node_controller->Remove();
if (!status.ok()) {
FDF_LOG(ERROR, "Could not remove child: %s", status.status_string());
}
}
此外,在 DFv2 中,OnBind()
事件是在 NodeController
通訊協定中定義,而從伺服器端的子節點叫用該事件。DriverBase::PrepareStop()
函式有機會在呼叫 DriverBase::Stop()
之前執行取消初始化。
下表列出 DFv1 和 DFv2 之間的常見驅動程式庫和裝置介面對應:
DFv1 | DFv2 |
---|---|
zx_driver_ops::bind() |
DriverBase::Start() |
zx_driver_ops::init() |
DriverBase::Start() |
zx_driver_ops::release() |
DriverBase::Stop() |
device_add() DdkAdd() |
Node::AddChild() |
device_add_composite() DdkAddComposite() |
無:依據規格新增複合式節點。請參閱複合式節點。 |
device_add_composite_node_spec() DdkAddCompositeNodeSpec() |
CompositeNodeManager::AddSpec() |
zx_protocol_device::init() DdkInit() |
無:在 DFv2 中,驅動程式庫會負責其新增的節點 (裝置) 生命週期。 |
zx_protocol_device::unbind() DdkUnbind() |
無:在 DFv2 中,驅動程式庫會負責其新增的節點 (裝置) 生命週期。 |
zx_protocol_device::release() DdkRelease() |
NodeController::Remove() |
zx_protocol_device::get_protocol() device_get_protocol() |
無:這些方法以 DFv2 中的 Banjo 通訊協定為基礎,所有通訊皆位於 FIDL 中。 |
zx_protocol_device::service_connect() device_service_connect() DdkServiceConnect() |
無:這是駕駛人彼此建立 FIDL 連線的傳統方法。詳情請參閱「使用 DFv2 服務探索」。 |
Device_connect_runtime_protocol() DdkConnectRuntimeProtocol() |
無:這些是 DFv1 中新增的服務和通訊協定探索方法。詳情請參閱「使用 DFv2 服務探索」。 |
使用 DFv2 記錄器
DFv2 中的新記錄機制並非使用 zxlogf()
(DFv2 中已淘汰),而是取決於 fdf::Logger
物件,該物件會在驅動程式啟動時透過 DriverStartArgs
從驅動程式庫主機傳遞。
fdf::DriverBase
類別會納入 fdf::Logger
,且驅動程式庫可透過呼叫 logger()
方法取得參照 (請參閱此 wlantap-driver
驅動程式庫範例)。透過這項參考資料,您可以使用 logger.logf()
函式或這些巨集列印記錄,例如:
FDF_LOG(INFO, "Example log message here");
更新巨集
除了更新介面之外,您也必須變更填入驅動程式庫介面函式的巨集:
從:
ZIRCON_DRIVER()
到:
FUCHSIA_DRIVER_EXPORT()
設定 compat 裝置伺服器
如果您的 DFv1 驅動程式庫與尚未遷移至 DFv2 的其他 DFv1 驅動程式通訊,您必須使用相容性填充碼,使目前的 DFv2 驅動程式庫能夠與其他 DFv1 驅動程式進行通訊。如要進一步瞭解如何在 DFv2 驅動程式庫中設定及使用此相容性輔助程式,請參閱「在 DFv2 驅動程式設定 compat 裝置伺服器」指南。
使用 DFv2 服務探索
進行驅動程式庫遷移時,您可能會遇到以下三種情況,也就是兩個驅動程式建立 FIDL 連線 (採用 child driver -> parent driver
格式) 的情境:
- 情境 1:DFv2 驅動程式庫 -> DFv2 驅動程式庫
- 情境 2:DFv1 驅動程式庫 -> DFv2 驅動程式庫
- 情境 3:DFv2 驅動程式庫 -> DFv1 驅動程式庫
情境 1 是 DFv2 驅動程式的標準案例 (此範例說明新的 DFv2 語法)。如要在此情境中更新驅動程式,請參閱下方的「DFv2 驅動程式庫至 DFv2 驅動程式」一節。
情境 2 和 3 比較複雜,因為 DFv1 驅動程式會包裝在 DFv2 環境中的相容性填充碼中。但差異如下:
在情境 3 中,驅動程式庫會連線至父項驅動程式庫的相容性輔助碼提供的
fuchsia_driver_compat::Service::Device
通訊協定,而驅動程式庫會透過這個通訊協定呼叫ConnectFidl()
方法來連線至實際通訊協定 (範例請見此 Gerrit 變更)。
如要在情境 2 或 3 下更新驅動程式,請參閱下方的「DFv1 驅動程式庫至 DFv2 驅動程式庫 (具有相容性輔助鍵)」一節。
DFv2 驅動程式庫至 DFv2 驅動程式庫
如果想讓其他 DFv2 驅動程式能夠找到您的驅動程式服務,請按照下列步驟操作:
更新驅動程式的
.fidl
檔案。DFv2 中的通訊協定探索需要為驅動程式庫的通訊協定新增
service
欄位,例如:library fuchsia.example; @discoverable @transport("Driver") protocol MyProtocol { MyMethod() -> (struct { ... }); }; service Service { my_protocol client_end:MyProtocol; };
更新子項驅動程式庫。
DFv2 驅動程式可以採用與 FIDL 服務相同的方式連線至通訊協定,例如:
incoming()->Connect<fuchsia_example::Service::MyProtocol>();
此外,您也需要更新元件資訊清單 (
.cml
) 檔案,才能使用驅動程式庫執行階段服務,例如:use: [ { service: "fuchsia.example.Service" }, ]
更新父項驅動程式庫。
您的父項驅動程式庫必須使用
fdf::DriverBase
的outgoing()
函式來取得fdf::OutgoingDirectory
物件。請注意,您必須使用服務而非通訊協定。如果您的驅動程式庫並未使用fdf::DriverBase
,則必須自行建立並提供fdf::OutgoingDirectory
。接著,您必須將執行階段服務新增至傳出目錄。以下範例為繼承自
fdf::DriverBase
類別的驅動程式庫:zx::status<> Start() override { auto protocol = [this]( fdf::ServerEnd<fuchsia_example::MyProtocol> server_end) mutable { // bindings_ is a class field with type fdf::ServerBindingGroup<fuchsia_example::MyProtocol> bindings_.AddBinding( dispatcher()->get(), std::move(server_end), this, fidl::kIgnoreBindingClosure); }; fuchsia_example::Service::InstanceHandler handler( {.my_protocol = std::move(protocol)}); auto status = outgoing()->AddService<fuchsia_wlan_phyimpl::Service>(std::move(handler)); if (status.is_error()) { return status.take_error(); } return zx::ok(); }
更新子節點的
NodeAddArgs
以加入執行階段服務優惠,例如:auto offers = std::vector{fdf::MakeOffer2<fuchsia_example::Service>(arena, name)}; fidl::WireSyncClient<fuchsia_driver_framework::Node> node(std::move(node())); auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena) .name(arena, “example_node”) .offers2(offers) .Build(); zx::result controller_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>(); ZX_ASSERT(controller_endpoints.is_ok()); auto result = node_->AddChild( args, std::move(controller_endpoints->server), {});
同樣地,請更新父項驅動程式庫程式的元件資訊清單 (
.cml
) 檔案,以提供執行階段服務,例如:capabilities: [ { service: "fuchsia.example.Service" }, ], expose: [ { service: "fuchsia.example.Service", from: "self", }, ],
套用至 DFv2 驅動程式庫的 DFv1 驅動程式庫 (含相容性填充碼)
如果想讓其他 DFv1 驅動程式能夠找到您的 DFv2 驅動程式庫服務,請執行下列操作:
更新 DFv1 驅動程式。
您必須按照上述「DFv2 驅動程式庫至 DFv2 驅動程式」一節所述的方式更新 DFv1 驅動程式的元件資訊清單 (
.cml
) 檔案,例如:子項驅動程式庫:
{ include: [ "//sdk/lib/driver_compat/compat.shard.cml", "inspect/client.shard.cml", "syslog/client.shard.cml", ], program: { runner: "driver", compat: "driver/child-driver-name.so", bind: "meta/bind/child-driver-name.bindbc", colocate: "true", }, use: [ { service: "fuchsia.example.Service" }, ], }
父項驅動程式庫:
{ include: [ "//sdk/lib/driver_compat/compat.shard.cml", "inspect/client.shard.cml", "syslog/client.shard.cml", ], program: { runner: "driver", compat: "driver/parent-driver-name.so", bind: "meta/bind/parent-driver-name.bindbc", }, capabilities: [ { service: "fuchsia.example.Service" }, ], expose: [ { service: "fuchsia.example.Service", from: "self", }, ], }
更新 DFv2 驅動程式庫。
以下範例顯示將 DFv2 父項服務公開給 DFv1 子項的方法:
fit::result<fdf::NodeError> AddChild() { fidl::Arena arena; auto offer = fdf::MakeOffer2<ft::Service>(kChildName); // Set the properties of the node that a driver will bind to. auto property = fdf::MakeProperty(1 /*BIND_PROTOCOL */, bind_fuchsia_test::BIND_PROTOCOL_COMPAT_CHILD); auto args = fdf::NodeAddArgs{ { .name = std::string(kChildName), .properties = std::vector{std::move(property)}, .offers2 = std::vector{std::move(offer)}, } }; // Create endpoints of the `NodeController` for the node. auto endpoints = fidl::CreateEndpoints<fdf::NodeController>(); if (endpoints.is_error()) { return fit::error(fdf::NodeError::kInternal); } auto add_result = node_.sync()->AddChild(fidl::ToWire(arena, std::move(args)), std::move(endpoints->server), {});
(資料來源:
root-driver.cc
)
更新其他驅動程式的元件資訊清單
如要完成將 DFv1 驅動程式庫遷移至 DFv2 的程序,您不僅需要更新目標驅動程式庫的元件資訊清單 (.cml
) 檔案,也需要更新其他與 Now-DFv2 驅動程式庫互動的其他驅動程式的元件資訊清單檔案。
請完成下列步驟:
請按照下列步驟更新分葉驅動程式 (不含子項驅動程式) 的元件資訊清單:
- 從
include
欄位移除//sdk/lib/driver/compat/compat.shard.cml
。 - 將
program.compat
欄位替換為program.binary
。
- 從
針對執行以下任務的其他驅動程式,更新元件資訊清單:
- 存取核心
args
。 - 建立複合型裝置。
- 偵測重新啟動、關閉或重新繫結通話。
- 使用 Banjo 通訊協定與其他駕駛人交談。
- 存取父項驅動程式庫的中繼資料,或是轉寄中繼資料。
- 連線至會繫結至驅動程式新增節點的 DFv1 驅動程式庫。
針對這些驅動程式,請根據以下變更更新其元件資訊清單:
將
compat.shard.cml
中的部分use
功能複製到元件資訊清單,例如:use: [ { protocol: [ "fuchsia.boot.Arguments", "fuchsia.boot.Items", "fuchsia.device.manager.SystemStateTransition", "fuchsia.driver.framework.CompositeNodeManager", ], }, { service: "fuchsia.driver.compat.Service" }, ],
將
program.runner
欄位設為driver
,例如:program: { runner: "driver", binary: "driver/compat.so", },
- 存取核心
公開 DFv2 驅動程式庫中的 devfs 節點
如要從 DFv2 驅動程式庫公開 devfs
節點,您必須將 device_args
成員新增至 NodeAddArgs
。特別是,它需要指定類別名稱,並實作連接器,透過使用 Connector
程式庫來簡化,例如:
zx::result connector = devfs_connector_.Bind(dispatcher());
if (connector.is_error()) {
return connector.take_error();
}
auto devfs =
fuchsia_driver_framework::wire::DevfsAddArgs::Builder(arena).connector(
std::move(connector.value()));
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, name)
.devfs_args(devfs.Build())
.Build();
(資料來源:parent-driver.cc
)
詳情請參閱 DFv2 驅動程式庫程式碼研究室中的「驅動程式庫」。此外,也請參閱程式碼研究室所述 ExportToDevfs
方法的此實作。
使用調度工具
調度工具會從 FIDL 用戶端和伺服器組合之間的管道擷取資料。根據預設,此管道中的 FIDL 呼叫為非同步性質。
如要瞭解如何為 DFv2 中的驅動程式導入同步處理程序,請參閱下列建議:
fdf::Dispatcher::GetCurrent()
方法會提供執行驅動程式庫的預設調度工具 (請參閱aml-ethernet
驅動程式庫範例)。建議您盡可能單獨使用這個預設調度工具。考慮使用多個調度工具的原因如下 (但不限於):
驅動程式需要平行處理才能提高效能。
驅動程式想要執行阻斷作業 (因為其不是舊版驅動程式庫或攜碼至 Fuchsia 的非 Fuchsia 驅動程式庫),且需要在遭到封鎖的情況下處理更多工作。
如果需要多個調度工具,
fdf::Dispatcher::Create()
方法可以為駕駛建立新的調度工具。然而,您必須在預設調度工具中呼叫這個方法 (例如在Start()
掛鉤中呼叫此方法),這樣驅動程式代管程序才會知道其他驅動程式屬於您的驅動程式庫。在 DFv2 中,您無須手動關閉調度工具。這些屬性將在
PrepareStop()
和Stop()
呼叫之間關閉。
如要進一步瞭解如何遷移驅動程式庫以使用多個調度工具,請參閱「更新 DFv1 驅動程式庫以使用非預設調度器」一節 (請參閱「從 Banjo 遷移至 FIDL」詞組)。
使用 DFv2 檢查功能
如要在 DFv2 中設定驅動程式庫維護的「檢查」inspect指標,您必須建立 inspect::ComponentInspector
物件,例如:
component_inspector_ =
std::make_unique<inspect::ComponentInspector>(out, dispatcher, *inspector_);
(資料來源:driver-inspector.cc
)
建立 inspect::ComponentInspector
物件需要下列三個輸入項目:
Context().outgoing()->component()
呼叫中的component::OutgoingDirectory
物件調度器
原始
inspect::Inspector
物件
不過,DFv2 檢查不需要將 inspect::Inspector
的 VMO 傳遞至驅動程式庫架構。
(選用) 實作自己的 load_firmware 方法
如果 DFv1 驅動程式庫會呼叫 DDK 程式庫中的 load_firmware()
函式,您就必須實作自己的函式版本,因為 DFv2 中未提供對等函式。
這個函式應該很簡單的實作。您必須手動從路徑取得備份 VMO。如需範例,請參閱這項變更器變更。
(選用) 使用從 FIDL 服務方案產生的節點屬性
DFv2 節點包含從父項 FIDL 服務優惠產生的節點屬性。
舉例來說,在父項驅動程式 (The Server) 範例中,父項驅動程式庫會新增名為 "parent"
的節點,以及 fidl.examples.EchoService
的服務優惠。在 DFv2 中,與這個節點繫結的驅動程式庫可以有該 FIDL 服務節點屬性的繫結規則,例如:
using fidl.examples.echo;
fidl.examples.echo.Echo == fidl.examples.echo.Echo.ZirconTransport;
詳情請參閱 FIDL 教學課程頁面的「產生的繫結程式庫」一節。
將單元測試更新為 DFv2
mock_ddk
程式庫 (用於測試驅動程式庫和裝置生命週期的單元測試) 僅適用於 DFv1。新的 DFv2 測試架構 (請參閱這項 Gerrit 變更) 可讓您透過 TestEnvironment
類別為 DFv2 驅動程式提供模擬的 FIDL 伺服器。
以下程式庫可用於單元測試 DFv2 驅動程式:
-
TestNode
- 此類別會實作fuchsia_driver_framework::Node
通訊協定,並可以提供給驅動程式庫以建立子節點。測試也會使用這個類別,存取驅動程式庫已建立的子節點。TestEnvironment
-OutgoingDirectory
物件的包裝函式,這個包裝函式會做為受測驅動程式庫傳入命名空間的幕後 VFS (虛擬檔案系統)。DriverUnderTest
- 此類別是受測試驅動程式庫的 RAII (資源擷取是初始化) 包裝函式。DriverRuntime
:此類別是代管驅動程式庫執行階段執行緒集區的 RAII 包裝函式。
//sdk/lib/driver/testing/cpp/driver_runtime.h
TestSynchronizedDispatcher
– 此類別是驅動程式庫調度器上的 RAII 包裝函式。
下列程式庫可能有助於編寫驅動程式庫單元測試:
//src/devices/bus/testing/fake-pdev/fake-pdev.h
- 這個輔助程式庫會實作pdev
FIDL 通訊協定的假版本。
最後,下列單元測試範例涵蓋不同的設定和測試案例:
//sdk/lib/driver/component/cpp/tests/driver_base_test.cc
- 這個檔案包含驅動程式庫測試中各種不同的執行緒模型範例。//sdk/lib/driver/component/cpp/tests/driver_fidl_test.cc
- 此檔案示範如何使用傳入和傳出的 FIDL 服務,其中包含驅動程式庫傳輸、Zircon 傳輸和devfs
。
其他資源
以下是一些 DFv2 驅動程式的範例:
本節提及的所有Gerrit 變更:
- [iwlwifi] iwlwifi 驅動程式的 Dfv2 遷移
- [compat-runtime-test] 遷移 DeviceServer 的使用
- [msd-arm-mali] 新增 DFv2 版本
- [sdk][驅動程式庫][testing] 新增測試程式庫
本節提及的所有原始碼檔案:
//examples/drivers/transport/zircon/v2/parent-driver.cc
//sdk/fidl/fuchsia.driver.framework/topology.fidl
//sdk/lib/driver/component/cpp/driver_base.h
//sdk/lib/driver/component/cpp/tests/driver_base_test.cc
//sdk/lib/driver/component/cpp/tests/driver_fidl_test.cc
//sdk/lib/driver/compat/cpp/banjo_server.h
//sdk/lib/driver/compat/cpp/banjo_client.h
//sdk/lib/driver/compat/cpp/device_server.h
//sdk/lib/driver/testing/cpp/driver_runtime.h
//src/connectivity/wlan/testing/wlantap-driver/wlantap-driver.cc
//src/devices/bus/testing/fake-pdev/fake-pdev.h
//src/devices/tests/v2/compat-runtime/root-driver.cc
//src/lib/ddk/include/lib/ddk/device.h
//src/lib/ddk/include/lib/ddk/driver.h
//third_party/iwlwifi/platform/driver-inspector.cc
本節提及的所有說明文件頁面:
- 班究琴
- 驅動程式和節點
- 駕駛人通訊
- 驅動程式和節點
- 駕駛調度器和執行緒
- 驅動因素
- 複合節點
- 公開驅動程式庫
- Fuchsia 元件檢查總覽
- 模擬 DDK 遷移作業
- 撕裂序列範例 (來自驅動程式庫)
- 父項驅動程式 (伺服器) (來自 FIDL 教學課程)
- 產生的繫結程式庫 (內容取自 FIDL 教學課程)