| # C++ driver rubric | |
| ## Writing a basic driver | |
| The code structure should follow the `fx` [create driver goldens][goldens] | |
| template. | |
| ### Meta directory | |
| Every driver directory is required to have a `meta` subdirectory containing | |
| these files: | |
| * A `bind` file defining the driver's `bind` rules | |
| * A [component manifest][component-manifest] | |
| ### Driver source code | |
| Write drivers using the [Driver Component library][driver-component] | |
| (`sdk/lib/driver/component/`). Drivers should inherit from the | |
| [fdf::DriverBase2][driver-base] 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][driver-base], 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_bytecode` | |
| * `fuchsia_cc_driver` | |
| * `fuchsia_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][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][node-offers] | |
| (`sdk/lib/driver/component/cpp/node_offers.h`). Create the properties using the | |
| [node_properties library][node-properties] | |
| (`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][driver-logger] | |
| (`sdk/lib/driver/logging/cpp/logger.h`). If you need to log a custom type, | |
| implement the `std::format`. | |
| Follow the [Fuchsia logging guidelines][logging-guidelines] by using `warning` | |
| or `error` log levels when documenting failures such as FIDL errors. | |
| ## Error handling | |
| Return [`zx::result<T>`][zx-result] from fallible functions instead of `void` | |
| or a `zx_status_t` plus an out-parameter, so the caller can handle the error. | |
| Use `fit::result<E, T>` for your own error types. Mark functions whose | |
| signature must return `zx_status_t` (C ABI, legacy interfaces) `[[nodiscard]]`. | |
| Never report success after a failure. Pass errors to completers that accept | |
| them (such as `fdf::StartCompleter`) with `take_error()` or | |
| `zx::make_result(status)`. If the completer cannot report an error | |
| (`fdf_power::ResumeCompleter`), leave the driver's state showing the failure. | |
| Log or comment any place that continues after a failure on purpose. | |
| ## Threading and synchronization | |
| Know which dispatcher runs the code before adding a lock. State accessed only | |
| from one [synchronized dispatcher][dispatcher-threads], such as the default one | |
| from `Start()`, needs no mutex. State accessed from an unsynchronized | |
| dispatcher, from several dispatchers, or from a thread the driver runtime does | |
| not manage (a dedicated interrupt thread, `std::thread`) does. Document this | |
| where the state is declared, or check `fdf::Dispatcher::GetCurrent()`. | |
| Do not hold a lock while calling anything that can re-enter the driver or wait | |
| on the same lock: update the state, release the lock, then make the call. | |
| Comment any lock held across a call on purpose. Do not release and re-acquire | |
| the lock around each call, or add a getter that takes the lock and returns a | |
| copy; both cause TOCTOU races. | |
| ## 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][mmio-buffer], 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][bitfields]. This library is found at | |
| `zircon/system/ulib/hwreg/include/hwreg/bitfields.h`. | |
| `hwreg` setters do not clamp values to the field width: they `ZX_DEBUG_ASSERT` | |
| in debug builds and overwrite neighboring fields in release builds. Check | |
| lengths, offsets, indices, and counts from outside the driver (FIDL clients, | |
| bus hosts, metadata, firmware), and values computed from them, before using | |
| them in a register, a DMA transfer size, or a buffer index. Where the value | |
| enters the driver, return `ZX_ERR_INVALID_ARGS`, or clamp if the protocol | |
| allows a maximum value; compute bounds so they cannot overflow; confirm that a | |
| client-supplied ID was registered by that client | |
| ([VMO registration][vmo-registration]). A comment naming what already | |
| guarantees the range can replace a check; see | |
| [Precondition checks][precondition-checks] for when to assert instead. | |
| 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. | |
| Atomics (`std::memory_order_release`) order memory accesses between CPUs, not | |
| accesses by a device. Before notifying the device of a DMA descriptor (a | |
| doorbell register write, an ownership bit, an index update), know whether the | |
| buffer is cached, and, at the notification, comment how the descriptor write | |
| is ordered before it. Barriers order accesses; | |
| [cache operations][dma-cache-coherency] make data visible to the device; one | |
| does not replace the other. Do not add a barrier just in case: comment every | |
| explicit barrier with the two accesses it orders. | |
| Unpin each `zx::pmt` when a transfer completes, fails, or is canceled, and at | |
| shutdown, but only after the device has stopped; if you cannot confirm that | |
| (for example, a disable timed out), leave it pinned. Destroying a `zx::pmt` | |
| without calling `unpin()` quarantines the pages until | |
| `zx_bti_release_quarantine()`. | |
| ### Clocks | |
| Clocks are controlled via the [fuchsia.hardware.clock FIDL service][clock-fidl]. | |
| 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][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][driver-test] | |
| (`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][fake-pdev] | |
| (`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][driver-logger] | |
| (`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][fake-clock] - `fuchsia.hardware.clock` service. | |
| * [//sdk/lib/driver/fake-gpio][fake-gpio] - `fuchsia.hardware.gpio` service. | |
| * [//sdk/lib/driver/fake-interconnect][fake-interconnect] - | |
| `fuchsia.hardware.interconnect` service. | |
| * [//sdk/lib/driver/fake-pin][fake-pin] - `fuchsia.hardware.pin` service. | |
| * [//sdk/lib/driver/fake-platform-device][fake-pdev] - | |
| `fuchsia.hardware.platform.device` and `fuchsia.hardware.power` services. | |
| * [//sdk/lib/driver/fake-reset][fake-reset] - `fuchsia.hardware.reset` service. | |
| * [//sdk/lib/driver/fake-vreg][fake-vreg] - `fuchsia.hardware.vreg` service. | |
| The following libraries are available for common internal Zircon objects, | |
| memory regions, or driver-runtime resources: | |
| * [//sdk/lib/driver/fake-bti][fake-bti]: Fakes Zircon BTI (`zx::bti`) objects. | |
| * [//sdk/lib/driver/fake-object][fake-object]: Fakes general Zircon kernel | |
| objects (`zx::handle`). | |
| * [//sdk/lib/driver/fake-resource][fake-resource]: Fakes Zircon resource | |
| (`zx::resource`) objects. | |
| * [//sdk/lib/driver/fake-mmio-reg][fake-mmio-reg]: Fakes MMIO registers via | |
| memory structures. | |
| * [//sdk/lib/driver/mock-mmio][mock-mmio]: Mocks MMIO regions for | |
| memory-mapped I/O register testing. | |
| ### Integration tests | |
| Integration tests should use the [Driver Test Realm][dtr] | |
| (`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. | |
| [goldens]: /tools/create/goldens/my-driver-cpp/ | |
| [component-manifest]: /docs/concepts/components/v2/component_manifests.md | |
| [driver-component]: /sdk/lib/driver/component/ | |
| [driver-base]: /sdk/lib/driver/component/cpp/driver_base2.h | |
| [add-child]: /sdk/lib/driver/node/cpp/add_child.h | |
| [node-offers]: /sdk/lib/driver/component/cpp/node_offers.h | |
| [node-properties]: /sdk/lib/driver/component/cpp/node_properties.h | |
| [driver-logger]: /sdk/lib/driver/logging/cpp/logger.h | |
| [logging-guidelines]: /docs/contribute/governance/rfcs/0003_logging.md#log_severity_levels | |
| [mmio-buffer]: /sdk/lib/driver/mmio/cpp/mmio-buffer.h | |
| [bitfields]: /zircon/system/ulib/hwreg/include/hwreg/bitfields.h | |
| [clock-fidl]: /sdk/fidl/fuchsia.hardware.clock/clock.fidl | |
| [gtests]: /third_party/googletest/src/googletest/include/gtest/gtest.h | |
| [driver-test]: /sdk/lib/driver/testing/cpp/driver_test.h | |
| [fake-pdev]: /sdk/lib/driver/fake-platform-device/ | |
| [fake-clock]: /sdk/lib/driver/fake-clock | |
| [fake-gpio]: /sdk/lib/driver/fake-gpio | |
| [fake-interconnect]: /sdk/lib/driver/fake-interconnect | |
| [fake-pin]: /sdk/lib/driver/fake-pin | |
| [fake-reset]: /sdk/lib/driver/fake-reset | |
| [fake-vreg]: /sdk/lib/driver/fake-vreg | |
| [fake-bti]: /sdk/lib/driver/fake-bti | |
| [fake-object]: /sdk/lib/driver/fake-object | |
| [fake-resource]: /sdk/lib/driver/fake-resource | |
| [fake-mmio-reg]: /sdk/lib/driver/fake-mmio-reg | |
| [mock-mmio]: /sdk/lib/driver/mock-mmio | |
| [dtr]: /sdk/lib/driver_test_realm/ | |
| [vmo-registration]: /docs/development/drivers/best_practices/vmo-registration-pattern.md | |
| [precondition-checks]: /docs/development/drivers/driver_guides/display/register-definitions.md#precondition_checks | |
| [dma-cache-coherency]: /docs/development/drivers/concepts/driver_development/dma.md#cache_coherency | |
| [zx-result]: /zircon/system/ulib/zx/include/lib/zx/result.h | |
| [dispatcher-threads]: /docs/concepts/drivers/driver-dispatcher-and-threads.md#synchronized-and-unsynchronized |