Writing a basic driver
The code structure should follow the fx create driver goldens
template.
Meta directory
Every driver directory is required to have a meta subdirectory containing
these files:
- A
bindfile defining the driver'sbindrules - A component manifest
Driver source code
Write drivers using the Driver Component library
(sdk/lib/driver/component/). Drivers should inherit from the
fdf::DriverBase2 class defined in the header
<lib/driver/component/cpp/driver_base2.h>.
The driver provides the FUCHSIA_DRIVER_EXPORT2 macro used to export the
driver symbol which is defined in the header
<lib/driver/component/cpp/driver_export2.h>. This macro should be located
in a .cc file.
Initializing a driver
Avoid placing any logic within the driver's constructor. Rather than using a
constructor, you must implement the driver’s initialization logic by overriding
exactly one of the Start() methods provided by
fdf::DriverBase2, even if the initialization logic is empty.
Ensure that only a single Start() variant is used for this purpose.
The initialization logic contains:
- Fetching and configuring all driver resources.
- Establishing service connections.
- Adding the driver's own service to the outgoing directory.
- Add child nodes (to be performed after resource setup and providing its own service).
- Release BTI from quarantine with
zx_bti_release_quarantine().
Shutting down a driver
The driver's destructor should remain free of any logic. In the majority of
scenarios, overriding and providing implementation for DriverBase2::Stop() is
unnecessary. Refrain from implementing DriverBase2::Stop() unless it is
specifically required for your driver's functionality, such as performing
graceful hardware shutdowns or unpinning DMA. The fdf::StopCompleter in the
function parameter must be called at the end of the function.
Build file
All new drivers must use Bazel for the build process and must include the following targets:
fuchsia_driver_bind_bytecodefuchsia_cc_driverfuchsia_driver_component
Driver communication
Drivers communicate with their parent drivers through FIDL services.
Serving a service
Drivers serving FIDL services are required to maintain a
fidl::ServerBindingGroup (when using Zircon transport) or an
fdf::ServerBindingGroup (for the driver transport). Unless the driver must
be restricted to a single client connection, avoid using fidl::ServerBinding
or fdf::ServerBinding.
The server bindings must be added to the driver’s outgoing directory within the
Start() function before any child nodes are created. Furthermore, the CML
file must specify the service in the capability and expose it from self.
Using a service
To use a service, drivers should include it in their bind rules and specify
it within the uses section of the CML.
Adding a child
The primary reason for adding a child node is if the driver needs to provide
services or resources for another driver. Avoid adding a child without reason.
Unless the driver needs to add child nodes dynamically, the child node should
be added as part of the initialization logic in the driver’s Start()
function.
Child nodes should be added using DriverBase2's AddChild() or the
add_child helper library (sdk/lib/driver/node/cpp/add_child.h).
The helper library should only be used if the child node needs to use a
different logger.
All child nodes should be unowned unless it’s being used to support devfs,
which is deprecated. If the child node is owned, then the driver must store the
fuchsia_driver_framework::Node client end that it receives from the helper
functions.
When adding a child node, explicitly define the child node’s name instead of
using name() from DriverBase2. Create the offers with the
node_offers helper library
(sdk/lib/driver/component/cpp/node_offers.h). Create the properties using the
node_properties library
(sdk/lib/driver/component/cpp/node_properties.h). The node property key and
values should use the generated bind library code bindings.
Logging
For logging, drivers should use format-based log APIs like fdf::info() found
in the driver logger library
(sdk/lib/driver/logging/cpp/logger.h). If you need to log a custom type,
implement the std::format.
Follow the Fuchsia logging guidelines by using warning
or error log levels when documenting failures such as FIDL errors.
Zircon resources
Interrupts
Interrupts should be retrieved and stored in the driver during initialization.
The interrupts should be wrapped by an async::Irq object
(sdk/lib/async/include/lib/async/cpp/irq.h).
An IrqMethod object should be used to handle interrupt triggers. Once the
interrupt trigger is handled, the Irq object should be acknowledged with
ack() so the interrupt is re-armed and can be triggered again.
DMA
For memory operations, drivers should use MmioBuffer, located at
sdk/lib/driver/mmio/cpp/mmio-buffer.h. This library provides a wrapper around
the raw mmio_block_t object for reading and writing.
To ensure safe access to register bitfields, it is recommended to use the
hwreg/bitfields library. This library is found at
zircon/system/ulib/hwreg/include/hwreg/bitfields.h.
The driver should take control of its BTI and stop any DMA which might be
ongoing during initialization. Once that is complete, it should tell the BTI
that it has regained control of the hardware by calling
zx_bti_release_quarantine() on it. Drivers that intend to share DMA with
hardware are required to pin the BTI beforehand. After the hardware has
finished accessing the memory, the driver is responsible for unpinning it.
Under no circumstances should the BTI be unpinned within the destructor.
Clocks
Clocks are controlled via the fuchsia.hardware.clock FIDL service.
Drivers are required to call Enable() on all clocks they depend on and
subsequently call Disable() once the clock signal is no longer needed.
Drivers must not call Disable() without first enabling the clock.
Testing
Verify that build targets include the necessary driver tests.
Unit tests
Unit tests should be written using gtests
(third_party/googletest/src/googletest/include/gtest/gtest.h).
Testing the entire driver
If the entire driver is being tested, the driver should be wrapped by
ForegroundDriverTest or BackgroundDriverTest from the
driver test library
(sdk/lib/driver/testing/cpp/driver_test.h). The unit test should call
StartDriver() right after the test is initialized and call StopDriver() in
the Teardown() function. All services and resources needed by the driver
should be initialized and served in the custom driver test’s Environment
class.
If the driver requires a platform device service, the test should instantiate a
FakePlatformDevice
(sdk/lib/driver/fake-platform-device/) DriverTestEnvironment and serve it.
Similarly, if the driver requires a FIDL service, a test implementation of the
service should be instantiated and served in the DriverTestEnvironment.
Testing parts of the driver
Tests may require specific driver environment components if only a subset of the driver's logic is being isolated for testing.
When evaluating logic that utilizes the driver logger library
(sdk/lib/driver/logging/cpp/logger.h), the test is required to instantiate
and maintain a fdf_testing::ScopedGlobalLogger instance
(sdk/lib/driver/testing/cpp/scoped_global_logger.h).
Furthermore, if a driver dispatcher is necessary for the logic, the test must
configure and hold a fdf_testing::DriverRuntime object
(sdk/lib/driver/testing/cpp/driver_runtime.h).
Fakes/mocks libraries
Tests requiring fake FIDL services or resources should utilize the fake and
mock libraries provided in sdk/lib/driver/ whenever they are available.
The following libraries are available for common FIDL services:
- //sdk/lib/driver/fake-clock -
fuchsia.hardware.clockservice. - //sdk/lib/driver/fake-gpio -
fuchsia.hardware.gpioservice. - //sdk/lib/driver/fake-interconnect -
fuchsia.hardware.interconnectservice. - //sdk/lib/driver/fake-pin -
fuchsia.hardware.pinservice. - //sdk/lib/driver/fake-platform-device -
fuchsia.hardware.platform.deviceandfuchsia.hardware.powerservices. - //sdk/lib/driver/fake-reset -
fuchsia.hardware.resetservice. - //sdk/lib/driver/fake-vreg -
fuchsia.hardware.vregservice.
The following libraries are available for common internal Zircon objects, memory regions, or driver-runtime resources:
- //sdk/lib/driver/fake-bti: Fakes Zircon BTI (
zx::bti) objects. - //sdk/lib/driver/fake-object: Fakes general Zircon kernel
objects (
zx::handle). - //sdk/lib/driver/fake-resource: Fakes Zircon resource
(
zx::resource) objects. - //sdk/lib/driver/fake-mmio-reg: Fakes MMIO registers via memory structures.
- //sdk/lib/driver/mock-mmio: Mocks MMIO regions for memory-mapped I/O register testing.
Integration tests
Integration tests should use the Driver Test Realm
(sdk/lib/driver_test_realm/) framework. To begin, the test must establish a
connection to the DriverTestRealm service, perform necessary configuration in
RealmArgs, and then call Start() with the arguments.