Important: This page contains information that is specific to the new version of the driver framework (DFv2).
In Fuchsia, every driver is bound to a node. A node is the main building block of the driver framework. A node can be thought of as a hardware or virtual device, or a node can be a part of a hardware device. For example, a GPIO node can represent a single GPIO pin attached to a GPIO controller while a RAM disk node can represent a virtual disk, not a real hardware device.
Drivers, when bound to nodes, can create child nodes. As a result, nodes form a directed acyclic graph representing all known hardware and virtual devices in a Fuchsia system.
Diagram 1. A node topology where the green circles are nodes representing devices and the blue boxes are drivers.
A node has the following properties:
A driver creating child nodes gets to assign binding properties, capabilities, and symbols to the new nodes.
A node represents a collection of resources, which in turn represents a hardware or virtual device in a Fuchsia system. When a parent driver creates a new node, the parent driver specifies which capabilities are associated with that node. When a child driver binds to that node, the driver framework routes the capabilities associated with the node to the incoming namespace of the child driver.
However, not all capabilities in a driver’s incoming namespace are from a parent driver. Some capabilities may come from non-driver components in the system.
Fuchsia's driver manager maintains a node topology that describes the parent-child relationships between nodes (representing devices) in a Fuchsia system. Starting with the root node, the driver bound to the root node creates child nodes – there’s no limit on how many children a node can have. The drivers that are bound to these child nodes often create their own children. As a result, these nodes form a single node topology, expressed in a directed acyclic graph, which describes all the hardware and virtual devices discovered in a Fuchsia system.
Diagram 2. Example of a USB bus topology.
In the example above, the USB bus driver (usb-bus-driver
) is bound to a node (usb-bus
) representing the USB bus. The driver then creates a child node for every new USB device discovered in the system. Depending on the node’s properties, each USB device node can have a specific USB driver bound to it. For instance, the USB keyboard driver (usb-keyboard-driver
) is bound to one of the USB device nodes. From which, we can guess that the usb-device-2
node likely represents a keyboard device connected to the system through a USB port.
Similar to the driver framework, the component framework has its own topology where components can declare children. However, the node topology and the component topology are kept separate. The first reason is that not all nodes may have drivers bound to them. This means that these unbound nodes are not associated with any specific components shown in the component topology. In fact, many nodes in the node topology are commonly left unbound (that is, unmatched with drivers) while the system is running. Second, in the node topology a node may have multiple parent nodes (see Composite nodes), which isn’t allowed in the component topology.
From the component framework’s perspective, the drivers’ topology appears to be flattened. The driver framework keeps the drivers in three flat collections of components: the boot collection, the package collection, and the universe package collection. In the component topology, all driver components appear to be siblings of each other under their parent component, which is the driver manager.
Diagram 3: The component topology showing driver components in three collections
However, unlike other components in the component topology, the driver framework is responsible for setting a driver component’s moniker. The driver framework names the moniker of a driver component based on the driver’s place in the node topology, not the component topology. For instance, a PCI driver’s component moniker may look like /bootstrap/boot-drivers:root.sys.platform.pci.00_14_0
. This component moniker implies the following node topology for the PCI driver: root
-> sys
-> platform
-> pci
-> 00_14_0
. The component moniker shows that this PCI driver component is only 2 layers down in the component topology, but the node (00_14_0
) bound to the PCI driver is 5 layers down in the node topology.
A driver’s lifetime in a Fuchsia system is tied to the lifetime of the node it’s bound to.
A driver can perform the following lifecycle actions on a Node
object it controls:
A node is created when the AddChild
FIDL method is called on an existing node. Either the driver that owns the existing node or another driver that has access to the node object can trigger a child node creation on the node.
When a driver creates a child node, the driver can take the following actions:
NodeController
object, which allows the parent driver to stop the child node.When a driver creates a child node, the driver has the option of owning that node. To own the child node, the driver adds an extra argument to the AddChild
call. If the driver requests to own the child node, the driver framework doesn't bind a new driver to the node. However, if the child node is not owned by the driver, the driver framework attempts to find a different driver that can bind to the new node.
When a driver creates a child node, the driver also has the ability to hold on to a NodeController
object of the child node. The driver can use this object (by calling Remove
) to stop the child node at any time, which results in stopping the driver bound to the node. The driver receives the OnBind
FIDL event through this object when the child node is bound to a driver.
A node removal takes place when one of the following event occurs:
Node
object.Remove
method on the target node’s NodeController
object.In the node topology, nodes are removed from the bottom first. If a node is bound to a driver, then the driver must be stopped before the node can be removed. And if the node to be removed is in the middle of the node topology, the driver framework ensures that all the child drivers are stopped and all the child nodes are removed before the target node is removed.
The driver manager creates a composite node when a driver wishes to bind itself to multiple parent nodes. In this case, the driver manager adds the composite node to each parent node as a child node. The driver manager then binds the driver to the composite node.
A composite node has composite bind rules, which enables the driver to specify a different set of bind rules for each parent node. A composite node’s capabilities are forwarded from each parent node, enabling the driver to access the combined capabilities of the parent nodes. However, bind properties are not forwarded from the parent nodes to the composite node. In the case of a composite node creation, the driver framework creates a composite node in response to a driver binding to multiple parent nodes, so it already knows the driver that will be bound to the node.
A camera may be created with a composite node as shown below:
Diagram 4. Representation of a composite node.
In the example above, a camera device is discovered in a Fuchsia system. This camera device has a GPIO pin that turns the camera on and a PCI device that transfers picture data out of the camera. The driver index matches this camera device to a driver named camera-driver
. This driver has composite bind rules indicating that it wishes to have two parent nodes: one for a GPIO device and the other for a PCI device. The driver manager creates a composite node named camera
and adds the node as a child to both the gpio-enable
node and the pci-device
node. The parent nodes then forward their capabilities to the composite node. Lastly, the driver manager binds the camera-driver
driver to the composite node.
Diagram 5. A real-world example showing a complex binding topology for a camera controller driver