blob: 58da7093c3c952f666454857ff935cc646534166 [file] [view] [edit]
# Rust 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
The Rust driver source code should be in a `src` subdirectory in a file called
`lib.rs`. The Rust source code should use the [fdf\_component library][fdf-component]
to define the driver.
The driver must be defined as a struct that implements the
[fdf\_component::Driver trait][driver-trait]. The implemented
[start method][start-method] receives a [DriverContext][driver-context] struct
which contains structures necessary to connect to and serve protocols and logs
for the driver. Additionally, the driver code must use the `driver_register!()`
macro to register the driver with the Driver Framework.
### Component manifest
The component manifest should not declare the main dispatcher to
`allow_sync_calls` since Rust drivers must be asynchronous.
### Initializing a driver
All initialization logic should be placed in the [start method][start-method]
and must store the Node handle from the [DriverContext][driver-context] to the
`Driver` struct. The initialization logic contains:
* Take (using [DriverContext::take\_node][take-node]) and store the
[Node][node-struct] object for the driver until shutdown. Dropping the `Node`
object will cause the driver to be shut down, intentionally or not. In most
cases this will be unused, but should be stored in the `Driver` object anyways
(as `_node` to silence the warning about it being unused). This will correctly
drop the `Node` when the driver shuts down.
* 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 own
service).
* Release BTI from quarantine.
### Shutting down a driver
In the majority of scenarios, `Driver` `stop()` implementation is empty. Refrain
from adding more to it unless explicit clean up specifically required for your
driver's functionality, such as performing graceful hardware shutdowns or
unpinning DMA.
If a driver controls when it should be shut down, it should store the
[Node][node-struct] object as something that can be dropped, like in an
`Option<Node>` or potentially behind a mutex if necessary. Dropping the `Node`
object will start the driver shutting down.
## Build file
All Rust drivers must use GN for the build process and must include the
following targets:
* `fuchsia_driver_bind_bytecode`
* `fuchsia_rust_driver`
* `fuchsia_driver_component`
## Driver communication
Drivers communicate with their parent drivers through FIDL services.
### Serving a service
Use [examples/drivers/transport/driver/rust\_next/parent][rust-next-parent] and
[examples/drivers/transport/zircon/rust\_next/parent/][zircon-next-parent] as
guidance for using the `rust_next` bindings, which Fuchsia *recommends* for
normal FIDL transport services, and *requires* for driver transport services (as
the old bindings do not support it).
Drivers serving FIDL services must implement the FIDL service as a trait. During
initialization, it needs to add a `ServiceOffer` in an outgoing directory and
then serve the directory to the `DriverContext` with the `serve_outgoing()`
function. Once the directory is served, spawn a task with the
[fuchsia\_async Scope library][fasync-scope] to run the `ServiceFs` event loop
in.
The CML file must specify the service in the capability and expose it from
`self`.
### Using a service
Use [examples/drivers/transport/driver/rust\_next/child][rust-next-child] and
[examples/drivers/transport/zircon/rust\_next/child/][zircon-next-child] as
guidance for using the `rust_next` bindings, which Fuchsia *recommends* for
normal FIDL transport services, and *requires* for driver transport services (as
the old bindings do not support it).
To use a service, drivers should include it in their `bind` rules and specify it
within the `uses` section of the CML. When connecting to services, use the
service capability instead of the protocol.
## 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 necessary, the child node should be added as part of the initialization
logic in the `start()` function.
Child nodes should be added using the Rust wrapper
[`Node::add_child`][node-add-child]. All child nodes should be unowned unless
it’s being used to support `devfs`, which is unsupported in Rust drivers.
Therefore, there should be no owned children.
## Logging
Use the standard [Rust logging API][rust-logging] for all logs. Follow the
[Fuchsia logging guidelines][logging-guidelines] by using `warning` or `error`
log levels when documenting failures such as FIDL errors.
## Zircon resources
Use the Zircon kernel bindings for resources such as `Vmo`, and `Interrupt`.
### Interrupts
Avoid storing interrupts as raw `zx::Handle` objects, as this necessitates using
`unsafe` code during wait operations. Instead, define your device struct as
generic over K: [zx::InterruptKind][interrupt-kind] (for example, `Device<K>`)
using the [Zircon interrupt bindings][zircon-interrupt]
([sdk/rust/zx/src/interrupt.rs][zircon-interrupt-source]) and use it to wrap and
store the handle within a [zx::Interrupt][zx-interrupt]\<K\> object.
Use [fasync::OnInterrupt][fasync-interrupt] to create a stream of interrupts and
process them in an async task instead of spawning a dedicated thread with
`std::thread::spawn` and blocking on `irq.wait()`.
### DMA
MMIO handles must be mapped into a [MmioRegion][mmio-region] object using
[`VmoMapping::map()`][vmo-map] ([sdk/lib/driver/mmio/rust/][mmio-rust-source]).
Drivers must never perform manual bitwise operations (for example,
`val |= 1 << 5;`) on raw integers when accessing MMIO registers. Instead,
they should use the `mmio::register!` and `mmio::register_block!` macros
### 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.
## Asynchronous code
Do not detach tasks using [`.detach()`][task-detach]. Instead, initialize a
[`fuchsia_async::Scope`][fasync-scope-struct] within the driver's `start` method
and use it to spawn concurrent work. The driver must retain ownership of the
`Scope`.
## Testing
Verify that build targets include the necessary driver tests.
### Unit Testing
You can and should test as much of your driver’s code in normal unit tests. To
"unit test" something while including driver startup and shutdown, you can use
the [fdf\_component::testing::TestHarness][test-harness] to start your driver
and interact with it.
If your driver declaration had an output\_name of `my_driver`, then the GN
targets for your driver would be `my_driver_test`. See the GN rules for any of
the rust example drivers in [//examples/drivers][examples-drivers] for
examples.
### Integration Tests
Use the [DriverTestRealm library][dtr-lib] to write integration tests, just like
you would with a C++ driver.
### General testing advice
When testing drivers that use `MmioRegion`:
* Prefer real VMOs: For simple use cases back the memory region with a real VMO,
don't mock `read`/`write` methods.
* Advanced usages: Mocking `read`/`write` is reasonable for complex scenarios,
consider using [`MockMemoryOps`][mock-memory-ops] or something like it.
* Use VMO Injection: In tests, create a `zx::Vmo`, map it using
`VmoMapping::map()`, and pass the resulting `MmioRegion` to the driver.
[goldens]: /tools/create/goldens/my-driver-rust/
[component-manifest]: /docs/concepts/components/v2/component_manifests.md
[fdf-component]: https://fuchsia-docs.firebaseapp.com/rust/fdf_component/index.html
[driver-trait]: https://fuchsia-docs.firebaseapp.com/rust/fdf_component/trait.Driver.html
[start-method]: https://fuchsia-docs.firebaseapp.com/rust/fdf_component/trait.Driver.html#tymethod.start
[driver-context]: https://fuchsia-docs.firebaseapp.com/rust/fdf_component/struct.DriverContext.html
[take-node]: https://fuchsia-docs.firebaseapp.com/rust/fdf_component/struct.DriverContext.html#method.take_node
[node-struct]: https://fuchsia-docs.firebaseapp.com/rust/fdf_component/struct.Node.html
[rust-next-parent]: /examples/drivers/transport/driver/rust_next/parent/
[zircon-next-parent]: /examples/drivers/transport/zircon/rust_next/parent/
[fasync-scope]: /src/lib/fuchsia-async/src/runtime/portable/scope.rs
[rust-next-child]: /examples/drivers/transport/driver/rust_next/child/
[zircon-next-child]: /examples/drivers/transport/zircon/rust_next/child/
[node-add-child]: /src/devices/bin/driver_manager_rust/node/src/add.rs
[rust-logging]: https://docs.rs/log/latest/log/
[logging-guidelines]: /docs/contribute/governance/rfcs/0003_logging.md#log_severity_levels
[interrupt-kind]: https://fuchsia-docs.firebaseapp.com/rust/fidl_next/fuchsia/zx/trait.InterruptKind.html
[zircon-interrupt]: https://fuchsia-docs.firebaseapp.com/rust/fidl_next/fuchsia/zx/struct.Interrupt.html
[zircon-interrupt-source]: /sdk/rust/zx/src/interrupt.rs
[zx-interrupt]: https://fuchsia-docs.firebaseapp.com/rust/fidl_next/fuchsia/zx/struct.Interrupt.html
[fasync-interrupt]: https://fuchsia-docs.firebaseapp.com/rust/fuchsia_async/struct.OnInterrupt.html
[mmio-region]: https://fuchsia-docs.firebaseapp.com/rust/mmio/region/struct.MmioRegion.html
[vmo-map]: https://fuchsia-docs.firebaseapp.com/rust/mmio/vmo/struct.VmoMapping.html#method.map
[mmio-rust-source]: /sdk/lib/driver/mmio/rust/
[clock-fidl]: /sdk/fidl/fuchsia.hardware.clock/clock.fidl
[task-detach]: https://fuchsia-docs.firebaseapp.com/rust/fuchsia_async/struct.Task.html#method.detach
[fasync-scope-struct]: https://fuchsia-docs.firebaseapp.com/rust/fuchsia_async/struct.Scope.html
[test-harness]: https://fuchsia-docs.firebaseapp.com/rust/fdf_component/testing/harness/struct.TestHarness.html
[examples-drivers]: /examples/drivers
[dtr-lib]: https://fuchsia-docs.firebaseapp.com/rust/fuchsia_driver_test/index.html
[mock-memory-ops]: https://fuchsia-docs.firebaseapp.com/rust/mmio/mock/struct.MockMemoryOps.html