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