Frequently asked questions for DFv1-to-DFv2 migration

Before you start working DFv1-to-DFv2 migration, 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 and those drivers won't be migrated to DFv2 all at once, you need to use this compatibility shim (by manually creating compat::DeviceServer) for enabling the drivers in different framework versions to talk to each other.

For more details on using the compatibility shim in a DFv2 driver, see the Set up the compat device server in a DFv2 driver guide.

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).
  • compat::ConnectBanjo makes it easier to connect to Banjo (see banjo_client.h).

For more details on these features, see the Serve Banjo protocols in a DFv2 driver guide.

Can DFv2 drivers use the compatibility shim for composite nodes?

The migration process for composite drivers are nearly identical to normal drivers, but composite drivers have slightly different ways for connecting to Banjo or FIDL protocols from parent nodes.

Because composite nodes have multiple parents, composite drivers need to identify the parent’s name when connecting to it. For example, below is a normal driver establishing a Banjo connection with its parent:

zx::result client_result =
  compat::ConnectBanjo<ddk::HidDeviceProtocolClient>(incoming());

The composite driver’s method is almost identical, except the parent name needs to be added:

zx::result client_result =
  compat::ConnectBanjo<ddk::HidDeviceProtocolClient>(incoming(), "gpio-int")

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 (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, through the device tree.

In DFv1, devices are controlled by zx_protocol_device while drivers are controlled by zx_driver_ops. 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 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 fdf::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 fdf::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<ServiceInstanceName>() method.
  • A DFv2 driver calls incoming()->Connect<ServiceInstanceName>() if the DriverBase class is used.

    For more information, see 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, which is routed to most components as /dev. When a driver adds a device 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 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() 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.

What has changed in the bind rules in DFv2?

DFv2 nodes contain additional node properties generated from their FIDL service offers.

However, it is unlikely that you will need to modify bind rules when migrating an existing DFv1 driver to DFv2.

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 and other libraries that depend on it.

However, a new compatibility library, 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 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.

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.)

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 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 demonstrates how a DFv2 fork of the msd-arm-mali driver was added.

The DFv2 concept docs on fuchsia.dev and this Gerrit change from the previous DFv1 Intel WiFi driver migration (the pcie-iwlwifi-driver.cc file contains most of the new APIs).