The code structure should follow the fx create driver goldens template.
Every driver directory is required to have a meta subdirectory containing these files:
bind file defining the driver's bind rulesWrite 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.
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:
zx_bti_release_quarantine().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.
All new drivers must use Bazel for the build process and must include the following targets:
fuchsia_driver_bind_bytecodefuchsia_cc_driverfuchsia_driver_componentDrivers communicate with their parent drivers through FIDL services.
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.
To use a service, drivers should include it in their bind rules and specify it within the uses section of the CML.
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.
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.
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.
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 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.
Verify that build targets include the necessary driver tests.
Unit tests should be written using gtests (third_party/googletest/src/googletest/include/gtest/gtest.h).
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.
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).
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:
fuchsia.hardware.clock service.fuchsia.hardware.gpio service.fuchsia.hardware.interconnect service.fuchsia.hardware.pin service.fuchsia.hardware.platform.device and fuchsia.hardware.power services.fuchsia.hardware.reset service.fuchsia.hardware.vreg service.The following libraries are available for common internal Zircon objects, memory regions, or driver-runtime resources:
zx::bti) objects.zx::handle).zx::resource) objects.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.