xref: /openbmc/qemu/hw/block/vhost-user-blk.c (revision d5938f29fea29581725426f203a74da746ca03e7)
1 /*
2  * vhost-user-blk host device
3  *
4  * Copyright(C) 2017 Intel Corporation.
5  *
6  * Authors:
7  *  Changpeng Liu <changpeng.liu@intel.com>
8  *
9  * Largely based on the "vhost-user-scsi.c" and "vhost-scsi.c" implemented by:
10  * Felipe Franciosi <felipe@nutanix.com>
11  * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
12  * Nicholas Bellinger <nab@risingtidesystems.com>
13  *
14  * This work is licensed under the terms of the GNU LGPL, version 2 or later.
15  * See the COPYING.LIB file in the top-level directory.
16  *
17  */
18 
19 #include "qemu/osdep.h"
20 #include "qapi/error.h"
21 #include "qemu/error-report.h"
22 #include "qemu/cutils.h"
23 #include "qom/object.h"
24 #include "hw/qdev-core.h"
25 #include "hw/qdev-properties.h"
26 #include "hw/virtio/vhost.h"
27 #include "hw/virtio/vhost-user-blk.h"
28 #include "hw/virtio/virtio.h"
29 #include "hw/virtio/virtio-bus.h"
30 #include "hw/virtio/virtio-access.h"
31 
32 static const int user_feature_bits[] = {
33     VIRTIO_BLK_F_SIZE_MAX,
34     VIRTIO_BLK_F_SEG_MAX,
35     VIRTIO_BLK_F_GEOMETRY,
36     VIRTIO_BLK_F_BLK_SIZE,
37     VIRTIO_BLK_F_TOPOLOGY,
38     VIRTIO_BLK_F_MQ,
39     VIRTIO_BLK_F_RO,
40     VIRTIO_BLK_F_FLUSH,
41     VIRTIO_BLK_F_CONFIG_WCE,
42     VIRTIO_BLK_F_DISCARD,
43     VIRTIO_BLK_F_WRITE_ZEROES,
44     VIRTIO_F_VERSION_1,
45     VIRTIO_RING_F_INDIRECT_DESC,
46     VIRTIO_RING_F_EVENT_IDX,
47     VIRTIO_F_NOTIFY_ON_EMPTY,
48     VHOST_INVALID_FEATURE_BIT
49 };
50 
51 static void vhost_user_blk_update_config(VirtIODevice *vdev, uint8_t *config)
52 {
53     VHostUserBlk *s = VHOST_USER_BLK(vdev);
54 
55     memcpy(config, &s->blkcfg, sizeof(struct virtio_blk_config));
56 }
57 
58 static void vhost_user_blk_set_config(VirtIODevice *vdev, const uint8_t *config)
59 {
60     VHostUserBlk *s = VHOST_USER_BLK(vdev);
61     struct virtio_blk_config *blkcfg = (struct virtio_blk_config *)config;
62     int ret;
63 
64     if (blkcfg->wce == s->blkcfg.wce) {
65         return;
66     }
67 
68     ret = vhost_dev_set_config(&s->dev, &blkcfg->wce,
69                                offsetof(struct virtio_blk_config, wce),
70                                sizeof(blkcfg->wce),
71                                VHOST_SET_CONFIG_TYPE_MASTER);
72     if (ret) {
73         error_report("set device config space failed");
74         return;
75     }
76 
77     s->blkcfg.wce = blkcfg->wce;
78 }
79 
80 static int vhost_user_blk_handle_config_change(struct vhost_dev *dev)
81 {
82     int ret;
83     struct virtio_blk_config blkcfg;
84     VHostUserBlk *s = VHOST_USER_BLK(dev->vdev);
85 
86     ret = vhost_dev_get_config(dev, (uint8_t *)&blkcfg,
87                                sizeof(struct virtio_blk_config));
88     if (ret < 0) {
89         error_report("get config space failed");
90         return -1;
91     }
92 
93     /* valid for resize only */
94     if (blkcfg.capacity != s->blkcfg.capacity) {
95         s->blkcfg.capacity = blkcfg.capacity;
96         memcpy(dev->vdev->config, &s->blkcfg, sizeof(struct virtio_blk_config));
97         virtio_notify_config(dev->vdev);
98     }
99 
100     return 0;
101 }
102 
103 const VhostDevConfigOps blk_ops = {
104     .vhost_dev_config_notifier = vhost_user_blk_handle_config_change,
105 };
106 
107 static int vhost_user_blk_start(VirtIODevice *vdev)
108 {
109     VHostUserBlk *s = VHOST_USER_BLK(vdev);
110     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
111     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
112     int i, ret;
113 
114     if (!k->set_guest_notifiers) {
115         error_report("binding does not support guest notifiers");
116         return -ENOSYS;
117     }
118 
119     ret = vhost_dev_enable_notifiers(&s->dev, vdev);
120     if (ret < 0) {
121         error_report("Error enabling host notifiers: %d", -ret);
122         return ret;
123     }
124 
125     ret = k->set_guest_notifiers(qbus->parent, s->dev.nvqs, true);
126     if (ret < 0) {
127         error_report("Error binding guest notifier: %d", -ret);
128         goto err_host_notifiers;
129     }
130 
131     s->dev.acked_features = vdev->guest_features;
132 
133     if (!s->inflight->addr) {
134         ret = vhost_dev_get_inflight(&s->dev, s->queue_size, s->inflight);
135         if (ret < 0) {
136             error_report("Error get inflight: %d", -ret);
137             goto err_guest_notifiers;
138         }
139     }
140 
141     ret = vhost_dev_set_inflight(&s->dev, s->inflight);
142     if (ret < 0) {
143         error_report("Error set inflight: %d", -ret);
144         goto err_guest_notifiers;
145     }
146 
147     ret = vhost_dev_start(&s->dev, vdev);
148     if (ret < 0) {
149         error_report("Error starting vhost: %d", -ret);
150         goto err_guest_notifiers;
151     }
152 
153     /* guest_notifier_mask/pending not used yet, so just unmask
154      * everything here. virtio-pci will do the right thing by
155      * enabling/disabling irqfd.
156      */
157     for (i = 0; i < s->dev.nvqs; i++) {
158         vhost_virtqueue_mask(&s->dev, vdev, i, false);
159     }
160 
161     return ret;
162 
163 err_guest_notifiers:
164     k->set_guest_notifiers(qbus->parent, s->dev.nvqs, false);
165 err_host_notifiers:
166     vhost_dev_disable_notifiers(&s->dev, vdev);
167     return ret;
168 }
169 
170 static void vhost_user_blk_stop(VirtIODevice *vdev)
171 {
172     VHostUserBlk *s = VHOST_USER_BLK(vdev);
173     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
174     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
175     int ret;
176 
177     if (!k->set_guest_notifiers) {
178         return;
179     }
180 
181     vhost_dev_stop(&s->dev, vdev);
182 
183     ret = k->set_guest_notifiers(qbus->parent, s->dev.nvqs, false);
184     if (ret < 0) {
185         error_report("vhost guest notifier cleanup failed: %d", ret);
186         return;
187     }
188 
189     vhost_dev_disable_notifiers(&s->dev, vdev);
190 }
191 
192 static void vhost_user_blk_set_status(VirtIODevice *vdev, uint8_t status)
193 {
194     VHostUserBlk *s = VHOST_USER_BLK(vdev);
195     bool should_start = virtio_device_started(vdev, status);
196     int ret;
197 
198     if (!vdev->vm_running) {
199         should_start = false;
200     }
201 
202     if (!s->connected) {
203         return;
204     }
205 
206     if (s->dev.started == should_start) {
207         return;
208     }
209 
210     if (should_start) {
211         ret = vhost_user_blk_start(vdev);
212         if (ret < 0) {
213             error_report("vhost-user-blk: vhost start failed: %s",
214                          strerror(-ret));
215             qemu_chr_fe_disconnect(&s->chardev);
216         }
217     } else {
218         vhost_user_blk_stop(vdev);
219     }
220 
221 }
222 
223 static uint64_t vhost_user_blk_get_features(VirtIODevice *vdev,
224                                             uint64_t features,
225                                             Error **errp)
226 {
227     VHostUserBlk *s = VHOST_USER_BLK(vdev);
228 
229     /* Turn on pre-defined features */
230     virtio_add_feature(&features, VIRTIO_BLK_F_SEG_MAX);
231     virtio_add_feature(&features, VIRTIO_BLK_F_GEOMETRY);
232     virtio_add_feature(&features, VIRTIO_BLK_F_TOPOLOGY);
233     virtio_add_feature(&features, VIRTIO_BLK_F_BLK_SIZE);
234     virtio_add_feature(&features, VIRTIO_BLK_F_FLUSH);
235     virtio_add_feature(&features, VIRTIO_BLK_F_RO);
236     virtio_add_feature(&features, VIRTIO_BLK_F_DISCARD);
237     virtio_add_feature(&features, VIRTIO_BLK_F_WRITE_ZEROES);
238 
239     if (s->config_wce) {
240         virtio_add_feature(&features, VIRTIO_BLK_F_CONFIG_WCE);
241     }
242     if (s->num_queues > 1) {
243         virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
244     }
245 
246     return vhost_get_features(&s->dev, user_feature_bits, features);
247 }
248 
249 static void vhost_user_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq)
250 {
251     VHostUserBlk *s = VHOST_USER_BLK(vdev);
252     int i, ret;
253 
254     if (!vdev->start_on_kick) {
255         return;
256     }
257 
258     if (!s->connected) {
259         return;
260     }
261 
262     if (s->dev.started) {
263         return;
264     }
265 
266     /* Some guests kick before setting VIRTIO_CONFIG_S_DRIVER_OK so start
267      * vhost here instead of waiting for .set_status().
268      */
269     ret = vhost_user_blk_start(vdev);
270     if (ret < 0) {
271         error_report("vhost-user-blk: vhost start failed: %s",
272                      strerror(-ret));
273         qemu_chr_fe_disconnect(&s->chardev);
274         return;
275     }
276 
277     /* Kick right away to begin processing requests already in vring */
278     for (i = 0; i < s->dev.nvqs; i++) {
279         VirtQueue *kick_vq = virtio_get_queue(vdev, i);
280 
281         if (!virtio_queue_get_desc_addr(vdev, i)) {
282             continue;
283         }
284         event_notifier_set(virtio_queue_get_host_notifier(kick_vq));
285     }
286 }
287 
288 static void vhost_user_blk_reset(VirtIODevice *vdev)
289 {
290     VHostUserBlk *s = VHOST_USER_BLK(vdev);
291 
292     vhost_dev_free_inflight(s->inflight);
293 }
294 
295 static int vhost_user_blk_connect(DeviceState *dev)
296 {
297     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
298     VHostUserBlk *s = VHOST_USER_BLK(vdev);
299     int ret = 0;
300 
301     if (s->connected) {
302         return 0;
303     }
304     s->connected = true;
305 
306     s->dev.nvqs = s->num_queues;
307     s->dev.vqs = s->vqs;
308     s->dev.vq_index = 0;
309     s->dev.backend_features = 0;
310 
311     vhost_dev_set_config_notifier(&s->dev, &blk_ops);
312 
313     ret = vhost_dev_init(&s->dev, &s->vhost_user, VHOST_BACKEND_TYPE_USER, 0);
314     if (ret < 0) {
315         error_report("vhost-user-blk: vhost initialization failed: %s",
316                      strerror(-ret));
317         return ret;
318     }
319 
320     /* restore vhost state */
321     if (virtio_device_started(vdev, vdev->status)) {
322         ret = vhost_user_blk_start(vdev);
323         if (ret < 0) {
324             error_report("vhost-user-blk: vhost start failed: %s",
325                          strerror(-ret));
326             return ret;
327         }
328     }
329 
330     return 0;
331 }
332 
333 static void vhost_user_blk_disconnect(DeviceState *dev)
334 {
335     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
336     VHostUserBlk *s = VHOST_USER_BLK(vdev);
337 
338     if (!s->connected) {
339         return;
340     }
341     s->connected = false;
342 
343     if (s->dev.started) {
344         vhost_user_blk_stop(vdev);
345     }
346 
347     vhost_dev_cleanup(&s->dev);
348 }
349 
350 static gboolean vhost_user_blk_watch(GIOChannel *chan, GIOCondition cond,
351                                      void *opaque)
352 {
353     DeviceState *dev = opaque;
354     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
355     VHostUserBlk *s = VHOST_USER_BLK(vdev);
356 
357     qemu_chr_fe_disconnect(&s->chardev);
358 
359     return true;
360 }
361 
362 static void vhost_user_blk_event(void *opaque, int event)
363 {
364     DeviceState *dev = opaque;
365     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
366     VHostUserBlk *s = VHOST_USER_BLK(vdev);
367 
368     switch (event) {
369     case CHR_EVENT_OPENED:
370         if (vhost_user_blk_connect(dev) < 0) {
371             qemu_chr_fe_disconnect(&s->chardev);
372             return;
373         }
374         s->watch = qemu_chr_fe_add_watch(&s->chardev, G_IO_HUP,
375                                          vhost_user_blk_watch, dev);
376         break;
377     case CHR_EVENT_CLOSED:
378         vhost_user_blk_disconnect(dev);
379         if (s->watch) {
380             g_source_remove(s->watch);
381             s->watch = 0;
382         }
383         break;
384     }
385 }
386 
387 static void vhost_user_blk_device_realize(DeviceState *dev, Error **errp)
388 {
389     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
390     VHostUserBlk *s = VHOST_USER_BLK(vdev);
391     Error *err = NULL;
392     int i, ret;
393 
394     if (!s->chardev.chr) {
395         error_setg(errp, "vhost-user-blk: chardev is mandatory");
396         return;
397     }
398 
399     if (!s->num_queues || s->num_queues > VIRTIO_QUEUE_MAX) {
400         error_setg(errp, "vhost-user-blk: invalid number of IO queues");
401         return;
402     }
403 
404     if (!s->queue_size) {
405         error_setg(errp, "vhost-user-blk: queue size must be non-zero");
406         return;
407     }
408 
409     if (!vhost_user_init(&s->vhost_user, &s->chardev, errp)) {
410         return;
411     }
412 
413     virtio_init(vdev, "virtio-blk", VIRTIO_ID_BLOCK,
414                 sizeof(struct virtio_blk_config));
415 
416     for (i = 0; i < s->num_queues; i++) {
417         virtio_add_queue(vdev, s->queue_size,
418                          vhost_user_blk_handle_output);
419     }
420 
421     s->inflight = g_new0(struct vhost_inflight, 1);
422     s->vqs = g_new(struct vhost_virtqueue, s->num_queues);
423     s->watch = 0;
424     s->connected = false;
425 
426     qemu_chr_fe_set_handlers(&s->chardev,  NULL, NULL, vhost_user_blk_event,
427                              NULL, (void *)dev, NULL, true);
428 
429 reconnect:
430     if (qemu_chr_fe_wait_connected(&s->chardev, &err) < 0) {
431         error_report_err(err);
432         goto virtio_err;
433     }
434 
435     /* check whether vhost_user_blk_connect() failed or not */
436     if (!s->connected) {
437         goto reconnect;
438     }
439 
440     ret = vhost_dev_get_config(&s->dev, (uint8_t *)&s->blkcfg,
441                                sizeof(struct virtio_blk_config));
442     if (ret < 0) {
443         error_report("vhost-user-blk: get block config failed");
444         goto reconnect;
445     }
446 
447     if (s->blkcfg.num_queues != s->num_queues) {
448         s->blkcfg.num_queues = s->num_queues;
449     }
450 
451     return;
452 
453 virtio_err:
454     g_free(s->vqs);
455     g_free(s->inflight);
456     virtio_cleanup(vdev);
457     vhost_user_cleanup(&s->vhost_user);
458 }
459 
460 static void vhost_user_blk_device_unrealize(DeviceState *dev, Error **errp)
461 {
462     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
463     VHostUserBlk *s = VHOST_USER_BLK(dev);
464 
465     virtio_set_status(vdev, 0);
466     qemu_chr_fe_set_handlers(&s->chardev,  NULL, NULL, NULL,
467                              NULL, NULL, NULL, false);
468     vhost_dev_cleanup(&s->dev);
469     vhost_dev_free_inflight(s->inflight);
470     g_free(s->vqs);
471     g_free(s->inflight);
472     virtio_cleanup(vdev);
473     vhost_user_cleanup(&s->vhost_user);
474 }
475 
476 static void vhost_user_blk_instance_init(Object *obj)
477 {
478     VHostUserBlk *s = VHOST_USER_BLK(obj);
479 
480     device_add_bootindex_property(obj, &s->bootindex, "bootindex",
481                                   "/disk@0,0", DEVICE(obj), NULL);
482 }
483 
484 static const VMStateDescription vmstate_vhost_user_blk = {
485     .name = "vhost-user-blk",
486     .minimum_version_id = 1,
487     .version_id = 1,
488     .fields = (VMStateField[]) {
489         VMSTATE_VIRTIO_DEVICE,
490         VMSTATE_END_OF_LIST()
491     },
492 };
493 
494 static Property vhost_user_blk_properties[] = {
495     DEFINE_PROP_CHR("chardev", VHostUserBlk, chardev),
496     DEFINE_PROP_UINT16("num-queues", VHostUserBlk, num_queues, 1),
497     DEFINE_PROP_UINT32("queue-size", VHostUserBlk, queue_size, 128),
498     DEFINE_PROP_BIT("config-wce", VHostUserBlk, config_wce, 0, true),
499     DEFINE_PROP_END_OF_LIST(),
500 };
501 
502 static void vhost_user_blk_class_init(ObjectClass *klass, void *data)
503 {
504     DeviceClass *dc = DEVICE_CLASS(klass);
505     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
506 
507     dc->props = vhost_user_blk_properties;
508     dc->vmsd = &vmstate_vhost_user_blk;
509     set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
510     vdc->realize = vhost_user_blk_device_realize;
511     vdc->unrealize = vhost_user_blk_device_unrealize;
512     vdc->get_config = vhost_user_blk_update_config;
513     vdc->set_config = vhost_user_blk_set_config;
514     vdc->get_features = vhost_user_blk_get_features;
515     vdc->set_status = vhost_user_blk_set_status;
516     vdc->reset = vhost_user_blk_reset;
517 }
518 
519 static const TypeInfo vhost_user_blk_info = {
520     .name = TYPE_VHOST_USER_BLK,
521     .parent = TYPE_VIRTIO_DEVICE,
522     .instance_size = sizeof(VHostUserBlk),
523     .instance_init = vhost_user_blk_instance_init,
524     .class_init = vhost_user_blk_class_init,
525 };
526 
527 static void virtio_register_types(void)
528 {
529     type_register_static(&vhost_user_blk_info);
530 }
531 
532 type_init(virtio_register_types)
533