1# Zircon Device Model
2
3## Introduction
4
5In Zircon, device drivers are implemented as ELF shared libraries (DSOs) which are
6loaded into Device Host (devhost) processes.  The Device Manager (devmgr) process,
7contains the Device Coordinator which keeps track of drivers and devices, manages
8the discovery of drivers, the creation and direction of Device Host processes, and
9maintains the Device Filesystem (devfs), which is the mechanism through which userspace
10services and applications (constrained by their namespaces) gain access to devices.
11
12The Device Coordinator views devices as part of a single unified tree.
13The branches (and sub-branches) of that tree consist of some number of
14devices within a Device Host process.  The decision as to how to sub-divide
15the overall tree among Device Hosts is based on system policy for isolating
16drivers for security or stability reasons and colocating drivers for performance
17reasons.
18
19NOTE: The current policy is simple (each device representing a physical bus-master
20capable hardware device and its children are placed into a separate devhost).  It
21will evolve to provide finer-grained partitioning.
22
23
24## Devices, Drivers, and Device Hosts
25
26Here's a (slightly trimmed for clarity) dump of the tree of devices in
27Zircon running on Qemu x86-64:
28
29```
30$ dm dump
31[root]
32   <root> pid=1509
33      [null] pid=1509 /boot/driver/builtin.so
34      [zero] pid=1509 /boot/driver/builtin.so
35   [misc]
36      <misc> pid=1645
37         [console] pid=1645 /boot/driver/console.so
38         [dmctl] pid=1645 /boot/driver/dmctl.so
39         [ptmx] pid=1645 /boot/driver/pty.so
40         [i8042-keyboard] pid=1645 /boot/driver/pc-ps2.so
41            [hid-device-001] pid=1645 /boot/driver/hid.so
42         [i8042-mouse] pid=1645 /boot/driver/pc-ps2.so
43            [hid-device-002] pid=1645 /boot/driver/hid.so
44   [sys]
45      <sys> pid=1416 /boot/driver/bus-acpi.so
46         [acpi] pid=1416 /boot/driver/bus-acpi.so
47         [pci] pid=1416 /boot/driver/bus-acpi.so
48            [00:00:00] pid=1416 /boot/driver/bus-pci.so
49            [00:01:00] pid=1416 /boot/driver/bus-pci.so
50               <00:01:00> pid=2015 /boot/driver/bus-pci.proxy.so
51                  [bochs_vbe] pid=2015 /boot/driver/bochs-vbe.so
52                     [framebuffer] pid=2015 /boot/driver/framebuffer.so
53            [00:02:00] pid=1416 /boot/driver/bus-pci.so
54               <00:02:00> pid=2052 /boot/driver/bus-pci.proxy.so
55                  [intel-ethernet] pid=2052 /boot/driver/intel-ethernet.so
56                     [ethernet] pid=2052 /boot/driver/ethernet.so
57            [00:1f:00] pid=1416 /boot/driver/bus-pci.so
58            [00:1f:02] pid=1416 /boot/driver/bus-pci.so
59               <00:1f:02> pid=2156 /boot/driver/bus-pci.proxy.so
60                  [ahci] pid=2156 /boot/driver/ahci.so
61            [00:1f:03] pid=1416 /boot/driver/bus-pci.so
62```
63
64The names in square brackets are devices.  The names in angle brackets are
65proxy devices, which are instantiated in the "lower" devhost, when process
66isolation is being provided.  The pid= field indicates the process object
67id of the devhost process that device is contained within.  The path indicates
68which driver implements that device.
69
70Above, for example, the pid 1416 devhost contains the pci bus driver, which has
71created devices for each PCI device in the system.  PCI device 00:02:00 happens
72to be an intel ethernet interface, which we have a driver for (intel-ethernet.so).
73A new devhost (pid 2052) is created, set up with a proxy device for PCI 00:02:00,
74and the intel ethernet driver is loaded and bound to it.
75
76Proxy devices are invisible within the Device filesystem, so this ethernet device
77appears as `/dev/sys/pci/00:02:00/intel-ethernet`.
78
79
80## Protocols, Interfaces, and Classes
81
82Devices may implement Protocols, which are C ABIs used by child devices
83to interact with parent devices in a device-specific manner. The
84[PCI Protocol](../../system/ulib/ddk/include/ddk/protocol/pci.h),
85[USB Protocol](../../system/ulib/ddk/include/ddk/protocol/usb.h),
86[Block Core Protocol](../../system/ulib/ddk/include/ddk/protocol/block.h), and
87[Ethermac Protocol](../../system/ulib/ddk/include/ddk/protocol/ethernet.h), are
88examples of these.  Protocols are usually in-process interactions between
89devices in the same devhost, but in cases of driver isolation, they may take
90place via RPC to a "higher" devhost (via proxy).
91
92Devices may implement Interfaces, which are RPC protocols that clients (services,
93applications, etc) use.  The base device interface supports POSIX style
94open/close/read/write IO.  Currently, Interfaces are supported via the ioctl
95operation in the base device interface.  In the future, Fuchsia's interface definition
96language and bindings (FIDL) will be supported.
97
98In many cases a Protocol is used to allow drivers to be simpler by taking advantage
99of a common implementation of an Interface.  For example, the "block" driver implements
100the common block interface, and binds to devices implementing the Block Core Protocol,
101and the "ethernet" driver does the same thing for the Ethernet Interface and Ethermac
102Protocol.  Some protocols, such as the two cited here, make use of shared memory, and
103non-rpc signaling for more efficient, lower latency, and higher throughput than could
104be achieved otherwise.
105
106Classes represent a promise that a device implements an Interface or Protocol.
107Devices exist in the Device Filesystem under a topological path, like
108`/sys/pci/00:02:00/intel-ethernet`.  If they are a specific class, they also appear
109as an alias under `/dev/class/CLASSNAME/...`.  The `intel-ethernet` driver implements
110the Ethermac interface, so it also shows up at `/dev/class/ethermac/000`.  The names
111within class directories are unique but not meaningful, and are assigned on demand.
112
113NOTE: Currently names in class directories are 3 digit decimal numbers, but they
114are likely to change form in the future.  Clients should not assume there is any
115specific meaning to a class alias name.
116
117
118## Device Driver Lifecycle
119
120Device drivers are loaded into devhost processes when it is determined they are
121needed.  What determines if they are loaded or not is the Binding Program, which
122is a description of what device a driver can bind to.  The Binding Program is
123defined using macros in [`ddk/binding.h`](../../system/ulib/ddk/include/ddk/binding.h)
124
125An example Binding Program from the Intel Ethernet driver:
126```
127ZIRCON_DRIVER_BEGIN(intel_ethernet, intel_ethernet_driver_ops, "zircon", "0.1", 9)
128    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI),
129    BI_ABORT_IF(NE, BIND_PCI_VID, 0x8086),
130    BI_MATCH_IF(EQ, BIND_PCI_DID, 0x100E), // Qemu
131    BI_MATCH_IF(EQ, BIND_PCI_DID, 0x15A3), // Broadwell
132    BI_MATCH_IF(EQ, BIND_PCI_DID, 0x1570), // Skylake
133    BI_MATCH_IF(EQ, BIND_PCI_DID, 0x1533), // I210 standalone
134    BI_MATCH_IF(EQ, BIND_PCI_DID, 0x15b7), // Skull Canyon NUC
135    BI_MATCH_IF(EQ, BIND_PCI_DID, 0x15b8), // I219
136    BI_MATCH_IF(EQ, BIND_PCI_DID, 0x15d8), // Kaby Lake NUC
137ZIRCON_DRIVER_END(intel_ethernet)
138```
139
140The ZIRCON_DRIVER_BEGIN and _END macros include the necessary compiler directives
141to put the binding program into an ELF NOTE section, allowing it to be inspected
142by the Device Coordinator without needing to fully load the driver into its process.
143The second parameter to the _BEGIN macro is a `zx_driver_ops_t` structure pointer (defined
144by `[ddk/driver.h](../../system/ulib/ddk/include/ddk/driver.h)` which defines the
145init, bind, create, and release methods.
146
147`init()` is invoked when a driver is loaded into a Device Host process and allows for
148any global initialization.  Typically none is required.  If the `init()` method is
149implemented and fails, the driver load will fail.
150
151`bind()` is invoked to offer the driver a device to bind to.  The device is one that
152has matched the bind program the driver has published.  If the `bind()` method succeeds,
153the driver **must** create a new device and add it as a child of the device passed in
154to the `bind()` method.  See Device Lifecycle for more information.
155
156`create()` is invoked for platform/system bus drivers or proxy drivers.  For the
157vast majority of drivers, this method is not required.
158
159`release()` is invoked before the driver is unloaded, after all devices it may have
160created in `bind()` and elsewhere have been destroyed.  Currently this method is
161**never** invoked.  Drivers, once loaded, remain loaded for the life of a Device Host
162process.
163
164
165## Device Lifecycle
166
167Within a Device Host process, devices exist as a tree of `zx_device_t` structures
168which are opaque to the driver.  These are created with `device_add()` which the
169driver provides a `zx_protocol_device_t` structure to.  The methods defined by the
170function pointers in this structure are the "[device ops](device-ops.md)".  The
171various structures and functions are defined in [`device.h`](../../system/ulib/ddk/include/ddk/device.h)
172
173The `device_add()` function creates a new device, adding it as a child to the
174provided parent device.  That parent device **must** be either the device passed
175in to the `bind()` method of a device driver, or another device which has been
176created by the same device driver.
177
178A side-effect of `device_add()` is that the newly created device will be added
179to the global Device Filesystem maintained by the Device Coordinator.  If the
180device is created with the **DEVICE_ADD_INVISIBLE** flag, it will not be accessible
181via opening its node in devfs until `device_make_visible()` is invoked.  This
182is useful for drivers that have to do extended initialization or probing and
183do not want to visibly publish their device(s) until that succeeds (and quietly
184remove them if that fails).
185
186Devices are reference counted.  When a driver creates one with `device_add()`,
187it then holds a reference on that device until it eventually calls `device_remove()`.
188If a device is opened by a remote process via the Device Filesystem, a reference
189is acquired there as well.  When a device's parent is removed, its `unbind()`
190method is invoked.  This signals to the driver that it should start shutting
191the device down and remove any child devices it has created by calling `device_remove()`
192on them.
193
194Since a child device may have work in progress when its `unbind()` method is
195called, it's possible that the parent device which just called `device_remove()`
196on the child could continue to receive device method calls or protocol method
197calls on behalf of that child.  It is advisable that before removing its children,
198the parent device should arrange for these methods to return errors, so that
199calls from a child before the child removal is completed do not start more
200work or cause unexpected interactions.
201
202From the moment that `device_add()` is called without the **DEVICE_ADD_INVISIBLE**
203flag, or `device_make_visible()` is called on an invisible device, other device
204ops may be called by the Device Host.
205
206The `release()` method is only called after the creating driver has called
207`device_remove()` on the device, all open instances of that device have been
208closed, and all children of that device have been removed and released.  This
209is the last opportunity for the driver to destroy or free any resources associated
210with the device.  It is not valid to refer to the `zx_device_t` for that device
211after `release()` returns.  Calling any device methods or protocol methods for
212protocols obtained from the parent device past this point is illegal and will
213likely result in a crash.
214
215### An Example of the Tear-Down Sequence
216
217To explain how the `unbind()` and `release()` work during the tear-down process,
218below is an example of how a USB WLAN driver would usually handle it.  In short,
219the `unbind()` call sequence is top-down while the `release()` sequence is bottom-up.
220
221Note that this is just an example. This might not match what exactly the real WLAN driver
222is doing.
223
224Assume a WLAN device is plugged in as a USB device, and a PHY interface has been
225created under the USB device. In addition to the PHY interface, 2 MAC interfaces
226have been created under the PHY interface.
227
228```
229            +------------+
230            | USB Device |
231            +------------+
232                  |
233            +------------+
234            |  WLAN PHY  | .unbind()
235            +------------+ .release()
236              |        |
237    +------------+  +------------+
238    | WLAN MAC 0 |  | WLAN MAC 1 | .unbind()
239    +------------+  +------------+ .release()
240```
241
242Now, we unplug this USB WLAN device.
243
244* The USB XHCI detects the removal and calls `device_remove(usb_device)`.
245
246* Since the parent device is being removed, the WLAN PHY's `unbind()` is called.
247  In this `unbind()`, it would remove the interface it created via `device_add()`:
248
249```
250    wlan_phy_unbind(void* ctx) {
251        // Stop interrupt or anything to prevent incoming requests.
252        ...
253
254        device_remove(wlan_phy);
255    }
256```
257
258* When wlan_phy is removed, unbind() will be called on all of its children (wlan_mac_0, wlan_mac_1).
259
260```
261    wlan_mac_unbind(void* ctx) {
262        // Stop accepting new requests, and notify clients that this device is offline (often just
263        // by returning an ZX_ERR_IO_NOT_PRESENT to any requests that happen after unbind).
264        ...
265
266        device_remove(iface_mac_X);
267    }
268```
269
270* Once all the clients of a device have been removed, and that device has no children,
271  its refcount will reach zero and its release() method will be called.
272
273* WLAN MAC 0 and 1's `release()` are called.
274
275```
276    wlan_mac_release(void* ctx) {
277        // Release sources allocated at creation.
278        ...
279
280        // Delete the object here.
281        ...
282    }
283```
284
285* The wlan_phy has no open connections, but still has child devices (wlan_mac_0 and wlan_mac_1).
286  Once they have both been `release()`'d, its refcount finally reaches zero and its release()
287  method is invoked.
288
289```
290    wlan_phy_release(void* ctx) {
291        // Release sources allocated at creation.
292        ...
293
294        // Delete the object here.
295        ...
296    }
297```
298