blob: c82710d090ad43f4945bbcff02c8582627406a36 [file] [log] [blame] [view]
# Write a minimal DFv2 driver
This guide walks through the steps involved in creating a minimal DFv2 driver.
The instructions in this guide are based on the minimal
[skeleton driver][skeleton-driver], which provides the minimum implementation
necessary to build, load, and register a new DFv2 driver in a Fuchsia system.
The steps are:
1. [Create a driver header file](#create-a-driver-header-file).
1. [Create a driver source file](#create-a-driver-source-file).
1. [Add the driver export macro](#add-the-driver-export-macro).
1. [Create a build file](#create-a-build-file).
1. [Write bind rules](#write-bind-rules).
1. [Create a driver component](#create-a-driver-component).
For more DFv2-related features, see [Additional tasks](#additional-tasks).
## Create a driver header file {:#create-a-driver-header-file .numbered}
To create a header file for your DFv2 driver, do the following:
1. Create a new header file (`.h`) for the driver (for example,
`skeleton_driver.h`).
1. Include the following interface to the header file:
```cpp
#include <lib/driver/component/cpp/driver_base.h>
```
1. Add an interface for the [`DriverBase`][driver-base] class,
for example:
```cpp
#include <lib/driver/component/cpp/driver_base.h>
namespace skeleton {
class SkeletonDriver : public fdf::DriverBase {
public:
SkeletonDriver(fdf::DriverStartArgs start_args, fdf::UnownedSynchronizedDispatcher driver_dispatcher);
// Called by the driver framework to initialize the driver instance.
zx::result<> SkeletonDriver::Start() override;
};
} // namespace skeleton
```
(Source: [`skeleton_driver.h`][skeleton-driver-h])
## Create a driver source file {:#create-a-driver-source-file .numbered}
To implement the basic methods for the `DriverBase` class,
do the following:
1. Create a new source file (`.cc`) for the driver (for example,
`skeleton_driver.cc`).
1. Include the header file created for the driver, for example:
```cpp
#include "skeleton_driver.h"
```
1. Implement the basic methods for the class, for example:
```cpp
#include "skeleton_driver.h"
namespace skeleton {
SkeletonDriver::SkeletonDriver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher)
: DriverBase("skeleton_driver", std::move(start_args),
std::move(driver_dispatcher)) {
}
zx::result<> SkeletonDriver::Start() {
return zx::ok();
}
} // namespace skeleton
```
(Source: [`skeleton_driver.cc`][skeleton-driver-cc])
This driver constructor needs to pass the driver name (for example,
`"skeleton_driver"`), `start_args`, and `driver_dispatcher` to the
`DriverBase` class.
## Add the driver export macro {:#add-the-driver-export-macro .numbered}
To add the driver export macro, do the following:
1. In the driver source file, include the following header file:
```cpp
#include <lib/driver/component/cpp/driver_export.h>
```
1. Add the following macro (which exports the driver class) at the
bottom of the driver source file:
```cpp
FUCHSIA_DRIVER_EXPORT(skeleton::SkeletonDriver);
```
For example:
```cpp
#include <lib/driver/component/cpp/driver_base.h>
#include <lib/driver/component/cpp/driver_export.h>
#include "skeleton_driver.h"
namespace skeleton {
SkeletonDriver::SkeletonDriver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher)
: DriverBase("skeleton_driver", std::move(start_args),
std::move(driver_dispatcher)) {
}
zx::result<> SkeletonDriver::Start() {
return zx::ok();
}
} // namespace skeleton
FUCHSIA_DRIVER_EXPORT(skeleton::SkeletonDriver);
```
(Source: [`skeleton_driver.cc`][skeleton-driver-cc])
## Create a build file {:#create-a-build-file .numbered}
To create a build file for the driver, do the following:
1. Create a new `BUILD.gn` file.
1. Include the following line to import the driver build rules:
```gn
import("//build/drivers.gni")
```
1. Add a target for the driver, for example:
```gn
fuchsia_cc_driver("driver") {
output_name = "skeleton_driver"
sources = [ "skeleton_driver.cc" ]
deps = [
"//sdk/lib/driver/component/cpp",
"//src/devices/lib/driver:driver_runtime",
]
}
```
(Source: [`BUILD.gn`][build-gn])
The `output_name` field must be unique among all drivers.
## Write bind rules {:#write-bind-rules .numbered}
To write bind rules for your driver, do the following:
1. Create a new bind rule file (`.bind`) for the driver
(for example, `skeleton_driver.bind`) in the `meta` directory.
1. Add basic bind rules, for example:
```
using gizmo.example;
gizmo.example.TEST_NODE_ID == "skeleton_driver";
```
(Source: [`skeleton_driver.bind`][skeleton-driver-bind])
1. In the `BUILD.gn` file, include the following line to import the
bind build rules:
```gn
import("//build/bind/bind.gni")
```
1. In the `BUILD.gn` file, add a target for the driver's bind rules,
for example:
```gn
driver_bind_rules("bind") {
rules = "meta/skeleton.bind"
bind_output = "skeleton_driver.bindbc"
deps = [ "//examples/drivers/bind_library:gizmo.example" ]
}
```
(Source: [`BUILD.gn`][build-gn])
The `bind_output` field must be unique among all drivers.
Note: To learn more about finding node properties and writing
bind rules, see [Bind Rules Tutorial][node-properties].
## Create a driver component {:#create-a-driver-component .numbered}
To create a Fuchsia component for the driver, do the following:
1. Create a new component manifest file (`.cml`) in the `meta`
directory (for example, `skeleton_driver.cml`).
1. Include the following component shards:
```
{
include: [
"inspect/client.shard.cml",
"syslog/client.shard.cml",
],
}
```
1. Add the driver's `program` information using the following format:
```
{
program: {
runner: "driver",
binary: "driver/<OUTPUT_NAME>.so",
bind: "meta/bind/<BIND_OUTPUT>",
},
}
```
The `binary` field must match the `output_name` field in the
`fuchsia_driver` target of the `BUILD.gn` file, and the `bind`
field must match `bind_output` in the `driver_bind_rules` target,
for example:
```
{
include: [
"inspect/client.shard.cml",
"syslog/client.shard.cml",
],
program: {
runner: "driver",
binary: "driver/skeleton_driver.so",
bind: "meta/bind/skeleton.bindbc",
},
}
```
(Source: [`skeleton_driver.cml`][skeleton-driver-cml])
1. Create a new JSON file to provide the component's information
(for example, `component-info.json`) in the `meta` directory.
1. Add the driver component's information in JSON format, for example:
```json
{
"short_description": "Driver Framework example for a skeleton DFv2 driver",
"manufacturer": "",
"families": [],
"models": [],
"areas": [
"DriverFramework"
]
}
```
(Source: [`component-info.json`][component-info-json])
1. In the `BUILD.gn` file, include the following line to import the component
build rules:
```gn
import("//build/components.gni")
```
1. In the `BUILD.gn` file, add a target for the driver component, for example:
```gn
fuchsia_driver_component("component") {
component_name = "skeleton"
manifest = "meta/skeleton.cml"
deps = [
":bind",
":driver"
]
info = "component-info.json"
}
```
(Source: [`BUILD.gn`][build-gn])
See the rules for these fields below:
- Set the `manifest` field to the location of the driver's `.cml` file.
- Set the `info` field to the location of the driver component
information JSON file.
- Set the `deps` array to include the `fuchsia_driver` and
`driver_bind_rules` targets from the `BUILD.gn` file.
You can now build, load, and register this DFv2 driver in a Fuchsia system
## Additional tasks {:#additional-tasks}
Note: The instructions below are based on the [Simple driver][simple-driver].
This section provides additional features you can add to your minimal DFv2
driver:
- [Add logs](#add-logs)
- [Add a child node](#add-a-child-node)
- [Clean up the driver](#clean-up-the-driver)
- [Add a compat device server](#add-a-compat-device-server)
### Add logs {:#add-logs}
By default, to print logs from a DFv2 driver, use the `FDF_LOG` macro, for
example:
```cpp
FDF_LOG(INFO, "Starting SimpleDriver")
```
In addition to using the `FDF_LOG` macro, you can also print logs using
Fuchsia's structured logger library
([`structured_logger.h`][structured-logger-h]), which uses the
`FDF_SLOG` macro.
To use structured logs from your DFv2 driver, do the following:
1. Include the following header:
```cpp
#include <lib/driver/logging/cpp/structured_logger.h>
```
1. Use the `FDF_SLOG` macro to print logs, for example:
```cpp
FDF_SLOG(ERROR, "Failed to add child", KV("status", result.status_string()));
```
### Add a child node {:#add-a-child-node}
A DFv2 driver can add child nodes using the following `Node` protocol in the
[`fuchsia.driver.framework`][topology-fidl] FIDL library:
```none {:.devsite-disable-click-to-copy}
open protocol Node {
flexible AddChild(resource struct {
args NodeAddArgs;
controller server_end:NodeController;
node server_end:<Node, optional>;
}) -> () error NodeError;
};
```
To facilitate this, during startup the driver framework provides a client of
the bound node's `Node` protocol to the DFv2 driver, through the `DriverBase`.
The driver can access its node client at any time to create child nodes on it.
However directly using this FIDL library requires a setup that includes
creating FIDL channel pairs and constructing the `NodeAddArgs` table.
Therefore the `DriverBase` class provides a set of helper functions to make
adding child nodes easier. (To see these helpers, check out the
[`driver_base.h`][driver-base-add-child] file.)
There are two types of nodes a DFv2 driver can add: **unowned** and **owned**.
The main difference between an unowned node and an owned node is whether they
participate in the [driver match process][driver-matching] or not.
The driver framework tries to find a driver that matches the properties of
unowned nodes so it can bind a driver to the node. Once a driver is matched and
bound to a node, the bound driver becomes the owner of the node.
On the other hand, owned nodes do not participate in matching since the driver
that created the node is already the owner.
#### DriverBase helper functions
The client to the node that your driver is currently bound to is stored in the
`DriverBase` object. This allows the driver to use the `DriverBase` class's
`AddChild()` and `AddOwnedChild()` functions to add a child node to this node.
However, to use these `DriverBase` helper functions, the node must not have been
moved out of the driver. If the node is moved out or your target node is not the
node that the driver is currently bound to (ie. for a grand-child node),
you need to use the namespace methods available in the
[`add_child.h`][fdf-add-child] file instead. These methods are the same as the
`DriverBase` helper functions except they can be used to add a child to a node
beyond the reach of the `DriverBase` object, by providing the correct parent
node client as a target.
Lastly, these helper functions take care of logging errors if they happen,
so no logging is needed by the driver.
#### Create an unowned node
To create an unowned node, a driver can use the `DriverBase::AddChild()` helper
functions. These functions allow setting the
properties on an unowned node, which the driver framework uses to find a matching
driver. The return result of both is a client end to the `NodeController` protocol,
which can either be kept by the driver or discarded safely.
The example code below creates an unowned node under the driver's bound node:
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/drivers/simple/dfv2/simple_driver.cc" adjust_indentation="auto" region_tag="add_child" %}
```
(Source: [`simple_driver.cc`][simple-example])
#### Create an owned node
To create an owned node, a driver can use the `DriverBase::AddOwnedChild()` helper
functions. These functions do not provide a properties argument since an owned
node does not participate in driver matching. The return result of both is an
`OwnedChildNode` object that contains a client end to the `NodeController` (which
is safe to discard) and a client end to the `Node` protocol, which is
**not safe to discard**. The driver must hold on to the `Node` client for as long as
it wants the owned node to stay around. Dropping this client will cause the driver
framework to remove the node.
The example code below creates an owned node:
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/drivers/metadata/retriever/retriever-driver.cc" adjust_indentation="auto" region_tag="add_child" %}
```
(Source: [`retriever-driver.cc`][retriever-example])
### Clean up the driver {:#clean-up-the-driver}
If a DFv2 driver needs to perform teardowns before it is stopped (for example,
stopping threads), then you need to override and implement additional
`DriverBase` methods: `PrepareStop()` and `Stop()`
The `PrepareStop()` function is called before the driver's `fdf` dispatchers are
shut down and the driver is deallocated. Therefore, the driver needs to
implement `PrepareStop()if` it needs to perform certain operations before the
driver's dispatchers shut down, for example:
```cpp
void SimpleDriver::PrepareStop(fdf::PrepareStopCompleter completer) {
// Teardown threads
FDF_LOG(INFO, "Preparing to stop SimpleDriver");
completer(zx::ok());
}
```
The `Stop()` function is called after all dispatchers belonging to this driver
are shut down, for example:
```cpp
void SimpleDriver::Stop() {
FDF_LOG(INFO, "Stopping SimpleDriver");
}
```
### Add a compat device server {:#add-a-compat-device-server}
If your DFv2 driver has descendant DFv1 drivers that haven't yet migrated to
DFv2, you need to use the compatibility shim to enable your DFv2 driver to talk
to other DFv1 drivers in the system. For more details, see the
[Set up the compat device server in a DFv2 driver][set-up-compat-device-server]
guide.
<!-- Reference links -->
[skeleton-driver]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/skeleton/
[simple-driver]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/simple/
[driver-base]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/component/cpp/driver_base.h
[skeleton-driver-h]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/skeleton/skeleton_driver.h
[skeleton-driver-cc]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/skeleton/skeleton_driver.cc
[build-gn]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/skeleton/BUILD.gn
[skeleton-driver-bind]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/skeleton/meta/skeleton_driver.bind
[node-properties]: /docs/development/drivers/tutorials/bind-rules-tutorial.md#looking_up_node_properties
[skeleton-driver-cml]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/skeleton/meta/skeleton_driver.cml
[component-info-json]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/skeleton/meta/component-info.json
[structured-logger-h]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/logging/cpp/structured_logger.h
[topology-fidl]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.driver.framework/topology.fidl
[node-adds-args]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/component/cpp/node_add_args.h
[bind-rules-tutorial]: /docs/development/drivers/tutorials/bind-rules-tutorial.md
[set-up-compat-device-server]: /docs/development/drivers/migration/set-up-compat-device-server.md
[simple-example]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/simple/dfv2/simple_driver.cc
[retriever-example]: https://cs.opensource.google/fuchsia/fuchsia/+/main:examples/drivers/metadata/retriever/retriever-driver.cc
[driver-base-add-child]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/component/cpp/driver_base.h;l=225-258
[fdf-add-child]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/driver/node/cpp/add_child.h
[driver-matching]: /docs/concepts/drivers/driver_binding.md