xref: /openbmc/qemu/hw/virtio/vhost-user-base.c (revision e7fe912276c0b5859404327b6ab9ce332a2a887b)
1 /*
2  * Base vhost-user-base implementation. This can be used to derive a
3  * more fully specified vhost-user backend either generically (see
4  * vhost-user-device) or via a specific stub for a device which
5  * encapsulates some fixed parameters.
6  *
7  * Copyright (c) 2023 Linaro Ltd
8  * Author: Alex Bennée <alex.bennee@linaro.org>
9  *
10  * SPDX-License-Identifier: GPL-2.0-or-later
11  */
12 
13 #include "qemu/osdep.h"
14 #include "qapi/error.h"
15 #include "hw/qdev-properties.h"
16 #include "hw/virtio/virtio-bus.h"
17 #include "hw/virtio/vhost-user-base.h"
18 #include "qemu/error-report.h"
19 
20 static void vub_start(VirtIODevice *vdev)
21 {
22     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
23     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
24     VHostUserBase *vub = VHOST_USER_BASE(vdev);
25     int ret, i;
26 
27     if (!k->set_guest_notifiers) {
28         error_report("binding does not support guest notifiers");
29         return;
30     }
31 
32     ret = vhost_dev_enable_notifiers(&vub->vhost_dev, vdev);
33     if (ret < 0) {
34         error_report("Error enabling host notifiers: %d", -ret);
35         return;
36     }
37 
38     ret = k->set_guest_notifiers(qbus->parent, vub->vhost_dev.nvqs, true);
39     if (ret < 0) {
40         error_report("Error binding guest notifier: %d", -ret);
41         goto err_host_notifiers;
42     }
43 
44     vub->vhost_dev.acked_features = vdev->guest_features;
45 
46     ret = vhost_dev_start(&vub->vhost_dev, vdev, true);
47     if (ret < 0) {
48         error_report("Error starting vhost-user-base: %d", -ret);
49         goto err_guest_notifiers;
50     }
51 
52     /*
53      * guest_notifier_mask/pending not used yet, so just unmask
54      * everything here. virtio-pci will do the right thing by
55      * enabling/disabling irqfd.
56      */
57     for (i = 0; i < vub->vhost_dev.nvqs; i++) {
58         vhost_virtqueue_mask(&vub->vhost_dev, vdev, i, false);
59     }
60 
61     return;
62 
63 err_guest_notifiers:
64     k->set_guest_notifiers(qbus->parent, vub->vhost_dev.nvqs, false);
65 err_host_notifiers:
66     vhost_dev_disable_notifiers(&vub->vhost_dev, vdev);
67 }
68 
69 static void vub_stop(VirtIODevice *vdev)
70 {
71     VHostUserBase *vub = VHOST_USER_BASE(vdev);
72     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
73     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
74     int ret;
75 
76     if (!k->set_guest_notifiers) {
77         return;
78     }
79 
80     vhost_dev_stop(&vub->vhost_dev, vdev, true);
81 
82     ret = k->set_guest_notifiers(qbus->parent, vub->vhost_dev.nvqs, false);
83     if (ret < 0) {
84         error_report("vhost guest notifier cleanup failed: %d", ret);
85         return;
86     }
87 
88     vhost_dev_disable_notifiers(&vub->vhost_dev, vdev);
89 }
90 
91 static void vub_set_status(VirtIODevice *vdev, uint8_t status)
92 {
93     VHostUserBase *vub = VHOST_USER_BASE(vdev);
94     bool should_start = virtio_device_should_start(vdev, status);
95 
96     if (vhost_dev_is_started(&vub->vhost_dev) == should_start) {
97         return;
98     }
99 
100     if (should_start) {
101         vub_start(vdev);
102     } else {
103         vub_stop(vdev);
104     }
105 }
106 
107 /*
108  * For an implementation where everything is delegated to the backend
109  * we don't do anything other than return the full feature set offered
110  * by the daemon (module the reserved feature bit).
111  */
112 static uint64_t vub_get_features(VirtIODevice *vdev,
113                                  uint64_t requested_features, Error **errp)
114 {
115     VHostUserBase *vub = VHOST_USER_BASE(vdev);
116     /* This should be set when the vhost connection initialises */
117     g_assert(vub->vhost_dev.features);
118     return vub->vhost_dev.features & ~(1ULL << VHOST_USER_F_PROTOCOL_FEATURES);
119 }
120 
121 /*
122  * To handle VirtIO config we need to know the size of the config
123  * space. We don't cache the config but re-fetch it from the guest
124  * every time in case something has changed.
125  */
126 static void vub_get_config(VirtIODevice *vdev, uint8_t *config)
127 {
128     VHostUserBase *vub = VHOST_USER_BASE(vdev);
129     Error *local_err = NULL;
130 
131     /*
132      * There will have been a warning during vhost_dev_init, but lets
133      * assert here as nothing will go right now.
134      */
135     g_assert(vub->config_size && vub->vhost_user.supports_config == true);
136 
137     if (vhost_dev_get_config(&vub->vhost_dev, config,
138                              vub->config_size, &local_err)) {
139         error_report_err(local_err);
140     }
141 }
142 
143 /*
144  * When the daemon signals an update to the config we just need to
145  * signal the guest as we re-read the config on demand above.
146  */
147 static int vub_config_notifier(struct vhost_dev *dev)
148 {
149     virtio_notify_config(dev->vdev);
150     return 0;
151 }
152 
153 const VhostDevConfigOps vub_config_ops = {
154     .vhost_dev_config_notifier = vub_config_notifier,
155 };
156 
157 static void vub_handle_output(VirtIODevice *vdev, VirtQueue *vq)
158 {
159     /*
160      * Not normally called; it's the daemon that handles the queue;
161      * however virtio's cleanup path can call this.
162      */
163 }
164 
165 static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserBase *vub)
166 {
167     vhost_user_cleanup(&vub->vhost_user);
168 
169     for (int i = 0; i < vub->num_vqs; i++) {
170         VirtQueue *vq = g_ptr_array_index(vub->vqs, i);
171         virtio_delete_queue(vq);
172     }
173 
174     virtio_cleanup(vdev);
175 }
176 
177 static int vub_connect(DeviceState *dev)
178 {
179     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
180     VHostUserBase *vub = VHOST_USER_BASE(vdev);
181     struct vhost_dev *vhost_dev = &vub->vhost_dev;
182 
183     if (vub->connected) {
184         return 0;
185     }
186     vub->connected = true;
187 
188     /*
189      * If we support VHOST_USER_GET_CONFIG we must enable the notifier
190      * so we can ping the guest when it updates.
191      */
192     if (vub->vhost_user.supports_config) {
193         vhost_dev_set_config_notifier(vhost_dev, &vub_config_ops);
194     }
195 
196     /* restore vhost state */
197     if (virtio_device_started(vdev, vdev->status)) {
198         vub_start(vdev);
199     }
200 
201     return 0;
202 }
203 
204 static void vub_event(void *opaque, QEMUChrEvent event);
205 
206 static void vub_disconnect(DeviceState *dev)
207 {
208     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
209     VHostUserBase *vub = VHOST_USER_BASE(vdev);
210 
211     if (!vub->connected) {
212         return;
213     }
214     vub->connected = false;
215 
216     vub_stop(vdev);
217     vhost_dev_cleanup(&vub->vhost_dev);
218 
219     /* Re-instate the event handler for new connections */
220     qemu_chr_fe_set_handlers(&vub->chardev,
221                              NULL, NULL, vub_event,
222                              NULL, dev, NULL, true);
223 }
224 
225 static void vub_event(void *opaque, QEMUChrEvent event)
226 {
227     DeviceState *dev = opaque;
228     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
229     VHostUserBase *vub = VHOST_USER_BASE(vdev);
230 
231     switch (event) {
232     case CHR_EVENT_OPENED:
233         if (vub_connect(dev) < 0) {
234             qemu_chr_fe_disconnect(&vub->chardev);
235             return;
236         }
237         break;
238     case CHR_EVENT_CLOSED:
239         /* defer close until later to avoid circular close */
240         vhost_user_async_close(dev, &vub->chardev, &vub->vhost_dev,
241                                vub_disconnect, vub_event);
242         break;
243     case CHR_EVENT_BREAK:
244     case CHR_EVENT_MUX_IN:
245     case CHR_EVENT_MUX_OUT:
246         /* Ignore */
247         break;
248     }
249 }
250 
251 static void vub_device_realize(DeviceState *dev, Error **errp)
252 {
253     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
254     VHostUserBase *vub = VHOST_USER_BASE(dev);
255     int ret;
256 
257     if (!vub->chardev.chr) {
258         error_setg(errp, "vhost-user-base: missing chardev");
259         return;
260     }
261 
262     if (!vub->virtio_id) {
263         error_setg(errp, "vhost-user-base: need to define device id");
264         return;
265     }
266 
267     if (!vub->num_vqs) {
268         vub->num_vqs = 1; /* reasonable default? */
269     }
270 
271     if (!vub->vq_size) {
272         vub->vq_size = 64;
273     }
274 
275     /*
276      * We can't handle config requests unless we know the size of the
277      * config region, specialisations of the vhost-user-base will be
278      * able to set this.
279      */
280     if (vub->config_size) {
281         vub->vhost_user.supports_config = true;
282     }
283 
284     if (!vhost_user_init(&vub->vhost_user, &vub->chardev, errp)) {
285         return;
286     }
287 
288     virtio_init(vdev, vub->virtio_id, vub->config_size);
289 
290     /*
291      * Disable guest notifiers, by default all notifications will be via the
292      * asynchronous vhost-user socket.
293      */
294     vdev->use_guest_notifier_mask = false;
295 
296     /* Allocate queues */
297     vub->vqs = g_ptr_array_sized_new(vub->num_vqs);
298     for (int i = 0; i < vub->num_vqs; i++) {
299         g_ptr_array_add(vub->vqs,
300                         virtio_add_queue(vdev, vub->vq_size,
301                                          vub_handle_output));
302     }
303 
304     vub->vhost_dev.nvqs = vub->num_vqs;
305     vub->vhost_dev.vqs = g_new0(struct vhost_virtqueue, vub->vhost_dev.nvqs);
306 
307     /* connect to backend */
308     ret = vhost_dev_init(&vub->vhost_dev, &vub->vhost_user,
309                          VHOST_BACKEND_TYPE_USER, 0, errp);
310 
311     if (ret < 0) {
312         do_vhost_user_cleanup(vdev, vub);
313     }
314 
315     qemu_chr_fe_set_handlers(&vub->chardev, NULL, NULL, vub_event, NULL,
316                              dev, NULL, true);
317 }
318 
319 static void vub_device_unrealize(DeviceState *dev)
320 {
321     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
322     VHostUserBase *vub = VHOST_USER_BASE(dev);
323     struct vhost_virtqueue *vhost_vqs = vub->vhost_dev.vqs;
324 
325     /* This will stop vhost backend if appropriate. */
326     vub_set_status(vdev, 0);
327     vhost_dev_cleanup(&vub->vhost_dev);
328     g_free(vhost_vqs);
329     do_vhost_user_cleanup(vdev, vub);
330 }
331 
332 static void vub_class_init(ObjectClass *klass, void *data)
333 {
334     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
335 
336     vdc->realize = vub_device_realize;
337     vdc->unrealize = vub_device_unrealize;
338     vdc->get_features = vub_get_features;
339     vdc->get_config = vub_get_config;
340     vdc->set_status = vub_set_status;
341 }
342 
343 static const TypeInfo vub_types[] = {
344     {
345         .name = TYPE_VHOST_USER_BASE,
346         .parent = TYPE_VIRTIO_DEVICE,
347         .instance_size = sizeof(VHostUserBase),
348         .class_init = vub_class_init,
349         .class_size = sizeof(VHostUserBaseClass),
350         .abstract = true
351     }
352 };
353 
354 DEFINE_TYPES(vub_types)
355