Drivers and nodes

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.

alt_text

Diagram 1. A node topology where the green circles are nodes representing devices and the blue boxes are drivers.

Node attributes

A node has the following attributes:

  • Node properties: Key-value pairs that describe which driver can bind to the node. The keys can be of integer or string type. The values can be of integer, string, boolean, or enum type.
  • Capabilities: FIDL capabilities that are offered to a driver when it is bound to the node. These can be both in-process and out-of-process capabilities.
  • Symbols: Key-value pairs where the keys are strings and the values are virtual addresses. A driver uses these symbols to hand pointers to a child driver for in-process communication. The symbols are only provided to a driver if it is co-located in the same driver host as its parent, otherwise the driver uses FIDL calls to communicate.

A driver creating child nodes gets to assign node properties, capabilities, and symbols to the new nodes.

Node capabilities

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.

Node topology

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.

alt_text

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.

Comparison to the component topology

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.

alt_text

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.

Node lifecycle

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:

  • Create a child node with specific node properties and capabilities.
  • Drop the node, which causes the driver manager to clean up the node and its descendants in the node topology.

Node creation

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:

  • Give the child node properties that determine which drivers can bind to the node.
  • Give the child node capabilities that later can be used by a driver bound to the node.
  • Keep the child node’s 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.

Node removal

A node removal takes place when one of the following event occurs:

  • The driver bound to the node drops its Node object.
  • A node (for instance, the parent node) calls the Remove method on the target node’s NodeController object.
  • The component framework shuts down the driver component, therefore removes the driver bound to the node.

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.

Composite nodes

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:

alt_text

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.

alt_text

Diagram 5. A real-world example showing a complex binding topology for a camera controller driver