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