Fuchsia drivers are components dynamically loaded into driver host processes in user space. The process of loading and coordinating drivers is managed by the driver manager. See the Driver framework and Drivers and nodes documentation for more information on driver hosts, driver manager, and the device topology.
Directory structure
Drivers may be found throughout the source tree under drivers subdirectories of
areas as specified in the source code layout
document. Most Fuchsia drivers are located under //src/devices/.
They are grouped based on the hardware FIDL protocols they implement, defined under
//sdk/fidl/. For example, a USB ethernet driver goes in
//src/connectivity/ethernet/drivers/
because it implements an ethernet protocol, while drivers that implement the
USB controller stack are located in //src/devices/usb/drivers/.
In the driver's BUILD.gn, four target types are typically defined:
driver_bind_rules: Compiles the.bindfile into driver bind bytecode.fuchsia_cc_driver(orfuchsia_rust_driver): Compiles the driver shared library.fuchsia_driver_component: Wraps the driver shared library and bind rules into a driver component with its component manifest (.cml).fuchsia_driver_package: Packages the driver component for inclusion in the system.
For detailed instructions and examples on configuring these GN targets, refer to Building drivers.
Creating a new driver
Creating a new driver can be done using the Create Tool:
fx create driver --path <PATH> --lang cppThis command creates the directory <PATH> containing template driver sources,
where the last segment of <PATH> becomes the driver name and GN target name.
After running this command, follow these steps:
- Define bind rules in
meta/<NAME>.bind. - Configure the component manifest in
meta/<NAME>.cml. - Add driver metadata in
meta/<NAME>-info.json, including ashort_descriptionandareasmatching an entry in//build/drivers/areas.txt. - Implement the driver class in C++ (inheriting from
fdf::DriverBase2) or Rust. - Include the
teststarget in the<PATH>:testsbuild target to ensure CQ coverage.
Declaring a driver and bind rules
Drivers are matched to devices (nodes) using declarative bind rules placed in a
.bind file alongside the driver. The bind compiler compiles these rules into
bytecode that the driver index evaluates without needing to load the driver into
memory.
The following example illustrates bind rules for an AHCI PCI device:
using fuchsia.pci;
using fuchsia.pci.massstorage;
fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE;
fuchsia.BIND_PCI_CLASS == fuchsia.pci.BIND_PCI_CLASS.MASS_STORAGE;
fuchsia.BIND_PCI_SUBCLASS == fuchsia.pci.massstorage.BIND_PCI_SUBCLASS_SATA;
fuchsia.BIND_PCI_INTERFACE == 0x01;
fuchsia.BIND_COMPOSITE == 1;
In BUILD.gn, declare the bind target:
driver_bind_rules("bind") {
rules = "meta/ahci.bind"
bind_output = "ahci.bindbc"
deps = [
"//src/devices/bind/fuchsia.pci",
"//src/devices/bind/fuchsia.pci.massstorage",
]
}
For more details on authoring bind rules, see Driver binding and the Bind rules tutorial.
In C++, the driver class inherits from fdf::DriverBase2 and exports itself using
the FUCHSIA_DRIVER_EXPORT2 macro:
#include <lib/driver/component/cpp/driver_base2.h>
#include <lib/driver/component/cpp/driver_export2.h>
class AhciDriver : public fdf::DriverBase2 {
public:
AhciDriver() = default;
zx::result<> Start(fdf::DriverContext context) override;
};
FUCHSIA_DRIVER_EXPORT2(AhciDriver);
When publishing child nodes that downstream drivers can bind to, specify node
properties using fuchsia_driver_framework::NodeProperty2 and fdf::MakeProperty2:
std::vector<fuchsia_driver_framework::NodeProperty2> properties = {
fdf::MakeProperty2(bind_fuchsia::PCI_VID, device_info.vendor_id),
fdf::MakeProperty2(bind_fuchsia::PCI_DID, device_info.device_id),
};
zx::result child = AddChild("sata-child", properties, offers);
Driver lifecycle
Start
The driver framework initializes a driver instance by calling its Start() hook.
Drivers receive an fdf::DriverContext providing access to their incoming namespace,
outgoing directory, node control, and dispatcher.
zx::result<> AhciDriver::Start(fdf::DriverContext context) {
// Access incoming services and initialize hardware.
return zx::ok();
}
Drivers should make no assumptions about the state of hardware when Start() is
invoked; the device may not be in a clean reset state if a previous driver host
crashed or restarted.
Drivers should avoid blocking the driver dispatcher with long-running operations
during Start(). If a driver needs to perform asynchronous initialization (such
as loading firmware or waiting on hardware calibration), implement the asynchronous
Start() overload that takes an fdf::StartCompleter:
void AhciDriver::Start(fdf::DriverContext context, fdf::StartCompleter completer) {
// Perform asynchronous initialization on the dispatcher.
// When complete:
completer(zx::ok());
}
PrepareStop and Stop
When a driver is being shut down or unbound, the framework invokes the teardown hooks:
PrepareStop(fdf::PrepareStopCompleter completer): The driver should stop servicing new client requests, cancel pending hardware transactions, and complete outstanding asynchronous tasks. Once finished, invokecompleter(zx::ok()).Stop(): Final cleanup before the driver instance is destroyed.
Driver communication
All communication in Fuchsia occurs over capabilities routed through the component framework, primarily FIDL service capabilities.
Connecting to parent services
Drivers connect to FIDL protocols exposed by parent nodes or the platform using their incoming namespace:
zx::result pci = context.incoming().Connect<fuchsia_hardware_pci::Service::Device>();
if (pci.is_error()) {
return pci.take_error();
}
Advertising services to children and components
A driver can advertise FIDL services to child drivers or non-driver components by adding service handlers to its outgoing directory and offering them when adding a child:
// 1. Add service to outgoing directory
zx::result result = context.outgoing()->AddService<fuchsia_example::EchoService>(
std::move(handler));
// 2. Offer service to child node
std::array offers = {
fdf::MakeOffer2<fuchsia_example::EchoService>(),
};
zx::result child = AddChild("echo-child", properties, offers);
For a comprehensive guide, see Driver communication and the FIDL tutorial.
Colocation and transport
Drivers that require high-throughput, low-latency communication with parent or child
drivers can be co-located within the same driver host process by setting
colocate: "true" in their component manifest. Co-located drivers can use the
Driver runtime transport (fdf::Channel) for in-process,
zero-copy communication without kernel context switches. Drivers in separate processes
communicate across Zircon channels.
Device interrupts
Hardware interrupts are represented by Zircon zx::interrupt
kernel objects. Drivers request an interrupt capability from their parent bus driver
(such as the platform bus or PCI driver).
Drivers should listen to interrupts asynchronously on their synchronized driver
dispatcher using async::IrqMethod:
#include <lib/async/cpp/irq.h>
class MyDriver : public fdf::DriverBase2 {
public:
zx::result<> Start(fdf::DriverContext context) override {
irq_method_.set_handler(
[this](async_dispatcher_t* dispatcher, async::IrqMethod* irq,
zx_status_t status, const zx_packet_interrupt_t* packet) {
OnInterrupt(status);
});
return zx::make_result(irq_method_.Post(dispatcher(), interrupt_.get()));
}
private:
void OnInterrupt(zx_status_t status) {
// Acknowledge interrupt and process hardware events.
interrupt_.ack();
}
zx::interrupt interrupt_;
async::IrqMethod irq_method_{this};
};
For more details and Rust examples, see Handle interrupts in a driver.
Driver rights and hardware resources
Drivers run in restricted user space processes with sandboxed namespaces. Drivers do not have arbitrary access to the filesystem, raw memory, or root resources.
Privileged hardware resources (such as MMIO ranges, interrupts, and Bus Transaction Initiators for DMA) are managed and partitioned by bus drivers such as the Platform Bus or PCI driver. Drivers request access to their assigned resources via FIDL protocols provided by their parent node.
Power management
Drivers integrate with the Fuchsia Power Framework through Driver Framework power
elements. By enabling runtime-managed dispatchers (power_managed_dispatchers_enabled: "true"
in the driver manifest), the driver runtime coordinates power transitions:
- Suspending: When entering a low-power state, power-managed dispatchers are paused,
and the driver's
SystemSuspendhook is called on the always-on dispatcher to save hardware state. - Resuming: When waking up, the driver's
SystemResumehook restores hardware state before dispatchers resume processing requests.
For detailed guidelines, see Device power management.