This page provides instructions, best practices, and examples related to updating various services (other than the DDK interfaces) in DFv1 drivers to DFv2.
Set up the compat device server
If your DFv1 driver talks to other DFv1 drivers that haven't yet migrated to DFv2, you need to use the compatibility shim to enable your now-DFv2 driver to talk to other DFv1 drivers in the system. For more information on setting up and using this compatibility shim in a DFv2 driver, see the Set up the compat device server in a DFv2 driver guide.
Use the DFv2 service discovery
When working on driver migration, you will likely encounter one or more
of the following three scenarios in which two drivers establish a FIDL
connection (in child driver -> parent driver
format):
- Scenario 1: DFv2 driver -> DFv2 driver
- Scenario 2: DFv1 driver -> DFv2 driver
- Scenario 3: DFv2 driver -> DFv1 driver
Scenario 1 is the standard case for DFv2 drivers (this example shows the new DFv2 syntax). To update your driver under this scenario, see the DFv2 driver to DFv2 driver section below.
Scenario 2 and 3 are more complicated because the DFv1 driver is wrapped in the compatibility shim in the DFv2 world. However, the differences are:
In scenario 2, this Gerrit change shows a method that exposes a service from the DFv2 parent to the DFv1 child.
In scenario 3, the driver is connected to the
fuchsia_driver_compat::Service::Device
protocol provided by the compatibility shim of the parent driver, and the driver calls theConnectFidl()
method through this protocol to connect to the real protocol (for an example, see this Gerrit change).
To update your driver under scenario 2 or 3, see the DFv1 driver to DFv2 driver (with compatibility shim) section below.
DFv2 driver to DFv2 driver
To enable other DFv2 drivers to discover your driver's service, do the following:
Update your driver's
.fidl
file.The protocol discovery in DFv2 requires adding
service
fields for the driver's protocols, for example:library fuchsia.example; @discoverable @transport("Driver") protocol MyProtocol { MyMethod() -> (struct { ... }); }; service Service { my_protocol client_end:MyProtocol; };
Update the child driver.
DFv2 drivers can connect to protocols in the same way as FIDL services, for example:
incoming()->Connect<fuchsia_example::Service::MyProtocol>();
You also need to update the component manifest (
.cml
) file to use your driver runtime service, for example:use: [ { service: "fuchsia.example.Service" }, ]
Update the parent driver.
Your parent driver needs to use the
fdf::DriverBase
'soutgoing()
function to get thefdf::OutgoingDirectory
object. Note that you must use services rather than protocols. If your driver isn't usingfdf::DriverBase
you must create and serve anfdf::OutgoingDirectory
on your own.Then you need to add the runtime service to your outgoing directory. The example below is a driver that inherits from the
fdf::DriverBase
class: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(); }
Update the child node's
NodeAddArgs
to include an offer for your runtime service, for example: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), {});
Similarly, update the parent driver's component manifest (
.cml
) file to offer your runtime service, for example:capabilities: [ { service: "fuchsia.example.Service" }, ], expose: [ { service: "fuchsia.example.Service", from: "self", }, ],
DFv1 driver to DFv2 driver (with compatibility shim)
To enable other DFv1 drivers to discover your DFv2 driver's service, do the following:
Update the DFv1 drivers.
You need to update the component manifest (
.cml
) files of the DFv1 drivers in the same way as mentioned in the DFv2 driver to DFv2 driver section above, for example:Child driver:
{ 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" }, ], }
Parent driver:
{ 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", }, ], }
Update the DFv2 driver.
The example below shows a method that exposes a service from the DFv2 parent to the DFv1 child:
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), {});
(Source:
root-driver.cc
)
Update component manifests of other drivers
To complete the migration of a DFv1 driver to DFv2, you not only need
to update the component manifest (.cml
) file of your target driver,
but you may also need to update the component manifest files of some
other drivers that interact with your now-DFv2 driver.
Do the following:
Update the component manifests of leaf drivers (that is, without child drivers) with the changes below:
- Remove
//sdk/lib/driver/compat/compat.shard.cml
from theinclude
field. - Replace the
program.compat
field withprogram.binary
.
- Remove
Update the component manifests of other drivers that perform the following tasks:
- Access kernel
args
. - Create composite devices.
- Detect reboot, shutdown, or rebind calls.
- Talk to other drivers using the Banjo protocol.
- Access metadata from a parent driver or forward it.
- Talk to a DFv1 driver that binds to a node added by your driver.
For these drivers, update their component manifest with the changes below:
Copy some of the
use
capabilities fromcompat.shard.cml
to the component manifest, for example:use: [ { protocol: [ "fuchsia.boot.Arguments", "fuchsia.boot.Items", "fuchsia.driver.framework.CompositeNodeManager", "fuchsia.system.state.SystemStateTransition", ], }, { service: "fuchsia.driver.compat.Service" }, ],
Set the
program.runner
field todriver
, for example:program: { runner: "driver", binary: "driver/compat.so", },
- Access kernel
Expose a devfs node from the DFv2 driver
To expose a devfs
node from a DFv2 driver, you need to add
the device_args
member to the NodeAddArgs
.
In particular, it requires specifying the class name as well as
implementing the connector, which can be simplified by making use of
the Connector
library, for example:
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();
(Source: parent-driver.cc
)
For more information, see
Expose the driver capabilities in the
DFv2 driver codelab. Also, see this implementation
of the ExportToDevfs
method mentioned in the codelab.
For more details on the devfs
setup process,
see the Set up devfs in a DFv2 driver guide.
Use dispatchers
Dispatchers fetch data from a channel between a FIDL client-and-server pair. By default, FIDL calls in this channel are asynchronous.
For introducing asynchronization to drivers in DFv2, see the following suggestions:
The
fdf::Dispatcher::GetCurrent()
method gives you the default dispatcher that the driver is running on (see thisaml-ethernet
driver example). If possible, it is recommended to use this default dispatcher alone.Consider using multiple dispatchers for the following reasons (but not limited to):
The driver requires parallelism for performance.
The driver wants to perform blocking operations (because it is either a legacy driver or a non-Fuchsia driver being ported to Fuchsia) and it needs to handle more work while blocked.
If multiple dispatchers are needed, the
fdf::Dispatcher::Create()
method can create new dispatchers for your driver. However, you must call this method on the default dispatcher (for example, call it inside theStart()
hook) so that the driver host is aware of the additional dispatchers that belong to your driver.In DFv2, you don't need to shut down the dispatchers manually. They will be shut down between the
PrepareStop()
andStop()
calls.
For more details on migrating a driver to use multiple dispatchers, see the Update the DFv1 driver to use non-default dispatchers section (in the Migrate from Banjo to FIDL phrase).
Use the DFv2 inspect
To set up driver-maintained inspect metrics in DFv2,
you may use the ComponentInspector
provided by fdf::DriverBase::inspector()
:
inspect::Node& root = inspector().root();
If a custom Inspector should be used, call fdf::DriverBase::InitInspectorExactlyOnce(inspector)
before accessing the inspector()
method.
DFv2 inspect does not require passing the VMO of inspect::Inspector
to the driver framework.
DFv2 drivers' Inspect will show up attributed to the driver (since it is a "normal" component). The monikers of DFv2 drivers are not stable, however, so when writing privacy selectors against a driver you should use wildcards and name filters to refer to a specific driver. For example,
bootstrap/*-drivers*:[name=sysmem]root
To access a driver's Inspect during debugging, you can use all the normal tools, such as
ffx inspect show --name sysmem "bootstrap/*-drivers*:root"
or
ffx inspect show --component sysmem.cm
(Optional) Implement your own load_firmware method
If your DFv1 driver calls the load_firmware()
function in the DDK library, you need to implement your own version
of this function because an equivalent function is not available in DFv2.
This function is expected to be simple to implement. You need to get the backing VMO from the path manually. For an example, see this Gerrit change.
(Optional) Use the node properties generated from FIDL service offers
DFv2 nodes contain the node properties generated from the FIDL service offers from their parents.
For instance, in the Parent Driver (The Server)
example, the parent driver adds a node called "parent"
with a service
offer for fidl.examples.EchoService
. In DFv2, a driver that binds to this
node can have a bind rule for that FIDL service node property, for example:
using fidl.examples.echo;
fidl.examples.echo.Echo == fidl.examples.echo.Echo.ZirconTransport;
For more information, see the Generated bind libraries section of the FIDL tutorial page.
Update unit tests to DFv2
The mock_ddk
library (which is used in unit tests for testing
driver and device life cycle) is specific to DFv1. The new DFv2 test
framework (see this Gerrit change) makes
mocked FIDL servers available to DFv2 drivers through the TestEnvironment
class.
The following libraries are available for unit testing DFv2 drivers:
-
TestNode
– This class implements thefuchsia_driver_framework::Node
protocol, which can be provided to a driver to create child nodes. This class is also used by tests to access the child nodes that the driver has created.TestEnvironment
– A wrapper over anOutgoingDirectory
object that serves as the backing VFS (virtual file system) for the incoming namespace of the driver under test.DriverUnderTest
– This class is a RAII (Resource Acquisition Is Initialization) wrapper for the driver under test.DriverRuntime
– This class is a RAII wrapper over the managed driver runtime thread pool.
//sdk/lib/driver/testing/cpp/driver_runtime.h
TestSynchronizedDispatcher
– This class is a RAII wrapper over the driver dispatcher.
The following library may be helpful for writing driver unit tests:
//src/devices/bus/testing/fake-pdev/fake-pdev.h
– This helper library implements a fake version of thepdev
FIDL protocol.
Lastly, the following example unit tests cover different configurations and test cases:
//sdk/lib/driver/component/cpp/tests/driver_base_test.cc
- This file contains examples of the different threading models that driver tests can have.//sdk/lib/driver/component/cpp/tests/driver_fidl_test.cc
- This file demonstrates how to work with incoming and outgoing FIDL services fo both driver transport and Zircon transport as well asdevfs
.
Additional resources
Some DFv2 drivers examples:
All the Gerrit changes mentioned in this section:
- [iwlwifi] Dfv2 migration for iwlwifi driver
- [compat-runtime-test] Migrate off usage of DeviceServer
- [msd-arm-mali] Add DFv2 version
- [sdk][driver][testing] Add testing library
All the source code files mentioned in this section:
//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
All the documentation pages mentioned in this section:
- Banjo
- Drivers and nodes
- Driver communication
- Drivers and nodes
- Driver dispatcher and threads
- Drivers
- Create a composite node
- Expose the driver capabilities
- Fuchsia component inspection overview
- Mock DDK Migration
- An Example of the Tear-Down Sequence (from Device driver lifecycle)
- Parent Driver (The Server) (from FIDL tutorial)
- Generated bind libraries (from FIDL tutorial)