*/
#include "qemu/osdep.h"
+#include "qapi/error.h"
#include "trace.h"
#include "qemu/iov.h"
+#include "qemu/main-loop.h"
#include "qemu/thread.h"
#include "qemu/error-report.h"
#include "hw/virtio/virtio-access.h"
-#include "hw/virtio/dataplane/vring.h"
-#include "hw/virtio/dataplane/vring-accessors.h"
-#include "sysemu/block-backend.h"
#include "hw/virtio/virtio-blk.h"
#include "virtio-blk.h"
#include "block/aio.h"
struct VirtIOBlockDataPlane {
bool starting;
bool stopping;
- bool disabled;
VirtIOBlkConf *conf;
-
VirtIODevice *vdev;
- Vring vring; /* virtqueue vring */
- EventNotifier *guest_notifier; /* irq */
QEMUBH *bh; /* bh for guest notification */
-
- Notifier insert_notifier, remove_notifier;
+ unsigned long *batch_notify_vqs;
+ bool batch_notifications;
/* Note that these EventNotifiers are assigned by value. This is
* fine as long as you do not call event_notifier_cleanup on them
*/
IOThread *iothread;
AioContext *ctx;
- EventNotifier host_notifier; /* doorbell */
-
- /* Operation blocker on BDS */
- Error *blocker;
- void (*saved_complete_request)(struct VirtIOBlockReq *req,
- unsigned char status);
};
/* Raise an interrupt to signal guest, if necessary */
-static void notify_guest(VirtIOBlockDataPlane *s)
+void virtio_blk_data_plane_notify(VirtIOBlockDataPlane *s, VirtQueue *vq)
{
- if (!vring_should_notify(s->vdev, &s->vring)) {
- return;
+ if (s->batch_notifications) {
+ set_bit(virtio_get_queue_index(vq), s->batch_notify_vqs);
+ qemu_bh_schedule(s->bh);
+ } else {
+ virtio_notify_irqfd(s->vdev, vq);
}
-
- event_notifier_set(s->guest_notifier);
}
static void notify_guest_bh(void *opaque)
{
VirtIOBlockDataPlane *s = opaque;
+ unsigned nvqs = s->conf->num_queues;
+ unsigned long bitmap[BITS_TO_LONGS(nvqs)];
+ unsigned j;
- notify_guest(s);
-}
+ memcpy(bitmap, s->batch_notify_vqs, sizeof(bitmap));
+ memset(s->batch_notify_vqs, 0, sizeof(bitmap));
-static void complete_request_vring(VirtIOBlockReq *req, unsigned char status)
-{
- VirtIOBlockDataPlane *s = req->dev->dataplane;
- stb_p(&req->in->status, status);
+ for (j = 0; j < nvqs; j += BITS_PER_LONG) {
+ unsigned long bits = bitmap[j];
- vring_push(s->vdev, &req->dev->dataplane->vring, &req->elem, req->in_len);
+ while (bits != 0) {
+ unsigned i = j + ctzl(bits);
+ VirtQueue *vq = virtio_get_queue(s->vdev, i);
- /* Suppress notification to guest by BH and its scheduled
- * flag because requests are completed as a batch after io
- * plug & unplug is introduced, and the BH can still be
- * executed in dataplane aio context even after it is
- * stopped, so needn't worry about notification loss with BH.
- */
- qemu_bh_schedule(s->bh);
-}
-
-static void handle_notify(EventNotifier *e)
-{
- VirtIOBlockDataPlane *s = container_of(e, VirtIOBlockDataPlane,
- host_notifier);
- VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
+ virtio_notify_irqfd(s->vdev, vq);
- event_notifier_test_and_clear(&s->host_notifier);
- blk_io_plug(s->conf->conf.blk);
- for (;;) {
- MultiReqBuffer mrb = {};
-
- /* Disable guest->host notifies to avoid unnecessary vmexits */
- vring_disable_notification(s->vdev, &s->vring);
-
- for (;;) {
- VirtIOBlockReq *req = vring_pop(s->vdev, &s->vring,
- sizeof(VirtIOBlockReq));
-
- if (req == NULL) {
- break; /* no more requests */
- }
-
- virtio_blk_init_request(vblk, req);
- trace_virtio_blk_data_plane_process_request(s, req->elem.out_num,
- req->elem.in_num,
- req->elem.index);
-
- virtio_blk_handle_request(req, &mrb);
- }
-
- if (mrb.num_reqs) {
- virtio_blk_submit_multireq(s->conf->conf.blk, &mrb);
+ bits &= bits - 1; /* clear right-most bit */
}
-
- if (likely(!vring_more_avail(s->vdev, &s->vring))) { /* vring emptied */
- /* Re-enable guest->host notifies and stop processing the vring.
- * But if the guest has snuck in more descriptors, keep processing.
- */
- vring_enable_notification(s->vdev, &s->vring);
- if (!vring_more_avail(s->vdev, &s->vring)) {
- break;
- }
- } else { /* fatal error */
- break;
- }
- }
- blk_io_unplug(s->conf->conf.blk);
-}
-
-static void data_plane_set_up_op_blockers(VirtIOBlockDataPlane *s)
-{
- assert(!s->blocker);
- error_setg(&s->blocker, "block device is in use by data plane");
- blk_op_block_all(s->conf->conf.blk, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_RESIZE, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_DRIVE_DEL, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_BACKUP_SOURCE, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_CHANGE, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_COMMIT_SOURCE, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_COMMIT_TARGET, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_EJECT, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT,
- s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_INTERNAL_SNAPSHOT,
- s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_INTERNAL_SNAPSHOT_DELETE,
- s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_MIRROR_SOURCE, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_STREAM, s->blocker);
- blk_op_unblock(s->conf->conf.blk, BLOCK_OP_TYPE_REPLACE, s->blocker);
-}
-
-static void data_plane_remove_op_blockers(VirtIOBlockDataPlane *s)
-{
- if (s->blocker) {
- blk_op_unblock_all(s->conf->conf.blk, s->blocker);
- error_free(s->blocker);
- s->blocker = NULL;
}
}
-static void data_plane_blk_insert_notifier(Notifier *n, void *data)
-{
- VirtIOBlockDataPlane *s = container_of(n, VirtIOBlockDataPlane,
- insert_notifier);
- assert(s->conf->conf.blk == data);
- data_plane_set_up_op_blockers(s);
-}
-
-static void data_plane_blk_remove_notifier(Notifier *n, void *data)
-{
- VirtIOBlockDataPlane *s = container_of(n, VirtIOBlockDataPlane,
- remove_notifier);
- assert(s->conf->conf.blk == data);
- data_plane_remove_op_blockers(s);
-}
-
/* Context: QEMU global mutex held */
-void virtio_blk_data_plane_create(VirtIODevice *vdev, VirtIOBlkConf *conf,
+bool virtio_blk_data_plane_create(VirtIODevice *vdev, VirtIOBlkConf *conf,
VirtIOBlockDataPlane **dataplane,
Error **errp)
{
*dataplane = NULL;
- if (!conf->iothread) {
- return;
- }
+ if (conf->iothread) {
+ if (!k->set_guest_notifiers || !k->ioeventfd_assign) {
+ error_setg(errp,
+ "device is incompatible with iothread "
+ "(transport does not support notifiers)");
+ return false;
+ }
+ if (!virtio_device_ioeventfd_enabled(vdev)) {
+ error_setg(errp, "ioeventfd is required for iothread");
+ return false;
+ }
- /* Don't try if transport does not support notifiers. */
- if (!k->set_guest_notifiers || !k->set_host_notifier) {
- error_setg(errp,
- "device is incompatible with dataplane "
- "(transport does not support notifiers)");
- return;
+ /* If dataplane is (re-)enabled while the guest is running there could
+ * be block jobs that can conflict.
+ */
+ if (blk_op_is_blocked(conf->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) {
+ error_prepend(errp, "cannot start virtio-blk dataplane: ");
+ return false;
+ }
}
-
- /* If dataplane is (re-)enabled while the guest is running there could be
- * block jobs that can conflict.
- */
- if (blk_op_is_blocked(conf->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) {
- error_prepend(errp, "cannot start dataplane thread: ");
- return;
+ /* Don't try if transport does not support notifiers. */
+ if (!virtio_device_ioeventfd_enabled(vdev)) {
+ return false;
}
s = g_new0(VirtIOBlockDataPlane, 1);
if (conf->iothread) {
s->iothread = conf->iothread;
object_ref(OBJECT(s->iothread));
+ s->ctx = iothread_get_aio_context(s->iothread);
+ } else {
+ s->ctx = qemu_get_aio_context();
}
- s->ctx = iothread_get_aio_context(s->iothread);
s->bh = aio_bh_new(s->ctx, notify_guest_bh, s);
-
- s->insert_notifier.notify = data_plane_blk_insert_notifier;
- s->remove_notifier.notify = data_plane_blk_remove_notifier;
- blk_add_insert_bs_notifier(conf->conf.blk, &s->insert_notifier);
- blk_add_remove_bs_notifier(conf->conf.blk, &s->remove_notifier);
-
- data_plane_set_up_op_blockers(s);
+ s->batch_notify_vqs = bitmap_new(conf->num_queues);
*dataplane = s;
+
+ return true;
}
/* Context: QEMU global mutex held */
void virtio_blk_data_plane_destroy(VirtIOBlockDataPlane *s)
{
+ VirtIOBlock *vblk;
+
if (!s) {
return;
}
- virtio_blk_data_plane_stop(s);
- data_plane_remove_op_blockers(s);
- notifier_remove(&s->insert_notifier);
- notifier_remove(&s->remove_notifier);
+ vblk = VIRTIO_BLK(s->vdev);
+ assert(!vblk->dataplane_started);
+ g_free(s->batch_notify_vqs);
qemu_bh_delete(s->bh);
- object_unref(OBJECT(s->iothread));
+ if (s->iothread) {
+ object_unref(OBJECT(s->iothread));
+ }
g_free(s);
}
+static bool virtio_blk_data_plane_handle_output(VirtIODevice *vdev,
+ VirtQueue *vq)
+{
+ VirtIOBlock *s = (VirtIOBlock *)vdev;
+
+ assert(s->dataplane);
+ assert(s->dataplane_started);
+
+ return virtio_blk_handle_vq(s, vq);
+}
+
/* Context: QEMU global mutex held */
-void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s)
+int virtio_blk_data_plane_start(VirtIODevice *vdev)
{
- BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s->vdev)));
+ VirtIOBlock *vblk = VIRTIO_BLK(vdev);
+ VirtIOBlockDataPlane *s = vblk->dataplane;
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vblk)));
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
- VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
- VirtQueue *vq;
+ unsigned i;
+ unsigned nvqs = s->conf->num_queues;
+ Error *local_err = NULL;
int r;
if (vblk->dataplane_started || s->starting) {
- return;
+ return 0;
}
s->starting = true;
- vq = virtio_get_queue(s->vdev, 0);
- if (!vring_setup(&s->vring, s->vdev, 0)) {
- goto fail_vring;
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
+ s->batch_notifications = true;
+ } else {
+ s->batch_notifications = false;
}
/* Set up guest notifier (irq) */
- r = k->set_guest_notifiers(qbus->parent, 1, true);
+ r = k->set_guest_notifiers(qbus->parent, nvqs, true);
if (r != 0) {
- fprintf(stderr, "virtio-blk failed to set guest notifier (%d), "
- "ensure -enable-kvm is set\n", r);
+ error_report("virtio-blk failed to set guest notifier (%d), "
+ "ensure -accel kvm is set.", r);
goto fail_guest_notifiers;
}
- s->guest_notifier = virtio_queue_get_guest_notifier(vq);
/* Set up virtqueue notify */
- r = k->set_host_notifier(qbus->parent, 0, true);
- if (r != 0) {
- fprintf(stderr, "virtio-blk failed to set host notifier (%d)\n", r);
- goto fail_host_notifier;
+ for (i = 0; i < nvqs; i++) {
+ r = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, true);
+ if (r != 0) {
+ fprintf(stderr, "virtio-blk failed to set host notifier (%d)\n", r);
+ while (i--) {
+ virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
+ virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
+ }
+ goto fail_guest_notifiers;
+ }
}
- s->host_notifier = *virtio_queue_get_host_notifier(vq);
-
- s->saved_complete_request = vblk->complete_request;
- vblk->complete_request = complete_request_vring;
s->starting = false;
vblk->dataplane_started = true;
trace_virtio_blk_data_plane_start(s);
- blk_set_aio_context(s->conf->conf.blk, s->ctx);
+ r = blk_set_aio_context(s->conf->conf.blk, s->ctx, &local_err);
+ if (r < 0) {
+ error_report_err(local_err);
+ goto fail_guest_notifiers;
+ }
/* Kick right away to begin processing requests already in vring */
- event_notifier_set(virtio_queue_get_host_notifier(vq));
+ for (i = 0; i < nvqs; i++) {
+ VirtQueue *vq = virtio_get_queue(s->vdev, i);
+
+ event_notifier_set(virtio_queue_get_host_notifier(vq));
+ }
/* Get this show started by hooking up our callbacks */
aio_context_acquire(s->ctx);
- aio_set_event_notifier(s->ctx, &s->host_notifier, true,
- handle_notify);
+ for (i = 0; i < nvqs; i++) {
+ VirtQueue *vq = virtio_get_queue(s->vdev, i);
+
+ virtio_queue_aio_set_host_notifier_handler(vq, s->ctx,
+ virtio_blk_data_plane_handle_output);
+ }
aio_context_release(s->ctx);
- return;
+ return 0;
- fail_host_notifier:
- k->set_guest_notifiers(qbus->parent, 1, false);
fail_guest_notifiers:
- vring_teardown(&s->vring, s->vdev, 0);
- fail_vring:
- s->disabled = true;
+ vblk->dataplane_disabled = true;
s->starting = false;
vblk->dataplane_started = true;
+ return -ENOSYS;
+}
+
+/* Stop notifications for new requests from guest.
+ *
+ * Context: BH in IOThread
+ */
+static void virtio_blk_data_plane_stop_bh(void *opaque)
+{
+ VirtIOBlockDataPlane *s = opaque;
+ unsigned i;
+
+ for (i = 0; i < s->conf->num_queues; i++) {
+ VirtQueue *vq = virtio_get_queue(s->vdev, i);
+
+ virtio_queue_aio_set_host_notifier_handler(vq, s->ctx, NULL);
+ }
}
/* Context: QEMU global mutex held */
-void virtio_blk_data_plane_stop(VirtIOBlockDataPlane *s)
+void virtio_blk_data_plane_stop(VirtIODevice *vdev)
{
- BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s->vdev)));
+ VirtIOBlock *vblk = VIRTIO_BLK(vdev);
+ VirtIOBlockDataPlane *s = vblk->dataplane;
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vblk));
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
- VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
+ unsigned i;
+ unsigned nvqs = s->conf->num_queues;
if (!vblk->dataplane_started || s->stopping) {
return;
}
/* Better luck next time. */
- if (s->disabled) {
- s->disabled = false;
+ if (vblk->dataplane_disabled) {
+ vblk->dataplane_disabled = false;
vblk->dataplane_started = false;
return;
}
s->stopping = true;
- vblk->complete_request = s->saved_complete_request;
trace_virtio_blk_data_plane_stop(s);
aio_context_acquire(s->ctx);
+ aio_wait_bh_oneshot(s->ctx, virtio_blk_data_plane_stop_bh, s);
- /* Stop notifications for new requests from guest */
- aio_set_event_notifier(s->ctx, &s->host_notifier, true, NULL);
-
- /* Drain and switch bs back to the QEMU main loop */
- blk_set_aio_context(s->conf->conf.blk, qemu_get_aio_context());
+ /* Drain and try to switch bs back to the QEMU main loop. If other users
+ * keep the BlockBackend in the iothread, that's ok */
+ blk_set_aio_context(s->conf->conf.blk, qemu_get_aio_context(), NULL);
aio_context_release(s->ctx);
- /* Sync vring state back to virtqueue so that non-dataplane request
- * processing can continue when we disable the host notifier below.
- */
- vring_teardown(&s->vring, s->vdev, 0);
-
- k->set_host_notifier(qbus->parent, 0, false);
+ for (i = 0; i < nvqs; i++) {
+ virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
+ virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
+ }
/* Clean up guest notifier (irq) */
- k->set_guest_notifiers(qbus->parent, 1, false);
+ k->set_guest_notifiers(qbus->parent, nvqs, false);
vblk->dataplane_started = false;
s->stopping = false;