This page provides best practices and examples for migrating driver interfaces from DFv1 to DFv2.
DFv1 drivers use the DDK (Driver Development Kit) library (//src/lib/ddk
).
In DFv2, this library is replaced with the driver component
library, which includes most of the essential utilities for DFv2 drivers.
Update dependencies from DDK to DFv2
Remove all package dependencies under the DDK library in your DFv1 driver and replace them with the new DFv2 library,
Source headers
Replace DDK dependencies in source header files:
DFv1
//sdk/lib/driver/component/cpp:cpp
DFv2
#include <lib/driver/component/cpp/driver_base.h>
Build dependencies
Replace DDK dependencies in the BUILD.gn
files:
DFv1
"//src/lib/ddk",
"//src/lib/ddktl",
DFv2
"//sdk/lib/driver/component/cpp:cpp",
Update interfaces from DDK to DFv2
ddk:Device mixin
ddk::Device<>
is a mixin defined in ddktl/device.h
that
simplifies writing DDK drivers in C++.
DFv1
The example of a DFv1 driver below uses the ddk::Device<>
mixin:
#include <ddktl/device.h>
class SimpleDriver;
using DeviceType = ddk::Device<SimpleDriver, ddk::Initializable>;
class SimpleDriver : public DeviceType {
public:
explicit SimpleDriver(zx_device_t* parent);
~SimpleDriver();
static zx_status_t Bind(void* ctx, zx_device_t* dev);
void DdkInit(ddk::InitTxn txn);
void DdkRelease();
};
} // namespace simple
DFv2
In DFv2, the DriverBase
class is used to write drivers.
DFv2 drivers need to inherit from the DriverBase
class instead of the
ddk::Device
mixin, for example:
#include <lib/driver/component/cpp/driver_base.h>
class SimpleDriver : public fdf::DriverBase {
public:
SimpleDriver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher);
zx::result<> Start() override;
};
Constructor, init hook, and bind hook
In DFv1, the driver is initialized through the constructor, the init hook, and the bind hook.
The init hook is typically implemented as DdkInit()
, for example:
void SimpleDriver::DdkInit(ddk::InitTxn txn) {}
The bind hook is a static function that is passed to the zx_driver_ops_t
struct, for example:
zx_status_t SimpleDriver::Bind(void* ctx, zx_device_t* dev) {}
static zx_driver_ops_t simple_driver_ops = [][5] -> zx_driver_ops_t {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = SimpleDriver::Bind;
return ops;
}();
In DFv2, all of the DFv1 logic needs to go into the DriverBase
class's
Start()
function in the following order: Constructor, Init hook, and
Bind hook.
DFv1
#include <ddktl/device.h>
class SimpleDriver;
using DeviceType = ddk::Device<SimpleDriver, ddk::Initializable>;
class SimpleDriver : public DeviceType {
public:
explicit SimpleDriver(zx_device_t* parent) {
zxlogf(INFO, "SimpleDriver constructor logic");
}
~SimpleDriver() { delete this; }
static zx_status_t Bind(void* ctx, zx_device_t* dev) {
zxlogf(INFO, "SimpleDriver bind logic");
}
void DdkInit(ddk::InitTxn txn) {
zxlogf(INFO, "SimpleDriver initialized");
}
};
} // namespace simple
static zx_driver_ops_t simple_driver_ops = [][5] -> zx_driver_ops_t {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = SimpleDriver::Bind;
return ops;
}();
DFv2
class SimpleDriver : public fdf::DriverBase {
public:
SimpleDriver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher);
zx::result<> Start() override {
zxlogf(INFO, "SimpleDriver constructor logic");
zxlogf(INFO, "SimpleDriver initialized");
zxlogf(INFO, "SimpleDriver bind logic");
}
};
Destructor, DdkSuspend(), DdkUnbind(), and DdkRelease()
In DFv1, the destructor, DdkSuspend()
, DdkUnbind()
, and DdkRelease()
are
used for tearing down objects and threads (for more information on these hooks,
see this DFv1 simple driver example).
In DFv2, the teardown logic needs to be implemented in PrepareStop()
and
Stop()
methods of the DriverBase
class, for example:
class SimpleDriver : public fdf::DriverBase {
public:
SimpleDriver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher);
...
zx::result<> PrepareStop(fdf::PrepareStopCompleter completer) override{
// Called before the driver dispatchers are shutdown. Only implement this function
// if you need to manually clean up objects (ex/ unique_ptrs) in the driver dispatchers");
completer(zx::ok());
}
zx::result<> Stop() {
// This is called after all driver dispatchers are shutdown.
// Use this function to perform any remaining teardowns
}
};
Also, when migrating to DFv2, exclude self-delete statements in the DdkRelease()
methods, for example:
void DdkRelease() {
delete this; // Do not migrate this statement to DFv2
}
ZIRCON_DRIVER() macro
In DFv1, a driver is declared through the ZIRCON_DRIVER()
macro. In DFv2, the
driver_export
library replaces the ZIRCON_DRIVER()
macro.
The driver export needs to be declared in the driver implementation, typically
in the .cc
file.
DFv1
An example of the ZIRCON_DRIVER()
macro:
static zx_driver_ops_t simple_driver_ops = [][5] -> zx_driver_ops_t {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = SimpleDriver::Bind;
return ops;
}();
ZIRCON_DRIVER(SimpleDriver, simple_driver_ops, "zircon", "0.1");
DFv2
The driver_export
example below migrates the DFv1 ZIRCON_DRIVER()
macro
example:
#include <lib/driver/component/cpp/driver_export.h>
...
FUCHSIA_DRIVER_EXPORT(simple::SimpleDriver);
ddk::Messageable mixin
In DFv1, the ddk::Messageable<>
mixin is used to implement a FIDL server.
In DFv2, the ddk:Messageable<>
mixin can be replaced by directly inheriting
from the FIDL server.
DFv1
using I2cDeviceType = ddk::Device<I2cChild, ddk::Messageable<fuchsia_hardware_i2c::Device>::Mixin>;
class I2cDriver : public I2cDeviceType {
...
// Functions from the fuchsia.hardware.i2c Device protocol
void Transfer(TransferRequestView request, TransferCompleter::Sync& completer) override;
void GetName(GetNameCompleter::Sync& completer) override;
}
DFv2
class I2cDriver : public DriverBase, public fidl::WireServer<fuchsia_hardware_i2c::Device> {
...
// Functions from the fuchsia.hardware.i2c Device protocol
void Transfer(TransferRequestView request, TransferCompleter::Sync& completer) override;
void GetName(GetNameCompleter::Sync& completer) override;
}
After implementing the FIDL server, you need to add the service to the outgoing directory:
Add a
ServerBindingGroup
to your driver class.Use
fidl::ServerBindingGroup
for Zircon transport andfdf::ServerBindingGroup
for driver transport, for example:class I2cDriver: public DriverBase { ... private: fidl::ServerBindingGroup<fidl_i2c::Device> bindings_; }
Add the bindings to the outgoing directory, for example:
auto serve_result = outgoing()->AddService<fuchsia_hardware_i2c::Service>( fuchsia_hardware_i2c::Service::InstanceHandler({ .device = bindings_.CreateHandler(this, dispatcher()->async_dispatcher(), fidl::kIgnoreBindingClosure), })); if (serve_result.is_error()) { FDF_LOG(ERROR, "Failed to add Device service %s", serve_result.status_string()); return serve_result.take_error(); }
DdkAdd() and device_add()
The DdkAdd()
and device_add()
methods are used to add child nodes,
for example:
zx_status_t status = device->DdkAdd(ddk::DeviceAddArgs("i2c");
To add a child node in DFv2, see the Add a child node section in the Write a minimal DFv2 driver guide.
DdkAddCompositeNodeSpec()
The DdkAddCompositeNodeSpec()
method adds a composite node spec to
the system, for example:
auto status = DdkAddCompositeNodeSpec("ft3x27_touch", spec);
For instructions on how to add a composite node spec in DFv2, see the Create a composite node guide.
device_get_protocol()
The device_get_protocol()
method is used to retrieve and connect to
a Banjo server. In DFv2, we replace the call with the
compat/cpp/banjo_client
library.
DFv1
misc_protocol_t misc;
zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_MISC, &misc);
if (status != ZX_OK) {
return status;
}
DFv2
zx::result<ddk::MiscProtocolClient> client =
compat::ConnectBanjo<ddk::MiscProtocolClient>(incoming());
FIDL connect functions
In DFv1, DDK provides the following functions for connecting to FIDL protocols:
DdkConnectFidlProtocol()
DdkConnectFragmentFidlProtocol()
DdkConnectRuntimeProtocol()
DdkConnectFragmentRuntimeProtocol()
In DFv2, you use the driver namespace library to connect to the FIDL protocols.
DdkConnectFidlProtocol()
DFv1
zx::result<fidl::ClientEnd<fuchsia_examples_gizmo::Device>> client_end =
DdkConnectFidlProtocol<fuchsia_examples_gizmo::Service::Device>();
if (client_end.is_error()) {
zxlogf(ERROR, "Failed to connect fidl protocol");
return client_end.status_value();
}
(Source: DFv1 driver transport example)
DFv2
zx::result<fidl::ClientEnd<fuchsia_examples_gizmo::Device>> connect_result =
incoming()->Connect<fuchsia_examples_gizmo::Service::Device>();
if (connect_result.is_error()) {
FDF_SLOG(ERROR, "Failed to connect gizmo device protocol.",
KV("status", connect_result.status_string()));
return connect_result.take_error();
}
(Source: DFv2 driver transport example)
DdkConnectFragmentFidlProtocol()
DFv1
zx::result<fidl::ClientEnd<fuchsia_examples_gizmo::Device>> client_end =
DdkConnectFragmentFidlProtocol<fuchsia_examples_gizmo::Service::Device>(parent, "gizmo");
if (codec_client_end.is_error()) {
zxlogf(ERROR, "Failed to connect to fidl protocol: %s",
zx_status_get_string(codec_client_end.status_value()));
return codec_client_end.status_value();
}
DFv2
zx::result<fidl::ClientEnd<fuchsia_examples_gizmo::Device>> connect_result =
incoming()->Connect<fuchsia_examples_gizmo::Service::Device>("gizmo");
if (connect_result.is_error()) {
FDF_LOG(ERROR, "Failed to connect gizmo device protocol: %s",
connect_result.status_string());
return connect_result.take_error();
}
DdkConnectRuntimeProtocol()
DFv1
zx::result<fidl::ClientEnd<fuchsia_examples_gizmo::Device>> client_end =
DdkConnectRuntimeProtocol<fuchsia_examples_gizmo::Service::Device>();
if (client_end.is_error()) {
zxlogf(ERROR, "Failed to connect fidl protocol");
return client_end.status_value();
}
(Source: DFv1 driver transport example)
DFv2
zx::result<fidl::ClientEnd<fuchsia_examples_gizmo::Device>> connect_result =
incoming()->Connect<fuchsia_examples_gizmo::Service::Device>();
if (connect_result.is_error()) {
FDF_SLOG(ERROR, "Failed to connect gizmo device protocol.",
KV("status", connect_result.status_string()));
return connect_result.take_error();
}
(Source: DFv2 driver transport example)
DdkConnectFragmentRuntimeProtocol()
DFv1
zx::result<fidl::ClientEnd<fuchsia_examples_gizmo::Device>> client_end =
DdkConnectFragmentRuntimeProtocol<fuchsia_examples_gizmo::Service::Device>("gizmo_parent");
if (client_end.is_error()) {
zxlogf(ERROR, "Failed to connect fidl protocol");
return client_end.status_value();
}
DFv2
zx::result<fidl::ClientEnd<fuchsia_examples_gizmo::Device>> connect_result =
incoming()->Connect<fuchsia_examples_gizmo::Service::Device>("gizmo_parent");
if (connect_result.is_error()) {
FDF_SLOG(ERROR, "Failed to connect gizmo device protocol.",
KV("status", connect_result.status_string()));
return connect_result.take_error();
}
Metadata
In DFv1, drivers can add and retrieve metadata using functions such as
DdkAddMetadata()
and ddk::GetEncodedMetadata<>()
. See the code examples
below.
To migrate these metadata functions to DFv2, do the following:
Complete the Set up a compat device server guide.
Follow the instructions in the Forward, add, and parse DFv1 metadata section.
DdkAddMetadata()
DFv1
zx_status_t status = dev->DdkAddMetadata(DEVICE_METADATA_PRIVATE,
metadata.value().data(), sizeof(metadata.value());
if (status != ZX_OK) {
zxlogf(ERROR, "DdkAddMetadata failed: %s", zx_status_get_string(status));
}
DFv2
compat_server->inner().AddMetadata(DEVICE_METADATA_PRIVATE,
metadata.value().data(), sizeof(metadata.value()));
ddk::GetEncodedMetadata<>()
DFv1
auto decoded =
ddk::GetEncodedMetadata<fuchsia_hardware_i2c_businfo::wire::I2CBusMetadata>(
parent, DEVICE_METADATA_I2C_CHANNELS);
DFv2
fidl::Arena arena;
zx::result i2c_bus_metadata =
compat::GetMetadata<fuchsia_hardware_i2c_businfo::wire::I2CBusMetadata>(
incoming(), arena, DEVICE_METADATA_I2C_CHANNELS);
Migrate other interfaces
TRACE_DURATION
The TRACE_DURATION()
method is available for both DFv1 and DFv2.
DFv1
In DFv1, the TRACE_DURATION
macro is imported from the following DDK
library:
#include <lib/ddk/trace/event.h>
DFv2
To use the TRACE_DURATION
macro in DFv2, change the include
line to the
following header:
#include <lib/trace/event.h>
And add the following line to the build dependencies:
fuchsia_cc_driver("i2c-driver") {
...
deps = [
...
"//zircon/system/ulib/trace",
]
}
zxlogf()
The zxlogf()
method is not available in DFv2 and needs to be migrated to
FDF_LOG()
or FDF_SLOG()
.
For instructions, see the Add logs section in the Write a minimal DFv2 driver guide.