Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright (c) 2023 Red Hat, Inc.
3 : : */
4 : :
5 : : #include <uapi/linux/vduse.h>
6 : :
7 : : #include <stdint.h>
8 : : #include <stdio.h>
9 : : #include <unistd.h>
10 : : #include <fcntl.h>
11 : :
12 : : #include <linux/virtio_net.h>
13 : :
14 : : #include <sys/ioctl.h>
15 : : #include <sys/mman.h>
16 : : #include <sys/stat.h>
17 : :
18 : : #include <rte_common.h>
19 : : #include <rte_eal_paging.h>
20 : : #include <rte_thread.h>
21 : :
22 : : #include "fd_man.h"
23 : : #include "iotlb.h"
24 : : #include "vduse.h"
25 : : #include "vhost.h"
26 : : #include "virtio_net_ctrl.h"
27 : :
28 : : #define VHOST_VDUSE_API_VERSION 0
29 : : #define VDUSE_CTRL_PATH "/dev/vduse/control"
30 : :
31 : : struct vduse {
32 : : struct fdset *fdset;
33 : : };
34 : :
35 : : static struct vduse vduse;
36 : :
37 : : static const char * const vduse_reqs_str[] = {
38 : : "VDUSE_GET_VQ_STATE",
39 : : "VDUSE_SET_STATUS",
40 : : "VDUSE_UPDATE_IOTLB",
41 : : };
42 : :
43 : : #define vduse_req_id_to_str(id) \
44 : : (id < RTE_DIM(vduse_reqs_str) ? \
45 : : vduse_reqs_str[id] : "Unknown")
46 : :
47 : : static int
48 : 0 : vduse_inject_irq(struct virtio_net *dev, struct vhost_virtqueue *vq)
49 : : {
50 : 0 : return ioctl(dev->vduse_dev_fd, VDUSE_VQ_INJECT_IRQ, &vq->index);
51 : : }
52 : :
53 : : static void
54 : 0 : vduse_iotlb_remove_notify(uint64_t addr, uint64_t offset, uint64_t size)
55 : : {
56 : 0 : munmap((void *)(uintptr_t)addr, offset + size);
57 : 0 : }
58 : :
59 : : static int
60 : 0 : vduse_iotlb_miss(struct virtio_net *dev, uint64_t iova, uint8_t perm __rte_unused)
61 : : {
62 : : struct vduse_iotlb_entry entry;
63 : : uint64_t size, page_size;
64 : : struct stat stat;
65 : : void *mmap_addr;
66 : : int fd, ret;
67 : :
68 : 0 : entry.start = iova;
69 : 0 : entry.last = iova + 1;
70 : :
71 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_IOTLB_GET_FD, &entry);
72 [ # # ]: 0 : if (ret < 0) {
73 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to get IOTLB entry for 0x%" PRIx64,
74 : : iova);
75 : 0 : return -1;
76 : : }
77 : :
78 : : fd = ret;
79 : :
80 : 0 : VHOST_CONFIG_LOG(dev->ifname, DEBUG, "New IOTLB entry:");
81 : 0 : VHOST_CONFIG_LOG(dev->ifname, DEBUG, "\tIOVA: %" PRIx64 " - %" PRIx64,
82 : : (uint64_t)entry.start, (uint64_t)entry.last);
83 : 0 : VHOST_CONFIG_LOG(dev->ifname, DEBUG, "\toffset: %" PRIx64, (uint64_t)entry.offset);
84 : 0 : VHOST_CONFIG_LOG(dev->ifname, DEBUG, "\tfd: %d", fd);
85 : 0 : VHOST_CONFIG_LOG(dev->ifname, DEBUG, "\tperm: %x", entry.perm);
86 : :
87 : 0 : size = entry.last - entry.start + 1;
88 : 0 : mmap_addr = mmap(0, size + entry.offset, entry.perm, MAP_SHARED, fd, 0);
89 [ # # ]: 0 : if (mmap_addr == MAP_FAILED) {
90 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
91 : : "Failed to mmap IOTLB entry for 0x%" PRIx64 ": %s",
92 : : iova, strerror(errno));
93 : : ret = -1;
94 : 0 : goto close_fd;
95 : : }
96 : :
97 : 0 : ret = fstat(fd, &stat);
98 [ # # ]: 0 : if (ret < 0) {
99 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to get page size.");
100 : 0 : munmap(mmap_addr, entry.offset + size);
101 : 0 : goto close_fd;
102 : : }
103 : 0 : page_size = (uint64_t)stat.st_blksize;
104 : :
105 : 0 : vhost_user_iotlb_cache_insert(dev, entry.start, (uint64_t)(uintptr_t)mmap_addr,
106 : 0 : entry.offset, size, page_size, entry.perm);
107 : :
108 : : ret = 0;
109 : 0 : close_fd:
110 : 0 : close(fd);
111 : :
112 : 0 : return ret;
113 : : }
114 : :
115 : : static struct vhost_backend_ops vduse_backend_ops = {
116 : : .iotlb_miss = vduse_iotlb_miss,
117 : : .iotlb_remove_notify = vduse_iotlb_remove_notify,
118 : : .inject_irq = vduse_inject_irq,
119 : : };
120 : :
121 : : static void
122 : 0 : vduse_control_queue_event(int fd, void *arg, int *close __rte_unused)
123 : : {
124 : : struct virtio_net *dev = arg;
125 : : uint64_t buf;
126 : : int ret;
127 : :
128 : 0 : ret = read(fd, &buf, sizeof(buf));
129 [ # # ]: 0 : if (ret < 0) {
130 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to read control queue event: %s",
131 : : strerror(errno));
132 : 0 : return;
133 : : }
134 : :
135 : 0 : VHOST_CONFIG_LOG(dev->ifname, DEBUG, "Control queue kicked");
136 [ # # ]: 0 : if (virtio_net_ctrl_handle(dev))
137 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to handle ctrl request");
138 : : }
139 : :
140 : : static void
141 : 0 : vduse_vring_setup(struct virtio_net *dev, unsigned int index, bool reconnect)
142 : : {
143 : 0 : struct vhost_virtqueue *vq = dev->virtqueue[index];
144 : : struct vhost_vring_addr *ra = &vq->ring_addrs;
145 : 0 : struct vduse_vq_info vq_info = { 0 };
146 : : struct vduse_vq_eventfd vq_efd;
147 : : int ret;
148 : :
149 : 0 : vq_info.index = index;
150 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_VQ_GET_INFO, &vq_info);
151 [ # # ]: 0 : if (ret) {
152 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to get VQ %u info: %s",
153 : : index, strerror(errno));
154 : 0 : return;
155 : : }
156 : :
157 [ # # ]: 0 : if (reconnect) {
158 : 0 : vq->last_avail_idx = vq->reconnect_log->last_avail_idx;
159 : 0 : vq->last_used_idx = vq->reconnect_log->last_avail_idx;
160 : : } else {
161 : 0 : vq->last_avail_idx = vq_info.split.avail_index;
162 : 0 : vq->last_used_idx = vq_info.split.avail_index;
163 : : }
164 : 0 : vq->size = vq_info.num;
165 : 0 : vq->ready = true;
166 : 0 : vq->enabled = vq_info.ready;
167 : 0 : ra->desc_user_addr = vq_info.desc_addr;
168 : 0 : ra->avail_user_addr = vq_info.driver_addr;
169 : 0 : ra->used_user_addr = vq_info.device_addr;
170 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "VQ %u info:", index);
171 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tnum: %u", vq_info.num);
172 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tdesc_addr: %llx",
173 : : (unsigned long long)vq_info.desc_addr);
174 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tdriver_addr: %llx",
175 : : (unsigned long long)vq_info.driver_addr);
176 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tdevice_addr: %llx",
177 : : (unsigned long long)vq_info.device_addr);
178 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tavail_idx: %u", vq->last_avail_idx);
179 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tused_idx: %u", vq->last_used_idx);
180 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tready: %u", vq_info.ready);
181 : 0 : vq->kickfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
182 [ # # ]: 0 : if (vq->kickfd < 0) {
183 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to init kickfd for VQ %u: %s",
184 : : index, strerror(errno));
185 : 0 : vq->kickfd = VIRTIO_INVALID_EVENTFD;
186 : 0 : return;
187 : : }
188 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tkick fd: %d", vq->kickfd);
189 : :
190 : 0 : vq->shadow_used_split = rte_malloc_socket(NULL,
191 : 0 : vq->size * sizeof(struct vring_used_elem),
192 : : RTE_CACHE_LINE_SIZE, 0);
193 : 0 : vq->batch_copy_elems = rte_malloc_socket(NULL,
194 : 0 : vq->size * sizeof(struct batch_copy_elem),
195 : : RTE_CACHE_LINE_SIZE, 0);
196 : :
197 : 0 : rte_rwlock_write_lock(&vq->access_lock);
198 : : vhost_user_iotlb_rd_lock(vq);
199 [ # # ]: 0 : if (vring_translate(dev, vq))
200 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to translate vring %d addresses",
201 : : index);
202 : :
203 [ # # ]: 0 : if (vhost_enable_guest_notification(dev, vq, 0))
204 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
205 : : "Failed to disable guest notifications on vring %d",
206 : : index);
207 : : vhost_user_iotlb_rd_unlock(vq);
208 : : rte_rwlock_write_unlock(&vq->access_lock);
209 : :
210 : 0 : vq_efd.index = index;
211 : 0 : vq_efd.fd = vq->kickfd;
212 : :
213 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_VQ_SETUP_KICKFD, &vq_efd);
214 [ # # ]: 0 : if (ret) {
215 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to setup kickfd for VQ %u: %s",
216 : : index, strerror(errno));
217 : 0 : close(vq->kickfd);
218 : 0 : vq->kickfd = VIRTIO_UNINITIALIZED_EVENTFD;
219 : 0 : return;
220 : : }
221 : :
222 [ # # ]: 0 : if (vq == dev->cvq) {
223 : 0 : ret = fdset_add(vduse.fdset, vq->kickfd, vduse_control_queue_event, NULL, dev);
224 [ # # ]: 0 : if (ret) {
225 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
226 : : "Failed to setup kickfd handler for VQ %u: %s",
227 : : index, strerror(errno));
228 : 0 : vq_efd.fd = VDUSE_EVENTFD_DEASSIGN;
229 : 0 : ioctl(dev->vduse_dev_fd, VDUSE_VQ_SETUP_KICKFD, &vq_efd);
230 : 0 : close(vq->kickfd);
231 : 0 : vq->kickfd = VIRTIO_UNINITIALIZED_EVENTFD;
232 : : }
233 : 0 : vhost_enable_guest_notification(dev, vq, 1);
234 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "Ctrl queue event handler installed");
235 : : }
236 : : }
237 : :
238 : : static void
239 : 0 : vduse_vring_cleanup(struct virtio_net *dev, unsigned int index)
240 : : {
241 : 0 : struct vhost_virtqueue *vq = dev->virtqueue[index];
242 : : struct vduse_vq_eventfd vq_efd;
243 : : int ret;
244 : :
245 [ # # # # ]: 0 : if (vq == dev->cvq && vq->kickfd >= 0)
246 : 0 : fdset_del(vduse.fdset, vq->kickfd);
247 : :
248 : 0 : vq_efd.index = index;
249 : 0 : vq_efd.fd = VDUSE_EVENTFD_DEASSIGN;
250 : :
251 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_VQ_SETUP_KICKFD, &vq_efd);
252 [ # # ]: 0 : if (ret)
253 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to cleanup kickfd for VQ %u: %s",
254 : : index, strerror(errno));
255 : :
256 : 0 : close(vq->kickfd);
257 : 0 : vq->kickfd = VIRTIO_UNINITIALIZED_EVENTFD;
258 : :
259 : 0 : rte_rwlock_write_lock(&vq->access_lock);
260 : 0 : vring_invalidate(dev, vq);
261 : : rte_rwlock_write_unlock(&vq->access_lock);
262 : :
263 : 0 : rte_free(vq->batch_copy_elems);
264 : 0 : vq->batch_copy_elems = NULL;
265 : :
266 : 0 : rte_free(vq->shadow_used_split);
267 : 0 : vq->shadow_used_split = NULL;
268 : :
269 : 0 : vq->enabled = false;
270 : 0 : vq->ready = false;
271 : 0 : vq->size = 0;
272 : 0 : vq->last_used_idx = 0;
273 : 0 : vq->last_avail_idx = 0;
274 : 0 : }
275 : :
276 : : /*
277 : : * Tests show that virtqueues get ready at the first retry at worst,
278 : : * but let's be on the safe side and allow more retries.
279 : : */
280 : : #define VDUSE_VQ_READY_POLL_MAX_RETRIES 100
281 : :
282 : : static int
283 : 0 : vduse_wait_for_virtqueues_ready(struct virtio_net *dev)
284 : : {
285 : : unsigned int i;
286 : : int ret;
287 : :
288 [ # # ]: 0 : for (i = 0; i < dev->nr_vring; i++) {
289 : : int retry_count = 0;
290 : :
291 [ # # ]: 0 : while (retry_count < VDUSE_VQ_READY_POLL_MAX_RETRIES) {
292 : 0 : struct vduse_vq_info vq_info = { 0 };
293 : :
294 : 0 : vq_info.index = i;
295 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_VQ_GET_INFO, &vq_info);
296 [ # # ]: 0 : if (ret) {
297 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
298 : : "Failed to get VQ %u info while polling ready state: %s",
299 : : i, strerror(errno));
300 : 0 : return -1;
301 : : }
302 : :
303 [ # # ]: 0 : if (vq_info.ready) {
304 : 0 : VHOST_CONFIG_LOG(dev->ifname, DEBUG,
305 : : "VQ %u is ready after %u retries", i, retry_count);
306 : 0 : break;
307 : : }
308 : :
309 : 0 : retry_count++;
310 : 0 : usleep(1000);
311 : : }
312 : :
313 [ # # ]: 0 : if (retry_count >= VDUSE_VQ_READY_POLL_MAX_RETRIES) {
314 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
315 : : "VQ %u ready state polling timeout after %u retries",
316 : : i, VDUSE_VQ_READY_POLL_MAX_RETRIES);
317 : 0 : return -1;
318 : : }
319 : : }
320 : :
321 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "All virtqueues are ready after polling");
322 : 0 : return 0;
323 : : }
324 : :
325 : : static void
326 : 0 : vduse_device_start(struct virtio_net *dev, bool reconnect)
327 : : {
328 : : unsigned int i, ret;
329 : :
330 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "Starting device...");
331 : :
332 : 0 : dev->notify_ops = vhost_driver_callback_get(dev->ifname);
333 [ # # ]: 0 : if (!dev->notify_ops) {
334 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
335 : : "Failed to get callback ops for driver");
336 : 0 : return;
337 : : }
338 : :
339 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_DEV_GET_FEATURES, &dev->features);
340 [ # # ]: 0 : if (ret) {
341 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to get features: %s",
342 : : strerror(errno));
343 : 0 : return;
344 : : }
345 : :
346 [ # # # # ]: 0 : if (reconnect && dev->features != dev->reconnect_log->features) {
347 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
348 : : "Mismatch between reconnect file features 0x%" PRIx64 " & device features 0x%" PRIx64,
349 : : dev->reconnect_log->features, dev->features);
350 : 0 : return;
351 : : }
352 : :
353 : 0 : dev->reconnect_log->features = dev->features;
354 : :
355 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "Negotiated Virtio features: 0x%" PRIx64,
356 : : dev->features);
357 : :
358 [ # # ]: 0 : if (dev->features &
359 : : ((1ULL << VIRTIO_NET_F_MRG_RXBUF) |
360 : : (1ULL << VIRTIO_F_VERSION_1) |
361 : : (1ULL << VIRTIO_F_RING_PACKED))) {
362 : 0 : dev->vhost_hlen = sizeof(struct virtio_net_hdr_mrg_rxbuf);
363 : : } else {
364 : 0 : dev->vhost_hlen = sizeof(struct virtio_net_hdr);
365 : : }
366 : :
367 [ # # ]: 0 : for (i = 0; i < dev->nr_vring; i++)
368 : 0 : vduse_vring_setup(dev, i, reconnect);
369 : :
370 : 0 : dev->flags |= VIRTIO_DEV_READY;
371 : :
372 [ # # # # ]: 0 : if (!dev->notify_ops->new_device ||
373 : 0 : dev->notify_ops->new_device(dev->vid) == 0)
374 : 0 : dev->flags |= VIRTIO_DEV_RUNNING;
375 : :
376 [ # # ]: 0 : for (i = 0; i < dev->nr_vring; i++) {
377 : 0 : struct vhost_virtqueue *vq = dev->virtqueue[i];
378 : :
379 [ # # ]: 0 : if (vq == dev->cvq)
380 : 0 : continue;
381 : :
382 [ # # ]: 0 : if (dev->notify_ops->vring_state_changed)
383 : 0 : dev->notify_ops->vring_state_changed(dev->vid, i, vq->enabled);
384 : : }
385 : : }
386 : :
387 : : static void
388 : 0 : vduse_device_stop(struct virtio_net *dev)
389 : : {
390 : : unsigned int i;
391 : :
392 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "Stopping device...");
393 : :
394 : 0 : vhost_destroy_device_notify(dev);
395 : :
396 : 0 : dev->flags &= ~VIRTIO_DEV_READY;
397 : :
398 [ # # ]: 0 : for (i = 0; i < dev->nr_vring; i++)
399 : 0 : vduse_vring_cleanup(dev, i);
400 : :
401 : 0 : vhost_user_iotlb_flush_all(dev);
402 : 0 : }
403 : :
404 : : static void
405 : 0 : vduse_events_handler(int fd, void *arg, int *close __rte_unused)
406 : : {
407 : : struct virtio_net *dev = arg;
408 : : struct vduse_dev_request req;
409 : : struct vduse_dev_response resp;
410 : : struct vhost_virtqueue *vq;
411 : 0 : uint8_t old_status = dev->status;
412 : : int ret;
413 : :
414 : : memset(&resp, 0, sizeof(resp));
415 : :
416 : 0 : ret = read(fd, &req, sizeof(req));
417 [ # # ]: 0 : if (ret < 0) {
418 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to read request: %s",
419 : : strerror(errno));
420 : 0 : return;
421 [ # # ]: 0 : } else if (ret < (int)sizeof(req)) {
422 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Incomplete to read request %d", ret);
423 : 0 : return;
424 : : }
425 : :
426 [ # # ]: 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "New request: %s (%u)",
427 : : vduse_req_id_to_str(req.type), req.type);
428 : :
429 [ # # # # ]: 0 : switch (req.type) {
430 : 0 : case VDUSE_GET_VQ_STATE:
431 : 0 : vq = dev->virtqueue[req.vq_state.index];
432 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tvq index: %u, avail_index: %u",
433 : : req.vq_state.index, vq->last_avail_idx);
434 : 0 : resp.vq_state.split.avail_index = vq->last_avail_idx;
435 : 0 : resp.result = VDUSE_REQ_RESULT_OK;
436 : 0 : break;
437 : 0 : case VDUSE_SET_STATUS:
438 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tnew status: 0x%08x",
439 : : req.s.status);
440 : 0 : old_status = dev->status;
441 : 0 : dev->status = req.s.status;
442 : 0 : dev->reconnect_log->status = dev->status;
443 : 0 : resp.result = VDUSE_REQ_RESULT_OK;
444 : 0 : break;
445 : 0 : case VDUSE_UPDATE_IOTLB:
446 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tIOVA range: %" PRIx64 " - %" PRIx64,
447 : : (uint64_t)req.iova.start, (uint64_t)req.iova.last);
448 : 0 : vhost_user_iotlb_cache_remove(dev, req.iova.start,
449 : 0 : req.iova.last - req.iova.start + 1);
450 : 0 : resp.result = VDUSE_REQ_RESULT_OK;
451 : 0 : break;
452 : 0 : default:
453 : 0 : resp.result = VDUSE_REQ_RESULT_FAILED;
454 : 0 : break;
455 : : }
456 : :
457 : 0 : resp.request_id = req.request_id;
458 : :
459 : 0 : ret = write(dev->vduse_dev_fd, &resp, sizeof(resp));
460 [ # # ]: 0 : if (ret != sizeof(resp)) {
461 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to write response %s",
462 : : strerror(errno));
463 : 0 : return;
464 : : }
465 : :
466 [ # # ]: 0 : if ((old_status ^ dev->status) & VIRTIO_DEVICE_STATUS_DRIVER_OK) {
467 [ # # ]: 0 : if (dev->status & VIRTIO_DEVICE_STATUS_DRIVER_OK) {
468 : : /* Poll virtqueues ready states before starting device */
469 : 0 : ret = vduse_wait_for_virtqueues_ready(dev);
470 [ # # ]: 0 : if (ret < 0) {
471 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
472 : : "Failed to wait for virtqueues ready, aborting device start");
473 : 0 : return;
474 : : }
475 : 0 : vduse_device_start(dev, false);
476 : : } else {
477 : 0 : vduse_device_stop(dev);
478 : : }
479 : : }
480 : :
481 [ # # ]: 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "Request %s (%u) handled successfully",
482 : : vduse_req_id_to_str(req.type), req.type);
483 : : }
484 : :
485 : : static char vduse_reconnect_dir[PATH_MAX];
486 : : static bool vduse_reconnect_path_set;
487 : :
488 : : static int
489 : 0 : vduse_reconnect_path_init(void)
490 : : {
491 : : const char *directory;
492 : : int ret;
493 : :
494 [ # # ]: 0 : if (vduse_reconnect_path_set == true)
495 : : return 0;
496 : :
497 : : /* from RuntimeDirectory= see systemd.exec */
498 : 0 : directory = getenv("RUNTIME_DIRECTORY");
499 [ # # ]: 0 : if (directory == NULL) {
500 : : /*
501 : : * Used standard convention defined in
502 : : * XDG Base Directory Specification and
503 : : * Filesystem Hierarchy Standard.
504 : : */
505 [ # # ]: 0 : if (getuid() == 0)
506 : : directory = "/var/run";
507 : : else
508 [ # # ]: 0 : directory = getenv("XDG_RUNTIME_DIR") ? : "/tmp";
509 : : }
510 : :
511 : : ret = snprintf(vduse_reconnect_dir, sizeof(vduse_reconnect_dir), "%s/vduse",
512 : : directory);
513 [ # # ]: 0 : if (ret < 0 || ret == sizeof(vduse_reconnect_dir)) {
514 : 0 : VHOST_CONFIG_LOG("vduse", ERR, "Error creating VDUSE reconnect path name");
515 : 0 : return -1;
516 : : }
517 : :
518 : 0 : ret = mkdir(vduse_reconnect_dir, 0700);
519 [ # # # # ]: 0 : if (ret < 0 && errno != EEXIST) {
520 : 0 : VHOST_CONFIG_LOG("vduse", ERR, "Error creating '%s': %s",
521 : : vduse_reconnect_dir, strerror(errno));
522 : 0 : return -1;
523 : : }
524 : :
525 : 0 : VHOST_CONFIG_LOG("vduse", INFO, "Created VDUSE reconnect directory in %s",
526 : : vduse_reconnect_dir);
527 : :
528 : 0 : vduse_reconnect_path_set = true;
529 : :
530 : 0 : return 0;
531 : : }
532 : :
533 : : static int
534 : 0 : vduse_reconnect_log_map(struct virtio_net *dev, bool create)
535 : : {
536 : : char reco_file[PATH_MAX];
537 : : int fd, ret;
538 : 0 : const char *name = dev->ifname + strlen("/dev/vduse/");
539 : :
540 [ # # ]: 0 : if (vduse_reconnect_path_init() < 0) {
541 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to initialize reconnect path");
542 : 0 : return -1;
543 : : }
544 : :
545 : : ret = snprintf(reco_file, sizeof(reco_file), "%s/%s", vduse_reconnect_dir, name);
546 [ # # ]: 0 : if (ret < 0 || ret == sizeof(reco_file)) {
547 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to create vduse reconnect path name");
548 : 0 : return -1;
549 : : }
550 : :
551 [ # # ]: 0 : if (create) {
552 : : fd = open(reco_file, O_CREAT | O_EXCL | O_RDWR, 0600);
553 [ # # ]: 0 : if (fd < 0) {
554 [ # # ]: 0 : if (errno == EEXIST) {
555 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Reconnect file %s exists but not the device",
556 : : reco_file);
557 : : } else {
558 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to open reconnect file %s (%s)",
559 : : reco_file, strerror(errno));
560 : : }
561 : 0 : return -1;
562 : : }
563 : :
564 : 0 : ret = ftruncate(fd, sizeof(*dev->reconnect_log));
565 [ # # ]: 0 : if (ret < 0) {
566 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to truncate reconnect file %s (%s)",
567 : : reco_file, strerror(errno));
568 : 0 : goto out_close;
569 : : }
570 : : } else {
571 : : fd = open(reco_file, O_RDWR, 0600);
572 [ # # ]: 0 : if (fd < 0) {
573 [ # # ]: 0 : if (errno == ENOENT)
574 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Missing reconnect file (%s)", reco_file);
575 : : else
576 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to open reconnect file %s (%s)",
577 : : reco_file, strerror(errno));
578 : 0 : return -1;
579 : : }
580 : : }
581 : :
582 : 0 : dev->reconnect_log = mmap(NULL, sizeof(*dev->reconnect_log), PROT_READ | PROT_WRITE,
583 : : MAP_SHARED, fd, 0);
584 [ # # ]: 0 : if (dev->reconnect_log == MAP_FAILED) {
585 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to mmap reconnect file %s (%s)",
586 : : reco_file, strerror(errno));
587 : : ret = -1;
588 : 0 : goto out_close;
589 : : }
590 : : ret = 0;
591 : :
592 : 0 : out_close:
593 : 0 : close(fd);
594 : :
595 : 0 : return ret;
596 : : }
597 : :
598 : : static int
599 : 0 : vduse_reconnect_log_check(struct virtio_net *dev, uint64_t features, uint32_t total_queues)
600 : : {
601 [ # # ]: 0 : if (dev->reconnect_log->version != VHOST_RECONNECT_VERSION) {
602 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
603 : : "Version mismatch between backend (0x%x) & reconnection file (0x%x)",
604 : : VHOST_RECONNECT_VERSION, dev->reconnect_log->version);
605 : 0 : return -1;
606 : : }
607 : :
608 [ # # ]: 0 : if ((dev->reconnect_log->features & features) != dev->reconnect_log->features) {
609 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
610 : : "Features mismatch between backend (0x%" PRIx64 ") & reconnection file (0x%" PRIx64 ")",
611 : : features, dev->reconnect_log->features);
612 : 0 : return -1;
613 : : }
614 : :
615 [ # # ]: 0 : if (dev->reconnect_log->nr_vrings != total_queues) {
616 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
617 : : "Queues number mismatch between backend (%u) and reconnection file (%u)",
618 : : total_queues, dev->reconnect_log->nr_vrings);
619 : 0 : return -1;
620 : : }
621 : :
622 : : return 0;
623 : : }
624 : :
625 : : static void
626 : 0 : vduse_reconnect_handler(int fd __rte_unused, void *arg, int *close)
627 : : {
628 : : struct virtio_net *dev = arg;
629 : :
630 : 0 : vduse_device_start(dev, true);
631 : :
632 : 0 : *close = 1;
633 : 0 : }
634 : :
635 : : static int
636 : 0 : vduse_reconnect_start_device(struct virtio_net *dev)
637 : : {
638 : : int fd, ret;
639 : :
640 : : /*
641 : : * Make vduse_device_start() being executed in the same
642 : : * context for both reconnection and fresh startup.
643 : : */
644 : 0 : fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
645 [ # # ]: 0 : if (fd < 0) {
646 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to create reconnect efd: %s",
647 : : strerror(errno));
648 : : ret = -1;
649 : 0 : goto out_err;
650 : : }
651 : :
652 : 0 : ret = fdset_add(vduse.fdset, fd, vduse_reconnect_handler, NULL, dev);
653 [ # # ]: 0 : if (ret) {
654 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to add reconnect efd %d to vduse fdset",
655 : : fd);
656 : 0 : goto out_err_close;
657 : : }
658 : :
659 : 0 : ret = eventfd_write(fd, (eventfd_t)1);
660 [ # # ]: 0 : if (ret < 0) {
661 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to write to reconnect eventfd");
662 : 0 : goto out_err_fdset;
663 : : }
664 : :
665 : : return 0;
666 : :
667 : : out_err_fdset:
668 : 0 : fdset_del(vduse.fdset, fd);
669 : 0 : out_err_close:
670 : 0 : close(fd);
671 : : out_err:
672 : : return ret;
673 : : }
674 : :
675 : : int
676 : 0 : vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool linearbuf)
677 : : {
678 : : int control_fd, dev_fd, vid, ret;
679 : : uint32_t i, max_queue_pairs, total_queues;
680 : : struct virtio_net *dev;
681 : 0 : struct virtio_net_config vnet_config = {{ 0 }};
682 : 0 : uint64_t ver = VHOST_VDUSE_API_VERSION;
683 : : uint64_t features;
684 : 0 : const char *name = path + strlen("/dev/vduse/");
685 : : bool reconnect = false;
686 : :
687 [ # # ]: 0 : if (vduse.fdset == NULL) {
688 : 0 : vduse.fdset = fdset_init("vduse-evt");
689 [ # # ]: 0 : if (vduse.fdset == NULL) {
690 : 0 : VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
691 : 0 : return -1;
692 : : }
693 : : }
694 : :
695 : : control_fd = open(VDUSE_CTRL_PATH, O_RDWR);
696 [ # # ]: 0 : if (control_fd < 0) {
697 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to open %s: %s",
698 : : VDUSE_CTRL_PATH, strerror(errno));
699 : 0 : return -1;
700 : : }
701 : :
702 [ # # ]: 0 : if (ioctl(control_fd, VDUSE_SET_API_VERSION, &ver)) {
703 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to set API version: %" PRIu64 ": %s",
704 : : ver, strerror(errno));
705 : : ret = -1;
706 : 0 : goto out_ctrl_close;
707 : : }
708 : :
709 : 0 : ret = rte_vhost_driver_get_features(path, &features);
710 [ # # ]: 0 : if (ret < 0) {
711 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to get backend features");
712 : 0 : goto out_ctrl_close;
713 : : }
714 : :
715 : 0 : ret = rte_vhost_driver_get_queue_num(path, &max_queue_pairs);
716 [ # # ]: 0 : if (ret < 0) {
717 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to get max queue pairs");
718 : 0 : goto out_ctrl_close;
719 : : }
720 : :
721 : 0 : VHOST_CONFIG_LOG(path, INFO, "VDUSE max queue pairs: %u", max_queue_pairs);
722 : 0 : total_queues = max_queue_pairs * 2;
723 : :
724 [ # # ]: 0 : if (max_queue_pairs == 1)
725 : 0 : features &= ~(RTE_BIT64(VIRTIO_NET_F_CTRL_VQ) | RTE_BIT64(VIRTIO_NET_F_MQ));
726 : : else
727 : 0 : total_queues += 1; /* Includes ctrl queue */
728 : :
729 : : dev_fd = open(path, O_RDWR);
730 [ # # ]: 0 : if (dev_fd >= 0) {
731 : 0 : VHOST_CONFIG_LOG(name, INFO, "Device already exists, reconnecting...");
732 : : reconnect = true;
733 [ # # ]: 0 : } else if (errno == ENOENT) {
734 : : struct vduse_dev_config *dev_config;
735 : :
736 : 0 : dev_config = malloc(offsetof(struct vduse_dev_config, config) +
737 : : sizeof(vnet_config));
738 [ # # ]: 0 : if (!dev_config) {
739 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to allocate VDUSE config");
740 : : ret = -1;
741 : 0 : goto out_ctrl_close;
742 : : }
743 : :
744 : 0 : vnet_config.max_virtqueue_pairs = max_queue_pairs;
745 : : memset(dev_config, 0, sizeof(struct vduse_dev_config));
746 : :
747 : 0 : rte_strscpy(dev_config->name, name, VDUSE_NAME_MAX - 1);
748 : 0 : dev_config->device_id = VIRTIO_ID_NET;
749 : 0 : dev_config->vendor_id = 0;
750 : 0 : dev_config->features = features;
751 : 0 : dev_config->vq_num = total_queues;
752 : 0 : dev_config->vq_align = rte_mem_page_size();
753 : 0 : dev_config->config_size = sizeof(struct virtio_net_config);
754 : 0 : memcpy(dev_config->config, &vnet_config, sizeof(vnet_config));
755 : :
756 : 0 : ret = ioctl(control_fd, VDUSE_CREATE_DEV, dev_config);
757 : 0 : free(dev_config);
758 : : dev_config = NULL;
759 [ # # ]: 0 : if (ret < 0) {
760 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to create VDUSE device: %s",
761 : : strerror(errno));
762 : 0 : goto out_ctrl_close;
763 : : }
764 : :
765 : : dev_fd = open(path, O_RDWR);
766 [ # # ]: 0 : if (dev_fd < 0) {
767 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to open newly created device %s: %s",
768 : : path, strerror(errno));
769 : : ret = -1;
770 : 0 : goto out_ctrl_close;
771 : : }
772 : : } else {
773 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to open device %s: %s",
774 : : path, strerror(errno));
775 : : ret = -1;
776 : 0 : goto out_ctrl_close;
777 : : }
778 : :
779 : 0 : ret = fcntl(dev_fd, F_SETFL, O_NONBLOCK);
780 [ # # ]: 0 : if (ret < 0) {
781 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to set chardev as non-blocking: %s",
782 : : strerror(errno));
783 : 0 : goto out_dev_close;
784 : : }
785 : :
786 : 0 : vid = vhost_new_device(&vduse_backend_ops);
787 [ # # ]: 0 : if (vid < 0) {
788 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to create new Vhost device");
789 : : ret = -1;
790 : 0 : goto out_dev_close;
791 : : }
792 : :
793 : : dev = get_device(vid);
794 [ # # ]: 0 : if (!dev) {
795 : : ret = -1;
796 : 0 : goto out_dev_destroy;
797 : : }
798 : :
799 : 0 : strncpy(dev->ifname, path, IF_NAME_SZ - 1);
800 : 0 : dev->vduse_ctrl_fd = control_fd;
801 : 0 : dev->vduse_dev_fd = dev_fd;
802 : :
803 : 0 : ret = vduse_reconnect_log_map(dev, !reconnect);
804 [ # # ]: 0 : if (ret < 0)
805 : 0 : goto out_dev_destroy;
806 : :
807 [ # # ]: 0 : if (reconnect) {
808 : 0 : ret = vduse_reconnect_log_check(dev, features, total_queues);
809 [ # # ]: 0 : if (ret < 0)
810 : 0 : goto out_log_unmap;
811 : :
812 : 0 : dev->status = dev->reconnect_log->status;
813 : : } else {
814 : 0 : dev->reconnect_log->version = VHOST_RECONNECT_VERSION;
815 : 0 : dev->reconnect_log->nr_vrings = total_queues;
816 : 0 : memcpy(&dev->reconnect_log->config, &vnet_config, sizeof(vnet_config));
817 : : }
818 : :
819 : 0 : vhost_setup_virtio_net(dev->vid, true, compliant_ol_flags, true, true);
820 : :
821 [ # # ]: 0 : if (extbuf)
822 : 0 : vhost_enable_extbuf(dev->vid);
823 : :
824 [ # # ]: 0 : if (linearbuf)
825 : 0 : vhost_enable_linearbuf(dev->vid);
826 : :
827 [ # # ]: 0 : for (i = 0; i < total_queues; i++) {
828 : 0 : struct vduse_vq_config vq_cfg = { 0 };
829 : : struct vhost_virtqueue *vq;
830 : :
831 : 0 : ret = alloc_vring_queue(dev, i);
832 [ # # ]: 0 : if (ret) {
833 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to alloc vring %d metadata", i);
834 : 0 : goto out_log_unmap;
835 : : }
836 : :
837 : 0 : vq = dev->virtqueue[i];
838 : 0 : vq->reconnect_log = &dev->reconnect_log->vring[i];
839 : :
840 [ # # ]: 0 : if (reconnect)
841 : 0 : continue;
842 : :
843 : 0 : vq_cfg.index = i;
844 : 0 : vq_cfg.max_size = 1024;
845 : :
846 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_VQ_SETUP, &vq_cfg);
847 [ # # ]: 0 : if (ret) {
848 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to set-up VQ %d", i);
849 : 0 : goto out_log_unmap;
850 : : }
851 : : }
852 : :
853 : 0 : dev->cvq = dev->virtqueue[max_queue_pairs * 2];
854 : :
855 : 0 : ret = fdset_add(vduse.fdset, dev->vduse_dev_fd, vduse_events_handler, NULL, dev);
856 [ # # ]: 0 : if (ret) {
857 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to add fd %d to vduse fdset",
858 : : dev->vduse_dev_fd);
859 : 0 : goto out_log_unmap;
860 : : }
861 : :
862 [ # # # # ]: 0 : if (reconnect && dev->status & VIRTIO_DEVICE_STATUS_DRIVER_OK) {
863 : 0 : ret = vduse_reconnect_start_device(dev);
864 [ # # ]: 0 : if (ret)
865 : 0 : goto out_log_unmap;
866 : : }
867 : :
868 : : return 0;
869 : :
870 : 0 : out_log_unmap:
871 : 0 : munmap(dev->reconnect_log, sizeof(*dev->reconnect_log));
872 : 0 : out_dev_destroy:
873 : 0 : vhost_destroy_device(vid);
874 : 0 : out_dev_close:
875 : : if (dev_fd >= 0)
876 : 0 : close(dev_fd);
877 : 0 : ioctl(control_fd, VDUSE_DESTROY_DEV, name);
878 : 0 : out_ctrl_close:
879 : 0 : close(control_fd);
880 : :
881 : 0 : return ret;
882 : : }
883 : :
884 : : int
885 : 0 : vduse_device_destroy(const char *path)
886 : : {
887 : 0 : const char *name = path + strlen("/dev/vduse/");
888 : : struct virtio_net *dev;
889 : : int vid, ret;
890 : :
891 [ # # ]: 0 : for (vid = 0; vid < RTE_MAX_VHOST_DEVICE; vid++) {
892 : 0 : dev = vhost_devices[vid];
893 : :
894 [ # # ]: 0 : if (dev == NULL)
895 : 0 : continue;
896 : :
897 [ # # ]: 0 : if (!strcmp(path, dev->ifname))
898 : : break;
899 : : }
900 : :
901 [ # # ]: 0 : if (vid == RTE_MAX_VHOST_DEVICE)
902 : : return -1;
903 : :
904 [ # # ]: 0 : if (dev->reconnect_log)
905 : 0 : munmap(dev->reconnect_log, sizeof(*dev->reconnect_log));
906 : :
907 : 0 : vduse_device_stop(dev);
908 : :
909 : 0 : fdset_del(vduse.fdset, dev->vduse_dev_fd);
910 : :
911 [ # # ]: 0 : if (dev->vduse_dev_fd >= 0) {
912 : 0 : close(dev->vduse_dev_fd);
913 : 0 : dev->vduse_dev_fd = -1;
914 : : }
915 : :
916 [ # # ]: 0 : if (dev->vduse_ctrl_fd >= 0) {
917 : : char reconnect_file[PATH_MAX];
918 : :
919 : 0 : ret = ioctl(dev->vduse_ctrl_fd, VDUSE_DESTROY_DEV, name);
920 [ # # ]: 0 : if (ret) {
921 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to destroy VDUSE device: %s",
922 : : strerror(errno));
923 : : } else {
924 : : /*
925 : : * VDUSE device was no more attached to the vDPA bus,
926 : : * so we can remove the reconnect file.
927 : : */
928 : : ret = snprintf(reconnect_file, sizeof(reconnect_file), "%s/%s",
929 : : vduse_reconnect_dir, name);
930 [ # # ]: 0 : if (ret < 0 || ret == sizeof(reconnect_file))
931 : 0 : VHOST_CONFIG_LOG(name, ERR,
932 : : "Failed to create vduse reconnect path name");
933 : : else
934 : 0 : unlink(reconnect_file);
935 : : }
936 : :
937 : 0 : close(dev->vduse_ctrl_fd);
938 : 0 : dev->vduse_ctrl_fd = -1;
939 : : }
940 : :
941 : 0 : vhost_destroy_device(vid);
942 : :
943 : 0 : return 0;
944 : : }
|