blob: 74f392b8264e70a6d8854efb46d2c449f72b5527 [file] [log] [blame] [view] [edit]
# Migrate from DFv1 to DFv2
Important: This phase assumes that your DFv1 driver has already been migrated
[from Banjo to FIDL][migrate-from-banjo-to-fidl].
When migrating a DFv1 driver to DFv2, a major part of work is to
[update the driver interfaces](#update-the-driver-interfaces-from-dfv1-to-dfv2)
to DFv2. However, if your target driver needs to talk to other DFv1 drivers
that haven't yet migrated to DFv2, you need to use the
[compatibility shim](#update-dependencies-for-the-compatibility-shim) to
enable your now-DFv2 driver to talk to other DFv1 drivers in the system.
## List of migration tasks {:#list-of-migration-tasks}
DFv1-to-DFv2 migration tasks are:
- [Update dependencies from DDK to DFv2](#update-dependencies-from-ddk-to-dfv2)
- ([Optional) Update dependencies for the compatibility shim](#update-dependencies-for-the-compatibility-shim)
- [Update the driver interfaces from DFv1 to DFv2](#update-the-driver-interfaces-from-dfv1-to-dfv2)
- [Use the DFv2 service discovery](#use-the-dfv2-service-discovery)
- [Update component manifests of other drivers](#update-component-manifests-of-other-drivers)
- [Expose a devfs node from the DFv2 driver](#expose-a-devfs-node-from-the-dfv2-driver)
- [Use dispatchers](#use-dispatchers)
- [Use the DFv2 inspect](#use-the-dfv2-inspect)
- [Use the DFv2 logger](#use-the-dfv2-logger)
- ([Optional) Implement your own load_firmware method](#implement-your-own-load-firmware-method)
- ([Optional) Use the node properties generated from FIDL service offers](#use-the-node-properties-generated-from-fidl-service-offers)
- [Update unit tests to DFv2](#update-unit-tests-to-dfv2)
For more information and examples, see
[Additional resources](#additional-resources).
## Before you start (Frequently asked questions) {:#before-you-start}
Before you start jumping into the DFv1-to-DFv2 migration tasks, the
frequently asked questions below can help you identify special conditions
or edge cases that may apply to your driver.
- **What is the compatibility shim and when is it necessary?**
DFv1 and DFv2 drivers exist under a single branch of the node topology
tree (that is, until all the drivers are migrated to DFv2) and they need
to be able to talk to each other. To help with the migration process,
Fuchsia's driver framework team created a compatibility shim to enable
DFv1 drivers to live in DFv2.
If your target driver talks to other DFv1 drivers that still use
[Banjo][banjo] and those drivers won't be migrated to DFv2 all at once,
you need to
[use this compatibility shim](#update-dependencies-for-the-compatibility-shim)
(by manually creating `compat::DeviceServer`) for enabling the drivers
in different framework versions to talk to each other.
- **Can DFv2 drivers talk to Banjo protocols using the compatibility shim?**
While it's strongly recommended that your DFv1 driver is migrated from
Banjo to FIDL, if it is necessary for a DFv2 driver to talk
to some existing Banjo protocols, the compatibility shim provides the
following features:
- `compat::BanjoServer` makes it easier to serve Banjo
(see [`banjo_server.h`][banjo-server-h]).
- `compat::ConnectBanjo` makes it easier to connect to Banjo
(see [`banjo_client.h`][banjo-client-h]).
- **What has changed in the new DFv2 driver interfaces?**
One major change in DFv2 is that drivers take control of the life cycle
of the child [nodes][driver-node] (or devices) created by the drivers.
This is different from DFv1 where the driver framework manages the life
cycles of devices, such as [tearing down devices][device-lifecycle],
through the device tree.
In DFv1, devices are controlled by [`zx_protocol_device`][ddk-device-h-77]
while drivers are controlled by [`zx_driver_ops`][ddk-driver-h-29].
If `ddktl` is used, the interfaces in `zx_protocol_device` need to be
wrapped by `Ddk*()` functions in the mixin template class. In DFv2,
[those interfaces](#update-the-driver-interfaces-from-dfv1-to-dfv2)
have changed significantly.
- **How does service discovery work in DFv2?**
In DFv2, using a FIDL service is required to establish a protocol
connection. The parent driver adds a FIDL service to the
`driver::OutgoingDirectory` object and serves it to the child node,
which then enables the parent driver to offer the service to the
child node.
DFv1 and DFv2 drivers do this differently in the following ways:
- In DFv1, the driver sets and passes the offer from the
`DeviceAddArgs::set_runtime_service_offers()` call. Then the driver
creates an `driver::OutgoingDirectory` object and passes the client
end handle through the `DeviceAddArgs::set_outgoing_dir()` call.
- In DFv2, the driver sets and passes the offer from the
`NodeAddArgs::offers` object. The driver adds the service to the
outgoing directory wrapped by the `DriverBase` class (originally
provided by the `Start()` function). When the child driver binds to
the child node, the driver host passes the incoming namespace
containing the service to the child driver's `Start()` function.
On the child driver side, DFv1 and DFv2 drivers also connect to the
protocol providing the service in different ways:
- A DFv1 driver calls the `DdkConnectRuntimeProtocol<ProtocolName>()`
method.
- A DFv2 driver calls the `driver::Connect<ProtocolName>()` method
(or `context().incoming()->Connect<ProtocolName>()` if the
`DriverBase` class is used).
For more information, see
[Use the DFv2 service discovery](#use-the-dfv2-service-discovery).
- **How does my driver's node (or device) get exposed in the system in DFv2?**
Fuchsia has a global tree of devices exposed as a filesystem known as
[`devfs`][devfs], which is routed to most components as `/dev`. When
a driver adds a [device node][driver-node], it has the option of adding
a "file" into `devfs`. Then this file in `devfs` allows other components
in the system to talk to the driver. For instance, an audio driver may add
a speaker device node and the audio driver wants to make sure that other
components can use this node to output audio to the speaker. To accomplish
this, the audio driver
[adds (or exposes) a `devfs` node](#expose-a-devfs-node-from-the-dfv2-driver)
for the speaker so that it appears as `/dev/class/audio/<random_number>`
in the system.
- **What is not implemented in DFv2 that was available in DFv1?**
If your DFv1 driver calls the [`load_firmware()`][load-firmware] function
in the DDK library, you need to implement your own since an equivalent
function is not available in DFv2. However, this is expected to be
[simple to implement](#implement-your-own-load-firmware-method).
- **What has changed in the bind rules in DFv2?**
DFv2 nodes contain additional
[node properties generated from their FIDL service offers](#use-the-node-properties-generated-from-fidl-service-offers).
- **What has changed in logging in DFv2?**
DFv2 drivers cannot use the `zxlogf()` function or any debug library
that wraps or uses this function. `zxlogf()` is defined in
`//src/lib/ddk/include/lib/ddk/debug.h` and is removed from the
dependencies in DFv2. Drivers migrating to DFv2 need to
[stop using this library](#use-the-dfv2-logger) and other libraries
that depend on it.
However, a new [compatibility library][logging-h], which is only
available in the Fuchsia source tree (`fuchsia.git`) environment, is
now added to allow DFv2 drivers to use DFv1-style logging.
- **What has changed in inspect in DFv2?**
DFv1 drivers use driver-specific inspect functions to create and update
driver-maintained metrics. For instance, in DFv1 the
`DeviceAddArgs::set_inspect_vmo()` function is called to indicate the
VMO that the driver uses for inspect. In DFv2, however, we can just
create an [`inspect::ComponentInspector`](#use-the-dfv2-inspect) object.
- **What do dispatchers do in DFv2?**
A FIDL file generates templates and data types for a client-and-server
pair. Between these client and server ends is a channel, and the
dispatchers at each end fetch data from the channel. For more
information on dispatchers, see
[Driver dispatcher and threads][driver-dispatcher].
- **What are some issues with the new threading model when migrating
a DFv1 driver to DFv2?**
FIDL calls in DFv2 are not on a single thread basis and are asynchronous
by design (although you can make them synchronous by adding `.sync()`
to FIDL calls or using `fdf::WireSyncClient`). Drivers are generally
discouraged from making synchronous calls because they can block other
tasks from running. (However, if necessary, a driver can create a
dispatcher with the `FDF_DISPATCHER_OPTION_ALLOW_SYNC_CALLS` option,
which is only supported for
[synchronized dispatchers][synchronized-dispatchers].)
Given the differences in the threading models between Banjo (DFv1) and
FIDL (DFv2), you'll need to decide which kind of FIDL call (that is,
synchronous or asynchronous) you want to use while migrating. If your
original code is designed around the synchronous nature of Banjo and
is hard to unwind to make it all asynchronous, then you may want to
consider using the synchronous version of FIDL at first (which,
however, may result in performance degradation for the time being).
Later, you can revisit these calls and optimize them into using
synchronous calls.
- **What has changed in testing drivers in DFv2?**
The `mock_ddk` library, which is used in driver unit tests, is
specific to DFv1. [New testing libraries](#update-unit-tests-to-dfv2)
are now available for DFv2 drivers.
- **Should I fork my driver into a DFv2 version while working on migration?**
Forking an existing driver for migration depends on the complexity
of the driver. In general, it is recommended to avoid forking a
driver because it could end up creating more work. However,
for larger drivers, it may make sense to fork the driver into
a DFv2 version so that you can gradually land migration changes
in smaller patches.
You can fork a driver by adding a new driver component in the GN args
and use a flag to decide between the DFv1 or DFv2 version. This
[example CL][gc-msd-arm-mali]{:.external} demonstrates how a DFv2 fork
of the `msd-arm-mali` driver was added.
- **What are some recommended readings?**
The [DFv2 concept docs][driver-concepts] on fuchsia.dev and this
[Gerrit change][gc-intel-wifi]{:.external} from the previous DFv1
Intel WiFi driver migration (the
[`pcie-iwlwifi-driver.cc`][pcie-iwlwifi-driver-cc]{:.external} file
contains most of the new APIs).
## Update dependencies from DDK to DFv2 {:#update-dependencies-from-ddk-to-dfv2}
DFv1 drivers use the DDK library (`//src/lib/ddk`). For DFv2 drivers,
you can safely remove all package dependencies under this DDK library
directory and replace them with the following new library, which
includes most of the essential utilities for DFv2 drivers:
```none
//sdk/lib/driver/component/cpp:cpp
```
In header files, include the following library for the new DFv2 driver
interfaces:
```cpp
#include <lib/driver/component/cpp/driver_base.h>
```
Note: After updating dependencies from DDK to DFv2, your driver won't
compile until you complete the next
[Update the driver interfaces from DFv1 to DFv2](#update-the-driver-interfaces-from-dfv1-to-dfv2)
section.
## (Optional) Update dependencies for the compatibility shim {:#update-dependencies-for-the-compatibility-shim}
**For DFv1 drivers that need to talk to DFv2 drivers**, you need the
following packages for the compatibility shim:
```none
//sdk/lib/driver/compat/cpp:cpp
//sdk/lib/driver/compat/cpp:symbols
```
In header files, you need the following libraries for the compatibility shim:
```cpp
#include <lib/driver/compat/cpp/compat.h>
#include <lib/driver/compat/cpp/symbols.h>
#include <lib/driver/compat/cpp/connect.h>
```
## Update the driver interfaces from DFv1 to DFv2 {:#update-the-driver-interfaces-from-dfv1-to-dfv2}
DFv2 provides a virtual class called [`DriverBase`][driver-base] that
wraps regular routines for a driver. For DFv2 drivers, inheriting
`DriverBase` in your new driver class is recommended, which makes the
interfaces much simpler.
For example, let's say you have the following class in your DFv1 driver:
```cpp {:.devsite-disable-click-to-copy}
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();
}
```
If you update the interface to inherit from `DriverBase`, the new class
may look like below:
```cpp {:.devsite-disable-click-to-copy}
class MyExampleDriver : public fdf::DriverBase {
public:
Driver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher);
zx::result<> Start() override;
void Stop() override;
}
```
In addition to starting and stopping the driver (as shown in the example
above), the `DriverBase` class provides the objects that enable the driver
to communicate with other components (and drivers). For instance,
instead of declaring and creating your own outgoing directory in DFv1
(as in the [Migrate from Banjo to FIDL][migrate-from-banjo-to-fidl] phase),
your driver can now call the `outgoing()` method of the `DriverBase` class
to retrieve an outgoing directory, for example:
```cpp {:.devsite-disable-click-to-copy}
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_; }
...
}
```
(Source: [`driver_base.h`][driver-base])
Another useful class available in DFv2 is the `Node` class. The example
below shows a DFv2 driver's code that connects to the `Node` server and
uses the [`AddChild()`][addchild] function to add a child node:
```cpp
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();
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), {});
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to add child: %s", result.status_string());
return zx::error(result.status());
}
return zx::ok();
}
```
The [`NodeController`][nodecontroller] endpoint (`controller_endpoints`
in the example above) passed to the `AddChild()` function can be used to
control the child node. For instance, this endpoint can be used to remove
the child node from the node topology, request the driver framework to bind
the node to a specific driver, or receive a callback when the child node is
bound. The example below shows a DFv2 driver's code that removes the child
node during shutdown:
```cpp
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());
}
}
```
Moreover, in DFv2, the `OnBind()` event is defined in the `NodeController`
protocol, which is invoked by the child node from the server side. The
`DriverBase::PrepareStop()` function provides a chance to perform
de-initializations before `DriverBase::Stop()` is called.
The table below shows the mapping of the common driver and device interfaces
between DFv1 and DFv2:
| DFv1 | DFv2 |
| ----------- | ----------- |
| `zx_driver_ops::bind()` | [`DriverBase::Start()`][driver-base-99] |
| `zx_driver_ops::init()` | [`DriverBase::Start()`][driver-base-99] |
| `zx_driver_ops::release()` | [`DriverBase::Stop()`][driver-base-99] |
| `device_add()` </br></br> `DdkAdd()` | [`Node::AddChild()`][driver-base-99] |
| `device_add_composite()`</br></br>`DdkAddComposite()` | **None**. Add composite nodes using specifications. See [Composite Node][composite-node]. |
| `device_add_composite_node_spec()`</br></br>`DdkAddCompositeNodeSpec()` | [`CompositeNodeManager::AddSpec()`][composite-node-spec] |
| `zx_protocol_device::init()`</br></br>`DdkInit()` | **None**. In DFv2, a driver is in charge of the life cycle of the nodes (devices) it adds. |
| `zx_protocol_device::unbind()`</br></br>`DdkUnbind()` | **None**. In DFv2, a driver is in charge of the life cycle of the nodes (devices) it adds. |
| `zx_protocol_device::release()`</br></br>`DdkRelease()` | [`NodeController::Remove()`][nodecontroller-remove] |
| `zx_protocol_device::get_protocol()`</br></br>`device_get_protocol()` | **None**. These methods are based on Banjo protocols In DFv2, all communications are in FIDL. |
| `zx_protocol_device::service_connect()`</br></br>`device_service_connect()`</br></br>`DdkServiceConnect()` | **None**. This is an old-fashioned approach for drivers to establish FIDL connections with each other. For more information, see [Use the DFv2 service discovery](#use-the-dfv2-service-discovery). |
| `Device_connect_runtime_protocol()`</br></br>`DdkConnectRuntimeProtocol()` | **None**. These are newly added methods for service and protocol discovery in DFv1. For more information, see [Use the DFv2 service discovery](#use-the-dfv2-service-discovery). |
Also, aside from updating the interfaces, you need to change the macro
that populates your driver interface functions:
- From:
```none
ZIRCON_DRIVER()
```
- To:
```none
FUCHSIA_DRIVER_EXPORT()
```
## Use the DFv2 service discovery {:#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][runtime-protocol-test] shows the new DFv2 syntax). To update
your driver under this scenario, see the
[DFv2 driver to DFv2 driver](#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][gc-scenario-2]{:.external} 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 the
`ConnectFidl()` method through this protocol to connect to the real
protocol (for an example, see this
[Gerrit change][pcie-iwlwifi-driver-cc]{:.external}).
To update your driver under **scenario 2 or 3**, see the
[DFv1 driver to DFv2 driver (with compatibility shim)](#dfv1-driver-to-dfv2-driver)
section below.
### DFv2 driver to DFv2 driver {:#dfv2-driver-to-dfv2-driver}
To enable other DFv2 drivers to discover your driver's service,
do the following:
1. Update your driver's `.fidl` file.
The protocol discovery in DFv2 requires adding `service` fields for
the driver's protocols, for example:
```none
library fuchsia.example;
@discoverable
@transport("Driver")
protocol MyProtocol {
MyMethod() -> (struct {
...
});
};
{{ '<strong>' }}service Service {
my_protocol client_end:MyProtocol;
};{{ '</strong>' }}
```
1. Update the child driver.
DFv2 drivers can connect to protocols in the same way as FIDL services,
for example:
```cpp
driver::Connect<fuchsia_example::Service::MyProtocol>
```
You also need to update the component manifest (`.cml`) file to use
your driver runtime service, for example:
```none
use: [
{ service: "fuchsia.example.Service" },
]
```
1. Update the parent driver.
Your parent driver needs to create a `driver::OutgoingDirectory` object.
You can use the `driver::OutgoingDirectory::Create()` method or the
`driver::DriverBase` class. With the `driver::OutgoingDirectory` object,
you must use services rather than protocols.
Then you need to add the runtime service to your outgoing directory.
The example below is a driver that inherits from the `driver::DriverBase`
class:
```cpp
zx::status<> Start() override {
auto protocol = [this](
fdf::ServerEnd<fuchsia_example::MyProtocol> server_end) mutable {
fdf::BindServer(dispatcher()->get(), std::move(server_end), this);
};
fuchsia_example::Service::InstanceHandler handler(
{.my_protocol = std::move(protocol)});
auto status =
context().outgoing().AddService<fuchsia_wlan_phyimpl::Service>(
std::move(handler));
if (status.is_error()) {
return status.take_error();
}
auto result = outgoing_dir_.Serve(std::move(server_end));
if (result.is_error()) {
return result.take_error();
}
return zx::ok();
}
```
Update the child node's [`NodeAddArgs`][nodeaddargs] to include an offer
for your runtime service, for example:
```cpp
{{ '<strong>' }}auto offers =
std::vector{fdf::MakeOffer<fuchsia_example::Service>(arena, name)};{{ '</strong>' }}
fidl::WireSyncClient<fuchsia_driver_framework::Node> node(std::move(node()));
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, “example_node”)
{{ '<strong>' }}.offers(offers){{ '</strong>' }}
.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:
```cpp
capabilities: [
{ service: "fuchsia.example.Service" },
],
expose: [
{
service: "fuchsia.example.Service",
from: "self",
},
],
```
### DFv1 driver to DFv2 driver (with compatibility shim) {:#dfv1-driver-to-dfv2-driver}
To enable other DFv1 drivers to discover your DFv2 driver's service,
do the following:
1. 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](#dfv2-driver-to-dfv2-driver) section above,
for example:
- Child driver:
```none
{
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",
},
{{ '<strong>' }}use: [
{ service: "fuchsia.example.Service" },
],{{ '</strong>' }}
}
```
- Parent driver:
```none
{
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",
},
{{ '<strong>' }}capabilities: [
{ service: "fuchsia.example.Service" },
],
expose: [
{
service: "fuchsia.example.Service",
from: "self",
},
],{{ '</strong>' }}
}
```
1. Update the DFv2 driver.
The example below shows a method that exposes a service from the DFv2
parent to the DFv1 child:
```cpp
fit::result<fdf::NodeError> AddChild() {
fidl::Arena arena;
auto offer = fdf::MakeOffer<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),
.offers = std::vector{std::move(offer)},
.properties = std::vector{std::move(property)},
}
};
// 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`][root-driver-cc])
## Update component manifests of other drivers {:#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:
1. 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 the
`include` field.
- Replace the `program.compat` field with `program.binary`.
2. 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 from
[`compat.shard.cml`][compat-shard-cml] to the component manifest,
for example:
```none
use: [
{
protocol: [
"fuchsia.boot.Arguments",
"fuchsia.boot.Items",
"fuchsia.device.composite.DeprecatedCompositeCreator",
"fuchsia.device.manager.SystemStateTransition",
"fuchsia.driver.framework.CompositeNodeManager",
],
},
{ service: "fuchsia.driver.compat.Service" },
],
```
- Set the `program.runner` field to `driver`, for example:
```none
program: {
runner: "driver",
binary: "driver/compat.so",
},
```
## Expose a devfs node from the DFv2 driver {:#expose-a-devfs-node-from-the-dfv2-driver}
To expose a [`devfs`][devfs] node from a DFv2 driver, you need to add
the `device_args` member to the [`NodeAddArgs`][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`][connector] library, for example:
```cpp
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`][v2-parent-driver-cc])
For more information, see
[Expose the driver capabilities][codelab-driver-service] in the
DFv2 driver codelab. Also, see this [implementation][export-to-devfs]
of the `ExportToDevfs` method mentioned in the codelab.
## Use dispatchers {:#use-dispatchers}
[Dispatchers][driver-dispatcher] 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 this
[`aml-ethernet`][aml-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 the `Start()` 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()` and `Stop()` calls.
For more details on migrating a driver to use multiple dispatchers,
see the
[Update the DFv1 driver to use non-default dispatchers][use-non-default-dispatchers]
section (in the [Migrate from Banjo to FIDL][migrate-from-banjo-to-fidl]
phrase).
## Use the DFv2 inspect {:#use-the-dfv2-inspect}
To set up driver-maintained [inspect][driver-inspect] metrics in DFv2,
you need to create an `inspect::ComponentInspector` object, for example:
```cpp
component_inspector_ =
std::make_unique<inspect::ComponentInspector>(out, dispatcher, *inspector_);
```
(Source: [`driver-inspector.cc`][driver-inspector])
Creating an `inspect::ComponentInspector` object needs the following
three input items:
- The `component::OutgoingDirectory` object from the
`Context().outgoing()->component()` call
- A dispatcher
- The original `inspect::Inspector` object
However, the DFv2 inspect does not require passing the VMO of
`inspect::Inspector` to the driver framework.
## Use the DFv2 logger {:#use-the-dfv2-logger}
Instead of using `zxlogf()` (which is deprecated in DFv2), the new
logging mechanism in DFv2 depends on the `driver::Logger` object,
which is passed from the driver host through `DriverStartArgs`
when starting the driver.
The `driver::DriverBase` class wraps `driver::Logger` and the driver
can get its reference by calling the `logger()` method (see this
[`wlantap-driver`][wlantap-driver] driver example). With this reference,
you can print out logs using the `logger.logf()` function or using these
[macros][logger-h], for example:
```cpp
FDF_LOG(INFO, "Example log message here");
```
## (Optional) Implement your own load_firmware method {:#implement-your-own-load-firmware-method}
If your DFv1 driver calls the [`load_firmware()`][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][gc-pcie-iwlwifi-driver]{:.external}.
## (Optional) Use the node properties generated from FIDL service offers {:#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)][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:
```none
using fidl.examples.echo;
fidl.examples.echo.Echo == fidl.examples.echo.Echo.ZirconTransport;
```
For more information, see the
[Generated bind libraries][generate-bind-libraries] section of the FIDL
tutorial page.
## Update unit tests to DFv2 {:#update-unit-tests-to-dfv2}
The [`mock_ddk`][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][gc-driver-testing]{:.external}) makes
mocked FIDL servers available to DFv2 drivers through the `TestEnvironment`
class.
The following libraries are available for unit testing DFv2 drivers:
- [`//sdk/lib/driver/testing/cpp`][driver-testing-cpp]
- `TestNode` – This class implements the `fuchsia_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 an `OutgoingDirectory` 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][raii]{:.external}) 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`][driver-testing-runtime]
- `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`][fake-pdev-h] – This
helper library implements a fake version of the `pdev` FIDL protocol.
Lastly, the following example unit tests cover different configurations and
test cases:
- [`//sdk/lib/driver/component/cpp/tests/driver_base_test.cc`][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`][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 as `devfs`.
## Additional resources {:#additional-resources}
Some DFv2 drivers examples:
- [`MagmaDriverBase`][magma-driver-base]
- [`WlanTapDriver`][wlantap-driver]
- [`AdcButtonsDriver`][adc-button-driver]
All the **Gerrit changes** mentioned in this section:
- [\[iwlwifi\] Dfv2 migration for iwlwifi driver][gc-iwlwifi-driver]{:.external}
- [\[compat-runtime-test\] Migrate off usage of DeviceServer][gc-scenario-2]{:.external}
- [\[msd-arm-mali\] Add DFv2 version][gc-msd-arm-mali-top-level]{:.external}
- [\[sdk\]\[driver\]\[testing\] Add testing library][gc-driver-testing]{:.external}
All the **source code files** mentioned in this section:
- [`//examples/drivers/transport/zircon/v2/parent-driver.cc`][v2-parent-driver-cc]
- [`//sdk/fidl/fuchsia.driver.framework/topology.fidl`][topology-fidl-80]
- [`//sdk/lib/driver/component/cpp/driver_base.h`][driver-base-h-70]
- [`//sdk/lib/driver/component/cpp/tests/driver_base_test.cc`][driver-base-test-cc]
- [`//sdk/lib/driver/component/cpp/tests/driver_fidl_test.cc`][driver-fidl-test-cc]
- [`//sdk/lib/driver/compat/cpp/banjo_server.h`][banjo-server-h]
- [`//sdk/lib/driver/compat/cpp/banjo_client.h`][banjo-client-h]
- [`//sdk/lib/driver/compat/cpp/device_server.h`][device-server-h-23]
- [`//sdk/lib/driver/testing/cpp/driver_runtime.h`][driver-testing-runtime]
- [`//src/connectivity/wlan/testing/wlantap-driver/wlantap-driver.cc`][wlantap-driver]
- [`//src/devices/bus/testing/fake-pdev/fake-pdev.h`][fake-pdev-h]
- [`//src/devices/tests/v2/compat-runtime/root-driver.cc`][root-driver-cc]
- [`//src/lib/ddk/include/lib/ddk/device.h`][ddk-device-h-77]
- [`//src/lib/ddk/include/lib/ddk/driver.h`][load-firmware]
- [`//third_party/iwlwifi/platform/driver-inspector.cc`][driver-inspector]
All the **documentation pages** mentioned in this section:
- [Banjo][banjo]
- [Drivers and nodes][driver-node]
- [Driver communication][driver-communication]
- [Drivers and nodes][driver-node]
- [Driver dispatcher and threads][driver-dispatcher]
- [Drivers][driver-concepts]
- [Composite nodes][composite-node]
- [Expose the driver capabilities][codelab-driver-service]
- [Fuchsia component inspection overview][driver-inspect]
- [Mock DDK Migration][mock-ddk]
- [An Example of the Tear-Down Sequence][device-lifecycle]
(from _Device driver lifecycle_)
- [Parent Driver (The Server)][parent-driver-the-server]
(from _FIDL tutorial_)
- [Generated bind libraries][generate-bind-libraries]
(from _FIDL tutorial_)
<!-- Reference links -->
[migrate-from-banjo-to-fidl]: /docs/development/drivers/migration/migrate-from-banjo-to-fidl.md
[banjo]: /docs/development/drivers/concepts/device_driver_model/banjo.md
[driver-dispatcher]: /docs/concepts/drivers/driver-dispatcher-and-threads.md
[driver-node]: /docs/concepts/drivers/drivers_and_nodes.md
[fake-pdev-h]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/devices/bus/testing/fake-pdev/fake-pdev.h
[driver-testing-cpp]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/testing/cpp/
[driver-base-test-cc]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/component/cpp/tests/driver_base_test.cc
[device-lifecycle]: /docs/development/drivers/concepts/device_driver_model/device-lifecycle.md#an_example_of_the_tear-down_sequence
[ddk-device-h-77]: https://source.corp.google.com/fuchsia/src/lib/ddk/include/lib/ddk/device.h;l=77
[ddk-driver-h-29]: https://source.corp.google.com/fuchsia/src/lib/ddk/include/lib/ddk/driver.h;l=29
[load-firmware]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/lib/ddk/include/lib/ddk/driver.h;l=416
[logging-h]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/compat/cpp/logging.h
[synchronized-dispatchers]: /docs/concepts/drivers/driver-dispatcher-and-threads.md#synchronized-and-unsynchronized
[gc-intel-wifi]:https://fuchsia-review.git.corp.google.com/c/fuchsia/+/692243
[pcie-iwlwifi-driver-cc]: https://fuchsia-review.git.corp.google.com/c/fuchsia/+/692243/47/src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform/pcie-iwlwifi-driver.cc
[driver-base]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/component/cpp/driver_base.h?q=sdk%2Flib%2Fdriver%2Fcomponent%2Fcpp%2Fdriver_base.h&ss=fuchsia%2Ffuchsia
[driver-base-99]: https://source.corp.google.com/h/turquoise-internal/turquoise/+/main:sdk/lib/driver/component/cpp/driver_base.h;l=99
[addchild]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.driver.framework/topology.fidl;drc=53130c6bb8b33ae921bb49a561966cbdbc2d6595;l=158
[nodecontroller]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.driver.framework/topology.fidl;l=101;drc=53130c6bb8b33ae921bb49a561966cbdbc2d6595
[runtime-protocol-test]: http://cs/fuchsia/src/devices/tests/v2/runtime-protocol/
[gc-scenario-2]: https://fuchsia-review.git.corp.google.com/c/fuchsia/+/920734
[pcie-iwlwifi-driver-cc]: https://fuchsia-review.git.corp.google.com/c/fuchsia/+/692243/47/src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform/pcie-iwlwifi-driver.cc#323
[nodeaddargs]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.driver.framework/topology.fidl;l=81
[root-driver-cc]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/devices/tests/v2/compat-runtime/root-driver.cc
[compat-shard-cml]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/compat/compat.shard.cml
[devfs]: /docs/concepts/drivers/driver_communication.md#service_discovery_using_devfs
[connector]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/devfs/cpp/connector.h;l=16
[v2-parent-driver-cc]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/transport/zircon/v2/parent-driver.cc;l=93
[codelab-driver-service]: /docs/get-started/sdk/learn/driver/driver-service.md
[export-to-devfs]: https://fuchsia.googlesource.com/sdk-samples/drivers/+/refs/heads/main/src/qemu_edu/drivers/qemu_edu.cc#74
[use-non-default-dispatchers]: migrate-from-banjo-to-fidl.md#update-the-dfv1-driver-to-use-non-default-dispatchers
[aml-ethernet]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/connectivity/ethernet/drivers/aml-ethernet/aml-ethernet.cc;l=181
[driver-inspect]: /docs/development/drivers/diagnostics/inspect.md
[driver-inspector]: https://fuchsia.googlesource.com/drivers/wlan/intel/iwlwifi/+/refs/heads/main/third_party/iwlwifi/platform/driver-inspector.cc#25
[wlantap-driver]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/connectivity/wlan/testing/wlantap-driver/wlantap-driver.cc;l=61
[logger-h]: https://source.corp.google.com/h/turquoise-internal/turquoise/+/main:sdk/lib/driver/logging/cpp/logger.h;l=15
[load-firmware]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/lib/ddk/include/lib/ddk/driver.h;l=408
[gc-pcie-iwlwifi-driver]: https://fuchsia-review.git.corp.google.com/c/fuchsia/+/692243/47/src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform/pcie-iwlwifi-driver.cc#60
[parent-driver-the-server]: /docs/development/drivers/tutorials/fidl-tutorial.md#parent_driver_the_server
[generate-bind-libraries]: /docs/development/drivers/tutorials/fidl-tutorial.md#generated-bind-libraries
[mock-ddk]: /docs/contribute/open_projects/graduated/mock_ddk_migration.md
[raii]: https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
[driver-testing-runtime]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/testing/cpp/driver_runtime.h
[driver-fidl-test-cc]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/component/cpp/tests/driver_fidl_test.cc
[magma-driver-base]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/graphics/magma/lib/magma_service/sys_driver/magma_driver_base.h
[wlantap-driver]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/connectivity/wlan/testing/wlantap-driver/wlantap-driver.cc
[adc-button-driver]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/input/drivers/adc-buttons/adc-buttons.h
[driver-base-h-70]: https://source.corp.google.com/fuchsia/sdk/lib/driver/component/cpp/driver_base.h;l=70?q=driverbase&sq=package:fuchsia
[topology-fidl-80]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.driver.framework/topology.fidl;l=80
[root-driver-cc]: https://source.corp.google.com/fuchsia/src/devices/tests/v2/compat-runtime/root-driver.cc
[device-server-h-23]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/compat/cpp/device_server.h;l=23?q=deviceserver&sq=&ss=fuchsia%2Ffuchsia
[driver-concepts]: /docs/concepts/drivers/README.md
[composite-node]: /docs/development/drivers/developer_guide/composite-node.md
[composite-node-spec]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.driver.framework/composite_node_spec.fidl;l=68
[nodecontroller-remove]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.driver.framework/topology.fidl;drc=53130c6bb8b33ae921bb49a561966cbdbc2d6595;l=103
[gc-iwlwifi-driver]: https://fuchsia-review.git.corp.google.com/c/fuchsia/+/692243
[gc-msd-arm-mali]: https://fuchsia-review.git.corp.google.com/c/fuchsia/+/853637/5/src/graphics/drivers/msd-arm-mali/BUILD.gn
[gc-msd-arm-mali-top-level]: https://fuchsia-review.git.corp.google.com/c/fuchsia/+/853637
[gc-driver-testing]: https://fuchsia-review.git.corp.google.com/c/fuchsia/+/770412
[driver-communication]: /docs/concepts/drivers/driver_communication.md
[banjo-server-h]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/compat/cpp/banjo_server.h
[banjo-client-h]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/compat/cpp/banjo_client.h