|
| 1 | +Virtual I/O (VIRTIO) |
| 2 | +########################## |
| 3 | + |
| 4 | +Overview |
| 5 | +******** |
| 6 | + |
| 7 | +Virtual I/O (VIRTIO) is a protocol used for communication with various devices, typically used in |
| 8 | +virtualized environments. Its main goal is to provide an efficient and standardized mechanism for |
| 9 | +interfacing with virtual devices from within a virtual machine. The communication relies on virtqueues |
| 10 | +and standard transfer methods like PCI or MMIO. |
| 11 | + |
| 12 | +Concepts |
| 13 | +******** |
| 14 | + |
| 15 | +Virtio defines various components used during communication and initialization. It specifies both the |
| 16 | +host (named "device" in the specification) and guest (named "driver" in the specification) sides. |
| 17 | +Currently Zephyr can only work as a guest. On top of the facilities exposed by the Virtio driver, |
| 18 | +a driver for a specific device (e.g. network card) can be implemented. |
| 19 | + |
| 20 | +A high-level overview of a system with a Virtio device is shown below. |
| 21 | + |
| 22 | +.. graphviz:: |
| 23 | + :caption: Virtual I/O overview |
| 24 | + |
| 25 | + digraph { |
| 26 | + |
| 27 | + subgraph cluster_host { |
| 28 | + style=filled; |
| 29 | + color=lightgrey; |
| 30 | + label = "Host"; |
| 31 | + labeljust=r; |
| 32 | + |
| 33 | + virtio_device [label = "virtio device"]; |
| 34 | + } |
| 35 | + |
| 36 | + transfer_method [label = "virtio transfer method"]; |
| 37 | + |
| 38 | + subgraph cluster_guest { |
| 39 | + style=filled; |
| 40 | + color=lightgrey; |
| 41 | + label = "Guest"; |
| 42 | + labeljust=r; |
| 43 | + |
| 44 | + virtio_driver [label = "virtio driver"]; |
| 45 | + specific_device_driver [label = "specific device driver"]; |
| 46 | + device_user [label = "device user"]; |
| 47 | + } |
| 48 | + |
| 49 | + virtio_device -> transfer_method; |
| 50 | + transfer_method -> virtio_device; |
| 51 | + transfer_method -> virtio_driver; |
| 52 | + virtio_driver -> transfer_method; |
| 53 | + virtio_driver -> specific_device_driver; |
| 54 | + specific_device_driver -> virtio_driver; |
| 55 | + specific_device_driver -> device_user; |
| 56 | + device_user -> specific_device_driver; |
| 57 | + } |
| 58 | + |
| 59 | +Configuration space |
| 60 | +=================== |
| 61 | +Each device provides configuration space, used for initialization and configuration. It allows |
| 62 | +selection of device and driver features, enabling specific virtqueues and setting their addresses. |
| 63 | +Once the device is configured, most of its configuration cannot be changed without resetting the device. |
| 64 | +The exact layout of the configuration space depends on the transfer method. |
| 65 | + |
| 66 | +Driver and device features |
| 67 | +-------------------------- |
| 68 | +The configuration space provides a way to negotiate feature bits, determining some non-mandatory |
| 69 | +capabilities of the devices. The exact available feature bits depend on the device and platform. |
| 70 | + |
| 71 | +Device-specific configuration |
| 72 | +----------------------------- |
| 73 | +Some of the devices offer device-specific configuration space, providing additional configuration options. |
| 74 | + |
| 75 | +Virtqueues |
| 76 | +========== |
| 77 | +The main mechanism used for transferring data between host and guest is a virtqueue. Specific |
| 78 | +devices have different numbers of virtqueues, for example devices supporting bidirectional transfer |
| 79 | +usually have one or more tx/rx virtqueue pairs. Virtio specifies two types of virtqueues: split |
| 80 | +virtqueues and packed virtqueues. Zephyr currently supports only split virtqueues. |
| 81 | + |
| 82 | +Split virtqueues |
| 83 | +---------------- |
| 84 | +A split virtqueue consists of three parts: descriptor table, available ring and used ring. |
| 85 | + |
| 86 | +The descriptor table holds descriptors of buffers, that is their physical addresses, lengths and flags. |
| 87 | +Each descriptor is either device writeable or driver writeable. The descriptors can be chained, creating |
| 88 | +descriptor chains. Typically a chain begins with descriptors containing the data for the device to read |
| 89 | +and ends with the device writeable part, where the device places its response. |
| 90 | + |
| 91 | +The main part of the available ring is a circular buffer of references (in the form of indexes) to the |
| 92 | +descriptors in the descriptor table. Once the guest decides to send the data to the host, it adds the index of |
| 93 | +the head of the descriptor chain to the top of the available ring. |
| 94 | + |
| 95 | +The used ring is similar to the available ring, but it's used by the host to return descriptors to the guest. In |
| 96 | +addition to storing descriptor indexes, it also provides information about the amount of data written to them. |
| 97 | + |
| 98 | +Common Virtio libraries |
| 99 | +*********************** |
| 100 | + |
| 101 | +Zephyr provides an API for interfacing with Virtio devices and virtqueues, which allows performing necessary operations |
| 102 | +over the lifetime of the Virtio device. |
| 103 | + |
| 104 | +Device initialization |
| 105 | +===================== |
| 106 | +Once the Virtio driver finishes performing low-level initialization common to the all devices using a given transfer method, |
| 107 | +like finding device on the bus and mapping Virtio structures, the device specific driver steps in and performs the next |
| 108 | +stages of initialization with the help of the Virtio API. |
| 109 | + |
| 110 | +The first thing the device-specific driver does is feature bits negotiation. It uses :c:func:`virtio_read_device_feature_bit` |
| 111 | +to determine which features the device offers, and then selects the ones it needs using :c:func:`virtio_write_driver_feature_bit`. |
| 112 | +After all required features have been selected, the device-specific driver calls :c:func:`virtio_commit_feature_bits`. Then, virtqueues |
| 113 | +are initialized with :c:func:`virtio_init_virtqueues`. This function enumerates the virtqueues, invoking the provided callback |
| 114 | +:c:type:`virtio_enumerate_queues` to determine the required size of each virtqueue. Initialization process is finalized by calling |
| 115 | +:c:func:`virtio_finalize_init`. From this point, if none of the functions returned errors, the virtqueues are operational. If the |
| 116 | +specific device provides one, the device-specific config can be obtained by calling :c:func:`virtio_get_device_specific_config`. |
| 117 | + |
| 118 | +Virtqueue operation |
| 119 | +=================== |
| 120 | +Once the virtqueues are operational, they can be used to send and receive data. To do so, the pointer to the nth |
| 121 | +virtqueue has to be acquired using :c:func:`virtio_get_virtqueue`. To send data consisting of a descriptor chain, |
| 122 | +:c:func:`virtq_add_buffer_chain` has to be used. Along the descriptor chain, it takes pointer to the callback that |
| 123 | +will be invoked once the device returns the given descriptor chain. After that, the virtqueue has to be notified using |
| 124 | +:c:func:`virtio_notify_virtqueue` from the Virtio API. |
| 125 | + |
| 126 | +API Reference |
| 127 | +************* |
| 128 | + |
| 129 | +.. doxygengroup:: virtio_interface |
| 130 | +.. doxygengroup:: virtqueue_interface |
0 commit comments