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