1================================== 2VDUSE - "vDPA Device in Userspace" 3================================== 4 5vDPA (virtio data path acceleration) device is a device that uses a 6datapath which complies with the virtio specifications with vendor 7specific control path. vDPA devices can be both physically located on 8the hardware or emulated by software. VDUSE is a framework that makes it 9possible to implement software-emulated vDPA devices in userspace. And 10to make the device emulation more secure, the emulated vDPA device's 11control path is handled in the kernel and only the data path is 12implemented in the userspace. 13 14Note that virtio block, network, and filesystem device types are supported 15by the VDUSE framework. Other device types may be added after the security 16implications of the corresponding device driver are clarified or fixed in 17the future. 18 19Create/Destroy VDUSE devices 20---------------------------- 21 22VDUSE devices are created as follows: 23 241. Create a new VDUSE instance with ioctl(VDUSE_CREATE_DEV) on 25 /dev/vduse/control. 26 272. Setup each virtqueue with ioctl(VDUSE_VQ_SETUP) on /dev/vduse/$NAME. 28 293. Begin processing VDUSE messages from /dev/vduse/$NAME. The first 30 messages will arrive while attaching the VDUSE instance to vDPA bus. 31 324. Send the VDPA_CMD_DEV_NEW netlink message to attach the VDUSE 33 instance to vDPA bus. 34 35VDUSE devices are destroyed as follows: 36 371. Send the VDPA_CMD_DEV_DEL netlink message to detach the VDUSE 38 instance from vDPA bus. 39 402. Close the file descriptor referring to /dev/vduse/$NAME. 41 423. Destroy the VDUSE instance with ioctl(VDUSE_DESTROY_DEV) on 43 /dev/vduse/control. 44 45The netlink messages can be sent via vdpa tool in iproute2 or use the 46below sample codes: 47 48.. code-block:: c 49 50 static int netlink_add_vduse(const char *name, enum vdpa_command cmd) 51 { 52 struct nl_sock *nlsock; 53 struct nl_msg *msg; 54 int famid; 55 56 nlsock = nl_socket_alloc(); 57 if (!nlsock) 58 return -ENOMEM; 59 60 if (genl_connect(nlsock)) 61 goto free_sock; 62 63 famid = genl_ctrl_resolve(nlsock, VDPA_GENL_NAME); 64 if (famid < 0) 65 goto close_sock; 66 67 msg = nlmsg_alloc(); 68 if (!msg) 69 goto close_sock; 70 71 if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, famid, 0, 0, cmd, 0)) 72 goto nla_put_failure; 73 74 NLA_PUT_STRING(msg, VDPA_ATTR_DEV_NAME, name); 75 if (cmd == VDPA_CMD_DEV_NEW) 76 NLA_PUT_STRING(msg, VDPA_ATTR_MGMTDEV_DEV_NAME, "vduse"); 77 78 if (nl_send_sync(nlsock, msg)) 79 goto close_sock; 80 81 nl_close(nlsock); 82 nl_socket_free(nlsock); 83 84 return 0; 85 nla_put_failure: 86 nlmsg_free(msg); 87 close_sock: 88 nl_close(nlsock); 89 free_sock: 90 nl_socket_free(nlsock); 91 return -1; 92 } 93 94How VDUSE works 95--------------- 96 97As mentioned above, a VDUSE device is created by ioctl(VDUSE_CREATE_DEV) on 98/dev/vduse/control. With this ioctl, userspace can specify some basic configuration 99such as device name (uniquely identify a VDUSE device), virtio features, virtio 100configuration space, the number of virtqueues and so on for this emulated device. 101Then a char device interface (/dev/vduse/$NAME) is exported to userspace for device 102emulation. Userspace can use the VDUSE_VQ_SETUP ioctl on /dev/vduse/$NAME to 103add per-virtqueue configuration such as the max size of virtqueue to the device. 104 105After the initialization, the VDUSE device can be attached to vDPA bus via 106the VDPA_CMD_DEV_NEW netlink message. Userspace needs to read()/write() on 107/dev/vduse/$NAME to receive/reply some control messages from/to VDUSE kernel 108module as follows: 109 110.. code-block:: c 111 112 static int vduse_message_handler(int dev_fd) 113 { 114 int len; 115 struct vduse_dev_request req; 116 struct vduse_dev_response resp; 117 118 len = read(dev_fd, &req, sizeof(req)); 119 if (len != sizeof(req)) 120 return -1; 121 122 resp.request_id = req.request_id; 123 124 switch (req.type) { 125 126 /* handle different types of messages */ 127 128 } 129 130 len = write(dev_fd, &resp, sizeof(resp)); 131 if (len != sizeof(resp)) 132 return -1; 133 134 return 0; 135 } 136 137The following control message types may be delivered via read(2) on 138/dev/vduse/$NAME: 139 140- VDUSE_GET_VQ_STATE: Get the state for virtqueue, userspace should return 141 avail index for split virtqueue or the device/driver ring wrap counters and 142 the avail and used index for packed virtqueue. 143 144- VDUSE_SET_STATUS: Set the device status, userspace should follow 145 the virtio spec: https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html 146 to process this message. For example, fail to set the FEATURES_OK device 147 status bit if the device can not accept the negotiated virtio features 148 get from the VDUSE_DEV_GET_FEATURES ioctl. 149 150- VDUSE_UPDATE_IOTLB: Notify userspace to update the memory mapping for specified 151 IOVA range, userspace should firstly remove the old mapping, then setup the new 152 mapping via the VDUSE_IOTLB_GET_FD ioctl. 153 154- VDUSE_SET_VQ_GROUP_ASID: Notify userspace to change the address space of a 155 virtqueue group (API version 1). 156 157- VDUSE_SET_VQ_READY: Notify userspace that a virtqueue should become ready or 158 not ready (when VDUSE_F_QUEUE_READY is negotiated). 159 160- VDUSE_SUSPEND: Notify userspace that the device is being suspended (when 161 VDUSE_F_SUSPEND is negotiated). 162 163After DRIVER_OK status bit is set via the VDUSE_SET_STATUS message, userspace is 164able to start the dataplane processing as follows: 165 1661. Get the specified virtqueue's information with the VDUSE_VQ_GET_INFO ioctl, 167 including the size, the IOVAs of descriptor table, available ring and used ring, 168 the state and the ready status. 169 1702. Pass the above IOVAs to the VDUSE_IOTLB_GET_FD ioctl so that those IOVA regions 171 can be mapped into userspace. Some sample codes is shown below: 172 173.. code-block:: c 174 175 static int perm_to_prot(uint8_t perm) 176 { 177 int prot = 0; 178 179 switch (perm) { 180 case VDUSE_ACCESS_WO: 181 prot |= PROT_WRITE; 182 break; 183 case VDUSE_ACCESS_RO: 184 prot |= PROT_READ; 185 break; 186 case VDUSE_ACCESS_RW: 187 prot |= PROT_READ | PROT_WRITE; 188 break; 189 } 190 191 return prot; 192 } 193 194 static void *iova_to_va(int dev_fd, uint64_t iova, uint64_t *len) 195 { 196 int fd; 197 void *addr; 198 size_t size; 199 struct vduse_iotlb_entry entry; 200 201 entry.start = iova; 202 entry.last = iova; 203 204 /* 205 * Find the first IOVA region that overlaps with the specified 206 * range [start, last] and return the corresponding file descriptor. 207 */ 208 fd = ioctl(dev_fd, VDUSE_IOTLB_GET_FD, &entry); 209 if (fd < 0) 210 return NULL; 211 212 size = entry.last - entry.start + 1; 213 *len = entry.last - iova + 1; 214 addr = mmap(0, size, perm_to_prot(entry.perm), MAP_SHARED, 215 fd, entry.offset); 216 close(fd); 217 if (addr == MAP_FAILED) 218 return NULL; 219 220 /* 221 * Using some data structures such as linked list to store 222 * the iotlb mapping. The munmap(2) should be called for the 223 * cached mapping when the corresponding VDUSE_UPDATE_IOTLB 224 * message is received or the device is reset. 225 */ 226 227 return addr + iova - entry.start; 228 } 229 2303. Setup the kick eventfd for the specified virtqueues with the VDUSE_VQ_SETUP_KICKFD 231 ioctl. The kick eventfd is used by VDUSE kernel module to notify userspace to 232 consume the available ring. This is optional since userspace can choose to poll the 233 available ring instead. 234 2354. Listen to the kick eventfd (optional) and consume the available ring. The buffer 236 described by the descriptors in the descriptor table should be also mapped into 237 userspace via the VDUSE_IOTLB_GET_FD ioctl before accessing. 238 2395. Inject an interrupt for specific virtqueue with the VDUSE_VQ_INJECT_IRQ ioctl 240 after the used ring is filled. 241 242Enabling ASID (API version 1) 243------------------------------ 244 245VDUSE supports per-address-space identifiers (ASIDs) starting with API 246version 1. Set it up with ioctl(VDUSE_SET_API_VERSION) on `/dev/vduse/control` 247and pass `VDUSE_API_VERSION_1` before creating a new VDUSE instance with 248ioctl(VDUSE_CREATE_DEV). 249 250Afterwards, ioctl(VDUSE_VQ_SETUP) takes a virtqueue group index in 251struct vduse_vq_config::group. The driver can change the address space 252of any virtqueue group by using the VDUSE_SET_VQ_GROUP_ASID message type, 253and the VDUSE instance needs to reply with VDUSE_REQ_RESULT_OK if it was 254possible to change it. 255 256Similarly, you can use ioctl(VDUSE_IOTLB_GET_FD2) to obtain the file descriptor 257describing an IOVA region of a specific ASID. Example usage: 258 259.. code-block:: c 260 261 static void *iova_to_va(int dev_fd, uint32_t asid, uint64_t iova, 262 uint64_t *len) 263 { 264 int fd; 265 void *addr; 266 size_t size; 267 struct vduse_iotlb_entry_v2 entry = { 0 }; 268 269 entry.v1.start = iova; 270 entry.v1.last = iova; 271 entry.asid = asid; 272 273 fd = ioctl(dev_fd, VDUSE_IOTLB_GET_FD2, &entry); 274 if (fd < 0) 275 return NULL; 276 277 size = entry.v1.last - entry.v1.start + 1; 278 *len = entry.v1.last - iova + 1; 279 addr = mmap(0, size, perm_to_prot(entry.v1.perm), MAP_SHARED, 280 fd, entry.v1.offset); 281 close(fd); 282 if (addr == MAP_FAILED) 283 return NULL; 284 285 /* 286 * Using some data structures such as linked list to store 287 * the iotlb mapping. The munmap(2) should be called for the 288 * cached mapping when the corresponding VDUSE_UPDATE_IOTLB 289 * message is received or the device is reset. 290 */ 291 292 return addr + iova - entry.v1.start; 293 } 294 295For more details on the uAPI, please see include/uapi/linux/vduse.h. 296