xref: /qemu/hw/virtio/vhost-shadow-virtqueue.c (revision dafb34c9926a38b3bfc4ab8f35a99ca1c706648c)
1 /*
2  * vhost shadow virtqueue
3  *
4  * SPDX-FileCopyrightText: Red Hat, Inc. 2021
5  * SPDX-FileContributor: Author: Eugenio Pérez <eperezma@redhat.com>
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "qemu/osdep.h"
11 #include "hw/virtio/vhost-shadow-virtqueue.h"
12 
13 #include "qemu/error-report.h"
14 #include "qapi/error.h"
15 #include "qemu/main-loop.h"
16 #include "linux-headers/linux/vhost.h"
17 
18 /**
19  * Validate the transport device features that both guests can use with the SVQ
20  * and SVQs can use with the device.
21  *
22  * @dev_features: The features
23  * @errp: Error pointer
24  */
25 bool vhost_svq_valid_features(uint64_t features, Error **errp)
26 {
27     bool ok = true;
28     uint64_t svq_features = features;
29 
30     for (uint64_t b = VIRTIO_TRANSPORT_F_START; b <= VIRTIO_TRANSPORT_F_END;
31          ++b) {
32         switch (b) {
33         case VIRTIO_F_ANY_LAYOUT:
34             continue;
35 
36         case VIRTIO_F_ACCESS_PLATFORM:
37             /* SVQ trust in the host's IOMMU to translate addresses */
38         case VIRTIO_F_VERSION_1:
39             /* SVQ trust that the guest vring is little endian */
40             if (!(svq_features & BIT_ULL(b))) {
41                 svq_features |= BIT_ULL(b);
42                 ok = false;
43             }
44             continue;
45 
46         default:
47             if (svq_features & BIT_ULL(b)) {
48                 svq_features &= ~BIT_ULL(b);
49                 ok = false;
50             }
51         }
52     }
53 
54     if (!ok) {
55         error_setg(errp, "SVQ Invalid device feature flags, offer: 0x%"PRIx64
56                          ", ok: 0x%"PRIx64, features, svq_features);
57     }
58     return ok;
59 }
60 
61 /**
62  * Forward guest notifications.
63  *
64  * @n: guest kick event notifier, the one that guest set to notify svq.
65  */
66 static void vhost_handle_guest_kick(EventNotifier *n)
67 {
68     VhostShadowVirtqueue *svq = container_of(n, VhostShadowVirtqueue, svq_kick);
69     event_notifier_test_and_clear(n);
70     event_notifier_set(&svq->hdev_kick);
71 }
72 
73 /**
74  * Forward vhost notifications
75  *
76  * @n: hdev call event notifier, the one that device set to notify svq.
77  */
78 static void vhost_svq_handle_call(EventNotifier *n)
79 {
80     VhostShadowVirtqueue *svq = container_of(n, VhostShadowVirtqueue,
81                                              hdev_call);
82     event_notifier_test_and_clear(n);
83     event_notifier_set(&svq->svq_call);
84 }
85 
86 /**
87  * Set the call notifier for the SVQ to call the guest
88  *
89  * @svq: Shadow virtqueue
90  * @call_fd: call notifier
91  *
92  * Called on BQL context.
93  */
94 void vhost_svq_set_svq_call_fd(VhostShadowVirtqueue *svq, int call_fd)
95 {
96     if (call_fd == VHOST_FILE_UNBIND) {
97         /*
98          * Fail event_notifier_set if called handling device call.
99          *
100          * SVQ still needs device notifications, since it needs to keep
101          * forwarding used buffers even with the unbind.
102          */
103         memset(&svq->svq_call, 0, sizeof(svq->svq_call));
104     } else {
105         event_notifier_init_fd(&svq->svq_call, call_fd);
106     }
107 }
108 
109 /**
110  * Get the shadow vq vring address.
111  * @svq: Shadow virtqueue
112  * @addr: Destination to store address
113  */
114 void vhost_svq_get_vring_addr(const VhostShadowVirtqueue *svq,
115                               struct vhost_vring_addr *addr)
116 {
117     addr->desc_user_addr = (uint64_t)(intptr_t)svq->vring.desc;
118     addr->avail_user_addr = (uint64_t)(intptr_t)svq->vring.avail;
119     addr->used_user_addr = (uint64_t)(intptr_t)svq->vring.used;
120 }
121 
122 size_t vhost_svq_driver_area_size(const VhostShadowVirtqueue *svq)
123 {
124     size_t desc_size = sizeof(vring_desc_t) * svq->vring.num;
125     size_t avail_size = offsetof(vring_avail_t, ring) +
126                                              sizeof(uint16_t) * svq->vring.num;
127 
128     return ROUND_UP(desc_size + avail_size, qemu_real_host_page_size);
129 }
130 
131 size_t vhost_svq_device_area_size(const VhostShadowVirtqueue *svq)
132 {
133     size_t used_size = offsetof(vring_used_t, ring) +
134                                     sizeof(vring_used_elem_t) * svq->vring.num;
135     return ROUND_UP(used_size, qemu_real_host_page_size);
136 }
137 
138 /**
139  * Set a new file descriptor for the guest to kick the SVQ and notify for avail
140  *
141  * @svq: The svq
142  * @svq_kick_fd: The svq kick fd
143  *
144  * Note that the SVQ will never close the old file descriptor.
145  */
146 void vhost_svq_set_svq_kick_fd(VhostShadowVirtqueue *svq, int svq_kick_fd)
147 {
148     EventNotifier *svq_kick = &svq->svq_kick;
149     bool poll_stop = VHOST_FILE_UNBIND != event_notifier_get_fd(svq_kick);
150     bool poll_start = svq_kick_fd != VHOST_FILE_UNBIND;
151 
152     if (poll_stop) {
153         event_notifier_set_handler(svq_kick, NULL);
154     }
155 
156     /*
157      * event_notifier_set_handler already checks for guest's notifications if
158      * they arrive at the new file descriptor in the switch, so there is no
159      * need to explicitly check for them.
160      */
161     if (poll_start) {
162         event_notifier_init_fd(svq_kick, svq_kick_fd);
163         event_notifier_set(svq_kick);
164         event_notifier_set_handler(svq_kick, vhost_handle_guest_kick);
165     }
166 }
167 
168 /**
169  * Stop the shadow virtqueue operation.
170  * @svq: Shadow Virtqueue
171  */
172 void vhost_svq_stop(VhostShadowVirtqueue *svq)
173 {
174     event_notifier_set_handler(&svq->svq_kick, NULL);
175 }
176 
177 /**
178  * Creates vhost shadow virtqueue, and instructs the vhost device to use the
179  * shadow methods and file descriptors.
180  *
181  * Returns the new virtqueue or NULL.
182  *
183  * In case of error, reason is reported through error_report.
184  */
185 VhostShadowVirtqueue *vhost_svq_new(void)
186 {
187     g_autofree VhostShadowVirtqueue *svq = g_new0(VhostShadowVirtqueue, 1);
188     int r;
189 
190     r = event_notifier_init(&svq->hdev_kick, 0);
191     if (r != 0) {
192         error_report("Couldn't create kick event notifier: %s (%d)",
193                      g_strerror(errno), errno);
194         goto err_init_hdev_kick;
195     }
196 
197     r = event_notifier_init(&svq->hdev_call, 0);
198     if (r != 0) {
199         error_report("Couldn't create call event notifier: %s (%d)",
200                      g_strerror(errno), errno);
201         goto err_init_hdev_call;
202     }
203 
204     event_notifier_init_fd(&svq->svq_kick, VHOST_FILE_UNBIND);
205     event_notifier_set_handler(&svq->hdev_call, vhost_svq_handle_call);
206     return g_steal_pointer(&svq);
207 
208 err_init_hdev_call:
209     event_notifier_cleanup(&svq->hdev_kick);
210 
211 err_init_hdev_kick:
212     return NULL;
213 }
214 
215 /**
216  * Free the resources of the shadow virtqueue.
217  *
218  * @pvq: gpointer to SVQ so it can be used by autofree functions.
219  */
220 void vhost_svq_free(gpointer pvq)
221 {
222     VhostShadowVirtqueue *vq = pvq;
223     vhost_svq_stop(vq);
224     event_notifier_cleanup(&vq->hdev_kick);
225     event_notifier_set_handler(&vq->hdev_call, NULL);
226     event_notifier_cleanup(&vq->hdev_call);
227     g_free(vq);
228 }
229