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) {
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 : 0 : struct vduse_vq_info vq_info = { 0 };
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 : : /*
276 : : * Tests show that virtqueues get ready at the first retry at worst,
277 : : * but let's be on the safe side and allow more retries.
278 : : */
279 : : #define VDUSE_VQ_READY_POLL_MAX_RETRIES 100
280 : :
281 : : static int
282 : 0 : vduse_wait_for_virtqueues_ready(struct virtio_net *dev)
283 : : {
284 : : unsigned int i;
285 : : int ret;
286 : :
287 [ # # ]: 0 : for (i = 0; i < dev->nr_vring; i++) {
288 : : int retry_count = 0;
289 : :
290 [ # # ]: 0 : while (retry_count < VDUSE_VQ_READY_POLL_MAX_RETRIES) {
291 : 0 : struct vduse_vq_info vq_info = { 0 };
292 : :
293 : 0 : vq_info.index = i;
294 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_VQ_GET_INFO, &vq_info);
295 [ # # ]: 0 : if (ret) {
296 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
297 : : "Failed to get VQ %u info while polling ready state: %s",
298 : : i, strerror(errno));
299 : 0 : return -1;
300 : : }
301 : :
302 [ # # ]: 0 : if (vq_info.ready) {
303 : 0 : VHOST_CONFIG_LOG(dev->ifname, DEBUG,
304 : : "VQ %u is ready after %u retries", i, retry_count);
305 : 0 : break;
306 : : }
307 : :
308 : 0 : retry_count++;
309 : 0 : usleep(1000);
310 : : }
311 : :
312 [ # # ]: 0 : if (retry_count >= VDUSE_VQ_READY_POLL_MAX_RETRIES) {
313 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
314 : : "VQ %u ready state polling timeout after %u retries",
315 : : i, VDUSE_VQ_READY_POLL_MAX_RETRIES);
316 : 0 : return -1;
317 : : }
318 : : }
319 : :
320 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "All virtqueues are ready after polling");
321 : 0 : return 0;
322 : : }
323 : :
324 : : static void
325 : 0 : vduse_device_start(struct virtio_net *dev, bool reconnect)
326 : : {
327 : : unsigned int i, ret;
328 : :
329 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "Starting device...");
330 : :
331 : 0 : dev->notify_ops = vhost_driver_callback_get(dev->ifname);
332 [ # # ]: 0 : if (!dev->notify_ops) {
333 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
334 : : "Failed to get callback ops for driver");
335 : 0 : return;
336 : : }
337 : :
338 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_DEV_GET_FEATURES, &dev->features);
339 [ # # ]: 0 : if (ret) {
340 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to get features: %s",
341 : : strerror(errno));
342 : 0 : return;
343 : : }
344 : :
345 [ # # # # ]: 0 : if (reconnect && dev->features != dev->reconnect_log->features) {
346 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
347 : : "Mismatch between reconnect file features 0x%" PRIx64 " & device features 0x%" PRIx64,
348 : : dev->reconnect_log->features, dev->features);
349 : 0 : return;
350 : : }
351 : :
352 : 0 : dev->reconnect_log->features = dev->features;
353 : :
354 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "Negotiated Virtio features: 0x%" PRIx64,
355 : : dev->features);
356 : :
357 [ # # ]: 0 : if (dev->features &
358 : : ((1ULL << VIRTIO_NET_F_MRG_RXBUF) |
359 : : (1ULL << VIRTIO_F_VERSION_1) |
360 : : (1ULL << VIRTIO_F_RING_PACKED))) {
361 : 0 : dev->vhost_hlen = sizeof(struct virtio_net_hdr_mrg_rxbuf);
362 : : } else {
363 : 0 : dev->vhost_hlen = sizeof(struct virtio_net_hdr);
364 : : }
365 : :
366 [ # # ]: 0 : for (i = 0; i < dev->nr_vring; i++)
367 : 0 : vduse_vring_setup(dev, i, reconnect);
368 : :
369 : 0 : dev->flags |= VIRTIO_DEV_READY;
370 : :
371 [ # # # # ]: 0 : if (!dev->notify_ops->new_device ||
372 : 0 : dev->notify_ops->new_device(dev->vid) == 0)
373 : 0 : dev->flags |= VIRTIO_DEV_RUNNING;
374 : :
375 [ # # ]: 0 : for (i = 0; i < dev->nr_vring; i++) {
376 : 0 : struct vhost_virtqueue *vq = dev->virtqueue[i];
377 : :
378 [ # # ]: 0 : if (vq == dev->cvq)
379 : 0 : continue;
380 : :
381 [ # # ]: 0 : if (dev->notify_ops->vring_state_changed)
382 : 0 : dev->notify_ops->vring_state_changed(dev->vid, i, vq->enabled);
383 : : }
384 : : }
385 : :
386 : : static void
387 : 0 : vduse_device_stop(struct virtio_net *dev)
388 : : {
389 : : unsigned int i;
390 : :
391 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "Stopping device...");
392 : :
393 : 0 : vhost_destroy_device_notify(dev);
394 : :
395 : 0 : dev->flags &= ~VIRTIO_DEV_READY;
396 : :
397 [ # # ]: 0 : for (i = 0; i < dev->nr_vring; i++)
398 : 0 : vduse_vring_cleanup(dev, i);
399 : :
400 : 0 : vhost_user_iotlb_flush_all(dev);
401 : 0 : }
402 : :
403 : : static void
404 : 0 : vduse_events_handler(int fd, void *arg, int *close __rte_unused)
405 : : {
406 : : struct virtio_net *dev = arg;
407 : : struct vduse_dev_request req;
408 : : struct vduse_dev_response resp;
409 : : struct vhost_virtqueue *vq;
410 : 0 : uint8_t old_status = dev->status;
411 : : int ret;
412 : :
413 : : memset(&resp, 0, sizeof(resp));
414 : :
415 : 0 : ret = read(fd, &req, sizeof(req));
416 [ # # ]: 0 : if (ret < 0) {
417 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to read request: %s",
418 : : strerror(errno));
419 : 0 : return;
420 [ # # ]: 0 : } else if (ret < (int)sizeof(req)) {
421 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Incomplete to read request %d", ret);
422 : 0 : return;
423 : : }
424 : :
425 [ # # ]: 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "New request: %s (%u)",
426 : : vduse_req_id_to_str(req.type), req.type);
427 : :
428 [ # # # # ]: 0 : switch (req.type) {
429 : 0 : case VDUSE_GET_VQ_STATE:
430 : 0 : vq = dev->virtqueue[req.vq_state.index];
431 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tvq index: %u, avail_index: %u",
432 : : req.vq_state.index, vq->last_avail_idx);
433 : 0 : resp.vq_state.split.avail_index = vq->last_avail_idx;
434 : 0 : resp.result = VDUSE_REQ_RESULT_OK;
435 : 0 : break;
436 : 0 : case VDUSE_SET_STATUS:
437 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tnew status: 0x%08x",
438 : : req.s.status);
439 : 0 : old_status = dev->status;
440 : 0 : dev->status = req.s.status;
441 : 0 : dev->reconnect_log->status = dev->status;
442 : 0 : resp.result = VDUSE_REQ_RESULT_OK;
443 : 0 : break;
444 : 0 : case VDUSE_UPDATE_IOTLB:
445 : 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "\tIOVA range: %" PRIx64 " - %" PRIx64,
446 : : (uint64_t)req.iova.start, (uint64_t)req.iova.last);
447 : 0 : vhost_user_iotlb_cache_remove(dev, req.iova.start,
448 : 0 : req.iova.last - req.iova.start + 1);
449 : 0 : resp.result = VDUSE_REQ_RESULT_OK;
450 : 0 : break;
451 : 0 : default:
452 : 0 : resp.result = VDUSE_REQ_RESULT_FAILED;
453 : 0 : break;
454 : : }
455 : :
456 : 0 : resp.request_id = req.request_id;
457 : :
458 : 0 : ret = write(dev->vduse_dev_fd, &resp, sizeof(resp));
459 [ # # ]: 0 : if (ret != sizeof(resp)) {
460 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to write response %s",
461 : : strerror(errno));
462 : 0 : return;
463 : : }
464 : :
465 [ # # ]: 0 : if ((old_status ^ dev->status) & VIRTIO_DEVICE_STATUS_DRIVER_OK) {
466 [ # # ]: 0 : if (dev->status & VIRTIO_DEVICE_STATUS_DRIVER_OK) {
467 : : /* Poll virtqueues ready states before starting device */
468 : 0 : ret = vduse_wait_for_virtqueues_ready(dev);
469 [ # # ]: 0 : if (ret < 0) {
470 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
471 : : "Failed to wait for virtqueues ready, aborting device start");
472 : 0 : return;
473 : : }
474 : 0 : vduse_device_start(dev, false);
475 : : } else {
476 : 0 : vduse_device_stop(dev);
477 : : }
478 : : }
479 : :
480 [ # # ]: 0 : VHOST_CONFIG_LOG(dev->ifname, INFO, "Request %s (%u) handled successfully",
481 : : vduse_req_id_to_str(req.type), req.type);
482 : : }
483 : :
484 : : static char vduse_reconnect_dir[PATH_MAX];
485 : : static bool vduse_reconnect_path_set;
486 : :
487 : : static int
488 : 0 : vduse_reconnect_path_init(void)
489 : : {
490 : : const char *directory;
491 : : int ret;
492 : :
493 [ # # ]: 0 : if (vduse_reconnect_path_set == true)
494 : : return 0;
495 : :
496 : : /* from RuntimeDirectory= see systemd.exec */
497 : 0 : directory = getenv("RUNTIME_DIRECTORY");
498 [ # # ]: 0 : if (directory == NULL) {
499 : : /*
500 : : * Used standard convention defined in
501 : : * XDG Base Directory Specification and
502 : : * Filesystem Hierarchy Standard.
503 : : */
504 [ # # ]: 0 : if (getuid() == 0)
505 : : directory = "/var/run";
506 : : else
507 [ # # ]: 0 : directory = getenv("XDG_RUNTIME_DIR") ? : "/tmp";
508 : : }
509 : :
510 : : ret = snprintf(vduse_reconnect_dir, sizeof(vduse_reconnect_dir), "%s/vduse",
511 : : directory);
512 [ # # ]: 0 : if (ret < 0 || ret == sizeof(vduse_reconnect_dir)) {
513 : 0 : VHOST_CONFIG_LOG("vduse", ERR, "Error creating VDUSE reconnect path name");
514 : 0 : return -1;
515 : : }
516 : :
517 : 0 : ret = mkdir(vduse_reconnect_dir, 0700);
518 [ # # # # ]: 0 : if (ret < 0 && errno != EEXIST) {
519 : 0 : VHOST_CONFIG_LOG("vduse", ERR, "Error creating '%s': %s",
520 : : vduse_reconnect_dir, strerror(errno));
521 : 0 : return -1;
522 : : }
523 : :
524 : 0 : VHOST_CONFIG_LOG("vduse", INFO, "Created VDUSE reconnect directory in %s",
525 : : vduse_reconnect_dir);
526 : :
527 : 0 : vduse_reconnect_path_set = true;
528 : :
529 : 0 : return 0;
530 : : }
531 : :
532 : : static int
533 : 0 : vduse_reconnect_log_map(struct virtio_net *dev, bool create)
534 : : {
535 : : char reco_file[PATH_MAX];
536 : : int fd, ret;
537 : 0 : const char *name = dev->ifname + strlen("/dev/vduse/");
538 : :
539 [ # # ]: 0 : if (vduse_reconnect_path_init() < 0) {
540 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to initialize reconnect path");
541 : 0 : return -1;
542 : : }
543 : :
544 : : ret = snprintf(reco_file, sizeof(reco_file), "%s/%s", vduse_reconnect_dir, name);
545 [ # # ]: 0 : if (ret < 0 || ret == sizeof(reco_file)) {
546 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to create vduse reconnect path name");
547 : 0 : return -1;
548 : : }
549 : :
550 [ # # ]: 0 : if (create) {
551 : : fd = open(reco_file, O_CREAT | O_EXCL | O_RDWR, 0600);
552 [ # # ]: 0 : if (fd < 0) {
553 [ # # ]: 0 : if (errno == EEXIST) {
554 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Reconnect file %s exists but not the device",
555 : : reco_file);
556 : : } else {
557 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to open reconnect file %s (%s)",
558 : : reco_file, strerror(errno));
559 : : }
560 : 0 : return -1;
561 : : }
562 : :
563 : 0 : ret = ftruncate(fd, sizeof(*dev->reconnect_log));
564 [ # # ]: 0 : if (ret < 0) {
565 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to truncate reconnect file %s (%s)",
566 : : reco_file, strerror(errno));
567 : 0 : goto out_close;
568 : : }
569 : : } else {
570 : : fd = open(reco_file, O_RDWR, 0600);
571 [ # # ]: 0 : if (fd < 0) {
572 [ # # ]: 0 : if (errno == ENOENT)
573 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Missing reconnect file (%s)", reco_file);
574 : : else
575 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to open reconnect file %s (%s)",
576 : : reco_file, strerror(errno));
577 : 0 : return -1;
578 : : }
579 : : }
580 : :
581 : 0 : dev->reconnect_log = mmap(NULL, sizeof(*dev->reconnect_log), PROT_READ | PROT_WRITE,
582 : : MAP_SHARED, fd, 0);
583 [ # # ]: 0 : if (dev->reconnect_log == MAP_FAILED) {
584 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to mmap reconnect file %s (%s)",
585 : : reco_file, strerror(errno));
586 : : ret = -1;
587 : 0 : goto out_close;
588 : : }
589 : : ret = 0;
590 : :
591 : 0 : out_close:
592 : 0 : close(fd);
593 : :
594 : 0 : return ret;
595 : : }
596 : :
597 : : static int
598 : 0 : vduse_reconnect_log_check(struct virtio_net *dev, uint64_t features, uint32_t total_queues)
599 : : {
600 [ # # ]: 0 : if (dev->reconnect_log->version != VHOST_RECONNECT_VERSION) {
601 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
602 : : "Version mismatch between backend (0x%x) & reconnection file (0x%x)",
603 : : VHOST_RECONNECT_VERSION, dev->reconnect_log->version);
604 : 0 : return -1;
605 : : }
606 : :
607 [ # # ]: 0 : if ((dev->reconnect_log->features & features) != dev->reconnect_log->features) {
608 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
609 : : "Features mismatch between backend (0x%" PRIx64 ") & reconnection file (0x%" PRIx64 ")",
610 : : features, dev->reconnect_log->features);
611 : 0 : return -1;
612 : : }
613 : :
614 [ # # ]: 0 : if (dev->reconnect_log->nr_vrings != total_queues) {
615 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR,
616 : : "Queues number mismatch between backend (%u) and reconnection file (%u)",
617 : : total_queues, dev->reconnect_log->nr_vrings);
618 : 0 : return -1;
619 : : }
620 : :
621 : : return 0;
622 : : }
623 : :
624 : : static void
625 : 0 : vduse_reconnect_handler(int fd __rte_unused, void *arg, int *close)
626 : : {
627 : : struct virtio_net *dev = arg;
628 : :
629 : 0 : vduse_device_start(dev, true);
630 : :
631 : 0 : *close = 1;
632 : 0 : }
633 : :
634 : : static int
635 : 0 : vduse_reconnect_start_device(struct virtio_net *dev)
636 : : {
637 : : int fd, ret;
638 : :
639 : : /*
640 : : * Make vduse_device_start() being executed in the same
641 : : * context for both reconnection and fresh startup.
642 : : */
643 : 0 : fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
644 [ # # ]: 0 : if (fd < 0) {
645 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to create reconnect efd: %s",
646 : : strerror(errno));
647 : : ret = -1;
648 : 0 : goto out_err;
649 : : }
650 : :
651 : 0 : ret = fdset_add(vduse.fdset, fd, vduse_reconnect_handler, NULL, dev);
652 [ # # ]: 0 : if (ret) {
653 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to add reconnect efd %d to vduse fdset",
654 : : fd);
655 : 0 : goto out_err_close;
656 : : }
657 : :
658 : 0 : ret = eventfd_write(fd, (eventfd_t)1);
659 [ # # ]: 0 : if (ret < 0) {
660 : 0 : VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to write to reconnect eventfd");
661 : 0 : goto out_err_fdset;
662 : : }
663 : :
664 : : return 0;
665 : :
666 : : out_err_fdset:
667 : 0 : fdset_del(vduse.fdset, fd);
668 : 0 : out_err_close:
669 : 0 : close(fd);
670 : : out_err:
671 : : return ret;
672 : : }
673 : :
674 : : int
675 : 0 : vduse_device_create(const char *path, bool compliant_ol_flags)
676 : : {
677 : : int control_fd, dev_fd, vid, ret;
678 : : uint32_t i, max_queue_pairs, total_queues;
679 : : struct virtio_net *dev;
680 : 0 : struct virtio_net_config vnet_config = {{ 0 }};
681 : 0 : uint64_t ver = VHOST_VDUSE_API_VERSION;
682 : : uint64_t features;
683 : 0 : const char *name = path + strlen("/dev/vduse/");
684 : : bool reconnect = false;
685 : :
686 [ # # ]: 0 : if (vduse.fdset == NULL) {
687 : 0 : vduse.fdset = fdset_init("vduse-evt");
688 [ # # ]: 0 : if (vduse.fdset == NULL) {
689 : 0 : VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
690 : 0 : return -1;
691 : : }
692 : : }
693 : :
694 : : control_fd = open(VDUSE_CTRL_PATH, O_RDWR);
695 [ # # ]: 0 : if (control_fd < 0) {
696 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to open %s: %s",
697 : : VDUSE_CTRL_PATH, strerror(errno));
698 : 0 : return -1;
699 : : }
700 : :
701 [ # # ]: 0 : if (ioctl(control_fd, VDUSE_SET_API_VERSION, &ver)) {
702 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to set API version: %" PRIu64 ": %s",
703 : : ver, strerror(errno));
704 : : ret = -1;
705 : 0 : goto out_ctrl_close;
706 : : }
707 : :
708 : 0 : ret = rte_vhost_driver_get_features(path, &features);
709 [ # # ]: 0 : if (ret < 0) {
710 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to get backend features");
711 : 0 : goto out_ctrl_close;
712 : : }
713 : :
714 : 0 : ret = rte_vhost_driver_get_queue_num(path, &max_queue_pairs);
715 [ # # ]: 0 : if (ret < 0) {
716 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to get max queue pairs");
717 : 0 : goto out_ctrl_close;
718 : : }
719 : :
720 : 0 : VHOST_CONFIG_LOG(path, INFO, "VDUSE max queue pairs: %u", max_queue_pairs);
721 : 0 : total_queues = max_queue_pairs * 2;
722 : :
723 [ # # ]: 0 : if (max_queue_pairs == 1)
724 : 0 : features &= ~(RTE_BIT64(VIRTIO_NET_F_CTRL_VQ) | RTE_BIT64(VIRTIO_NET_F_MQ));
725 : : else
726 : 0 : total_queues += 1; /* Includes ctrl queue */
727 : :
728 : : dev_fd = open(path, O_RDWR);
729 [ # # ]: 0 : if (dev_fd >= 0) {
730 : 0 : VHOST_CONFIG_LOG(name, INFO, "Device already exists, reconnecting...");
731 : : reconnect = true;
732 [ # # ]: 0 : } else if (errno == ENOENT) {
733 : : struct vduse_dev_config *dev_config;
734 : :
735 : 0 : dev_config = malloc(offsetof(struct vduse_dev_config, config) +
736 : : sizeof(vnet_config));
737 [ # # ]: 0 : if (!dev_config) {
738 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to allocate VDUSE config");
739 : : ret = -1;
740 : 0 : goto out_ctrl_close;
741 : : }
742 : :
743 : 0 : vnet_config.max_virtqueue_pairs = max_queue_pairs;
744 : : memset(dev_config, 0, sizeof(struct vduse_dev_config));
745 : :
746 : 0 : rte_strscpy(dev_config->name, name, VDUSE_NAME_MAX - 1);
747 : 0 : dev_config->device_id = VIRTIO_ID_NET;
748 : 0 : dev_config->vendor_id = 0;
749 : 0 : dev_config->features = features;
750 : 0 : dev_config->vq_num = total_queues;
751 : 0 : dev_config->vq_align = rte_mem_page_size();
752 : 0 : dev_config->config_size = sizeof(struct virtio_net_config);
753 : 0 : memcpy(dev_config->config, &vnet_config, sizeof(vnet_config));
754 : :
755 : 0 : ret = ioctl(control_fd, VDUSE_CREATE_DEV, dev_config);
756 : 0 : free(dev_config);
757 : : dev_config = NULL;
758 [ # # ]: 0 : if (ret < 0) {
759 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to create VDUSE device: %s",
760 : : strerror(errno));
761 : 0 : goto out_ctrl_close;
762 : : }
763 : :
764 : : dev_fd = open(path, O_RDWR);
765 [ # # ]: 0 : if (dev_fd < 0) {
766 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to open newly created device %s: %s",
767 : : path, strerror(errno));
768 : : ret = -1;
769 : 0 : goto out_ctrl_close;
770 : : }
771 : : } else {
772 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to open device %s: %s",
773 : : path, strerror(errno));
774 : : ret = -1;
775 : 0 : goto out_ctrl_close;
776 : : }
777 : :
778 : 0 : ret = fcntl(dev_fd, F_SETFL, O_NONBLOCK);
779 [ # # ]: 0 : if (ret < 0) {
780 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to set chardev as non-blocking: %s",
781 : : strerror(errno));
782 : 0 : goto out_dev_close;
783 : : }
784 : :
785 : 0 : vid = vhost_new_device(&vduse_backend_ops);
786 [ # # ]: 0 : if (vid < 0) {
787 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to create new Vhost device");
788 : : ret = -1;
789 : 0 : goto out_dev_close;
790 : : }
791 : :
792 : : dev = get_device(vid);
793 [ # # ]: 0 : if (!dev) {
794 : : ret = -1;
795 : 0 : goto out_dev_destroy;
796 : : }
797 : :
798 : 0 : strncpy(dev->ifname, path, IF_NAME_SZ - 1);
799 : 0 : dev->vduse_ctrl_fd = control_fd;
800 : 0 : dev->vduse_dev_fd = dev_fd;
801 : :
802 : 0 : ret = vduse_reconnect_log_map(dev, !reconnect);
803 [ # # ]: 0 : if (ret < 0)
804 : 0 : goto out_dev_destroy;
805 : :
806 [ # # ]: 0 : if (reconnect) {
807 : 0 : ret = vduse_reconnect_log_check(dev, features, total_queues);
808 [ # # ]: 0 : if (ret < 0)
809 : 0 : goto out_log_unmap;
810 : :
811 : 0 : dev->status = dev->reconnect_log->status;
812 : : } else {
813 : 0 : dev->reconnect_log->version = VHOST_RECONNECT_VERSION;
814 : 0 : dev->reconnect_log->nr_vrings = total_queues;
815 : 0 : memcpy(&dev->reconnect_log->config, &vnet_config, sizeof(vnet_config));
816 : : }
817 : :
818 : 0 : vhost_setup_virtio_net(dev->vid, true, compliant_ol_flags, true, true);
819 : :
820 [ # # ]: 0 : for (i = 0; i < total_queues; i++) {
821 : 0 : struct vduse_vq_config vq_cfg = { 0 };
822 : : struct vhost_virtqueue *vq;
823 : :
824 : 0 : ret = alloc_vring_queue(dev, i);
825 [ # # ]: 0 : if (ret) {
826 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to alloc vring %d metadata", i);
827 : 0 : goto out_log_unmap;
828 : : }
829 : :
830 : 0 : vq = dev->virtqueue[i];
831 : 0 : vq->reconnect_log = &dev->reconnect_log->vring[i];
832 : :
833 [ # # ]: 0 : if (reconnect)
834 : 0 : continue;
835 : :
836 : 0 : vq_cfg.index = i;
837 : 0 : vq_cfg.max_size = 1024;
838 : :
839 : 0 : ret = ioctl(dev->vduse_dev_fd, VDUSE_VQ_SETUP, &vq_cfg);
840 [ # # ]: 0 : if (ret) {
841 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to set-up VQ %d", i);
842 : 0 : goto out_log_unmap;
843 : : }
844 : : }
845 : :
846 : 0 : dev->cvq = dev->virtqueue[max_queue_pairs * 2];
847 : :
848 : 0 : ret = fdset_add(vduse.fdset, dev->vduse_dev_fd, vduse_events_handler, NULL, dev);
849 [ # # ]: 0 : if (ret) {
850 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to add fd %d to vduse fdset",
851 : : dev->vduse_dev_fd);
852 : 0 : goto out_log_unmap;
853 : : }
854 : :
855 [ # # # # ]: 0 : if (reconnect && dev->status & VIRTIO_DEVICE_STATUS_DRIVER_OK) {
856 : 0 : ret = vduse_reconnect_start_device(dev);
857 [ # # ]: 0 : if (ret)
858 : 0 : goto out_log_unmap;
859 : : }
860 : :
861 : : return 0;
862 : :
863 : 0 : out_log_unmap:
864 : 0 : munmap(dev->reconnect_log, sizeof(*dev->reconnect_log));
865 : 0 : out_dev_destroy:
866 : 0 : vhost_destroy_device(vid);
867 : 0 : out_dev_close:
868 : : if (dev_fd >= 0)
869 : 0 : close(dev_fd);
870 : 0 : ioctl(control_fd, VDUSE_DESTROY_DEV, name);
871 : 0 : out_ctrl_close:
872 : 0 : close(control_fd);
873 : :
874 : 0 : return ret;
875 : : }
876 : :
877 : : int
878 : 0 : vduse_device_destroy(const char *path)
879 : : {
880 : 0 : const char *name = path + strlen("/dev/vduse/");
881 : : struct virtio_net *dev;
882 : : int vid, ret;
883 : :
884 [ # # ]: 0 : for (vid = 0; vid < RTE_MAX_VHOST_DEVICE; vid++) {
885 : 0 : dev = vhost_devices[vid];
886 : :
887 [ # # ]: 0 : if (dev == NULL)
888 : 0 : continue;
889 : :
890 [ # # ]: 0 : if (!strcmp(path, dev->ifname))
891 : : break;
892 : : }
893 : :
894 [ # # ]: 0 : if (vid == RTE_MAX_VHOST_DEVICE)
895 : : return -1;
896 : :
897 [ # # ]: 0 : if (dev->reconnect_log)
898 : 0 : munmap(dev->reconnect_log, sizeof(*dev->reconnect_log));
899 : :
900 : 0 : vduse_device_stop(dev);
901 : :
902 : 0 : fdset_del(vduse.fdset, dev->vduse_dev_fd);
903 : :
904 [ # # ]: 0 : if (dev->vduse_dev_fd >= 0) {
905 : 0 : close(dev->vduse_dev_fd);
906 : 0 : dev->vduse_dev_fd = -1;
907 : : }
908 : :
909 [ # # ]: 0 : if (dev->vduse_ctrl_fd >= 0) {
910 : : char reconnect_file[PATH_MAX];
911 : :
912 : 0 : ret = ioctl(dev->vduse_ctrl_fd, VDUSE_DESTROY_DEV, name);
913 [ # # ]: 0 : if (ret) {
914 : 0 : VHOST_CONFIG_LOG(name, ERR, "Failed to destroy VDUSE device: %s",
915 : : strerror(errno));
916 : : } else {
917 : : /*
918 : : * VDUSE device was no more attached to the vDPA bus,
919 : : * so we can remove the reconnect file.
920 : : */
921 : : ret = snprintf(reconnect_file, sizeof(reconnect_file), "%s/%s",
922 : : vduse_reconnect_dir, name);
923 [ # # ]: 0 : if (ret < 0 || ret == sizeof(reconnect_file))
924 : 0 : VHOST_CONFIG_LOG(name, ERR,
925 : : "Failed to create vduse reconnect path name");
926 : : else
927 : 0 : unlink(reconnect_file);
928 : : }
929 : :
930 : 0 : close(dev->vduse_ctrl_fd);
931 : 0 : dev->vduse_ctrl_fd = -1;
932 : : }
933 : :
934 : 0 : vhost_destroy_device(vid);
935 : :
936 : 0 : return 0;
937 : : }
|