blob: ccacad05a43d82fda15e198a1a4893e9993d398a [file] [log] [blame] [view]
# Update DDK interfaces to DFv2
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`][ddk]).
In DFv2, this library is replaced with the [driver component][driver-component]
library, which includes most of the essential utilities for DFv2 drivers.
## Update dependencies from DDK to DFv2 {:#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,
Note: After updating dependencies from DDK to DFv2, your driver won't
compile until you complete the next
[Update interfaces from DDK to DFv2](#update-interfaces-from-ddk-to-dfv2)
section.
### Source headers
Replace DDK dependencies in source header files:
* {DFv1}
```none
//sdk/lib/driver/component/cpp:cpp
```
* {DFv2}
```cpp
#include <lib/driver/component/cpp/driver_base.h>
```
### Build dependencies
Replace DDK dependencies in the `BUILD.gn` files:
* {DFv1}
```gn
"//src/lib/ddk",
"//src/lib/ddktl",
```
* {DFv2}
```gn
"//sdk/lib/driver/component/cpp:cpp",
```
## Update interfaces from DDK to DFv2 {:#update-interfaces-from-ddk-to-dfv2}
### ddk:Device mixin
`ddk::Device<>` is a mixin defined in [`ddktl/device.h`][device-h] that
simplifies writing DDK drivers in C++.
* {DFv1}
The example of a DFv1 driver below uses the `ddk::Device<>` mixin:
```cpp
#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`][driver-base] class is used to write drivers.
DFv2 drivers need to inherit from the `DriverBase` class instead of the
`ddk::Device` mixin, for example:
```cpp
#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:
```cpp
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:
```cpp
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}
```cpp
#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}
```cpp
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][dfv1-simple-driver]).
In DFv2, the teardown logic needs to be implemented in `PrepareStop()` and
`Stop()` methods of the `DriverBase` class, for example:
```cpp
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:
```cpp
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`][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:
```cpp
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:
```cpp
#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}
```cpp
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}
```cpp
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:
1. Add a `ServerBindingGroup` to your driver class.
1. Use `fidl::ServerBindingGroup` for Zircon transport and
`fdf::ServerBindingGroup` for driver transport, for example:
```cpp
class I2cDriver: public DriverBase {
...
private:
fidl::ServerBindingGroup<fidl_i2c::Device> bindings_;
}
```
1. Add the bindings to the outgoing directory, for example:
```cpp
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:
```cpp
zx_status_t status = device->DdkAdd(ddk::DeviceAddArgs("i2c");
```
To add a child node in DFv2, see the [Add a child node][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:
```cpp
auto status = DdkAddCompositeNodeSpec("ft3x27_touch", spec);
```
For instructions on how to add a composite node spec in DFv2,
see the [Create a composite node][composite-nodes] 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`][banjo-client] library.
* {DFv1}
```cpp
misc_protocol_t misc;
zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_MISC, &misc);
if (status != ZX_OK) {
return status;
}
```
* {DFv2}
```cpp
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()`](#ddkconnectfidlprotocol)
- [`DdkConnectFragmentFidlProtocol()`](#ddkconnectfragmentfidlprotocol)
- [`DdkConnectRuntimeProtocol()`](#ddkconnectruntimeprotocol)
- [`DdkConnectFragmentRuntimeProtocol()`](#ddkconnectfragmentruntimeprotocol)
In DFv2, you use the [driver namespace][driver-namespace] library to connect
to the FIDL protocols.
#### DdkConnectFidlProtocol()
* {DFv1}
```cpp
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][dfv1-driver-transport-zircon])
* {DFv2}
```cpp
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][dfv2-driver-transport-zircon])
#### DdkConnectFragmentFidlProtocol()
* {DFv1}
```cpp
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}
```cpp
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}
```cpp
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][dfv1-driver-transport-driver])
* {DFv2}
```cpp
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][dfv2-driver-transport-driver])
#### DdkConnectFragmentRuntimeProtocol()
* {DFv1}
```cpp
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}
```cpp
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:
1. Complete the [Set up a compat device server][set-up-a-compat-device-server]
guide.
1. Follow the instructions in the
[Forward, add, and parse DFv1 metadata][forward-add-and-parse] section.
#### DdkAddMetadata()
* {DFv1}
```cpp
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}
```cpp
compat_server->inner().AddMetadata(DEVICE_METADATA_PRIVATE,
metadata.value().data(), sizeof(metadata.value()));
```
#### ddk::GetEncodedMetadata<>()
* {DFv1}
```cpp
auto decoded =
ddk::GetEncodedMetadata<fuchsia_hardware_i2c_businfo::wire::I2CBusMetadata>(
parent, DEVICE_METADATA_I2C_CHANNELS);
```
* {DFv2}
```cpp
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 {:#migrating-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:
```cpp
#include <lib/ddk/trace/event.h>
```
* {DFv2}
To use the `TRACE_DURATION` macro in DFv2, change the `include` line to the
following header:
```cpp
#include <lib/trace/event.h>
```
And add the following line to the build dependencies:
```cpp
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][add-logs] section in the
_Write a minimal DFv2 driver_ guide.
<!-- Reference links -->
[ddk]: /docs/development/drivers/concepts/driver_development/using-ddktl.md
[driver-component]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/component/cpp/
[device-h]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/lib/ddktl/include/ddktl/device.h
[driver-base]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/component/cpp/driver_base.h
[dfv1-simple-driver]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/simple/dfv1/simple_driver.cc
[driver-export]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/component/cpp/driver_export.h
[add-a-child-node]: /docs/development/drivers/developer_guide/write-a-minimal-dfv2-driver.md#add-a-child-node
[composite-nodes]: /docs/development/drivers/developer_guide/create-a-composite-node.md
[banjo-client]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/compat/cpp/include/lib/driver/compat/cpp/banjo_client.h
[driver-namespace]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/incoming/cpp/namespace.h
[dfv1-driver-transport-zircon]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/transport/zircon/v1/child-driver.cc
[dfv2-driver-transport-zircon]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/transport/zircon/v2/child-driver.cc
[dfv1-driver-transport-driver]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/transport/driver/v1/child-driver.cc
[dfv2-driver-transport-driver]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/transport/driver/v2/child-driver.cc
[set-up-a-compat-device-server]: /docs/development/drivers/migration/set-up-compat-device-server.md#set-up-the-compat-device-server
[forward-add-and-parse]: /docs/development/drivers/migration/set-up-compat-device-server.md#forward-add-and-parse-dfv1-metadata
[add-logs]: /docs/development/drivers/developer_guide/write-a-minimal-dfv2-driver.md#add-logs