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