ebb6ff25cd
When 'system_reset' is called, the main loop clear the memory region cache before the BH has a chance to execute. Later when the deferred function is called, some assumptions that were made when scheduling them are no longer true when they actually execute. This is what happens using a virtio-blk device (fresh RHEL7.8 install): $ (sleep 12.3; echo system_reset; sleep 12.3; echo system_reset; sleep 1; echo q) \ | qemu-system-x86_64 -m 4G -smp 8 -boot menu=on \ -device virtio-blk-pci,id=image1,drive=drive_image1 \ -drive file=/var/lib/libvirt/images/rhel78.qcow2,if=none,id=drive_image1,format=qcow2,cache=none \ -device virtio-net-pci,netdev=net0,id=nic0,mac=52:54:00:c4:e7:84 \ -netdev tap,id=net0,script=/bin/true,downscript=/bin/true,vhost=on \ -monitor stdio -serial null -nographic (qemu) system_reset (qemu) system_reset (qemu) qemu-system-x86_64: hw/virtio/virtio.c:225: vring_get_region_caches: Assertion `caches != NULL' failed. Aborted (gdb) bt Thread 1 (Thread 0x7f109c17b680 (LWP 10939)): #0 0x00005604083296d1 in vring_get_region_caches (vq=0x56040a24bdd0) at hw/virtio/virtio.c:227 #1 0x000056040832972b in vring_avail_flags (vq=0x56040a24bdd0) at hw/virtio/virtio.c:235 #2 0x000056040832d13d in virtio_should_notify (vdev=0x56040a240630, vq=0x56040a24bdd0) at hw/virtio/virtio.c:1648 #3 0x000056040832d1f8 in virtio_notify_irqfd (vdev=0x56040a240630, vq=0x56040a24bdd0) at hw/virtio/virtio.c:1662 #4 0x00005604082d213d in notify_guest_bh (opaque=0x56040a243ec0) at hw/block/dataplane/virtio-blk.c:75 #5 0x000056040883dc35 in aio_bh_call (bh=0x56040a243f10) at util/async.c:90 #6 0x000056040883dccd in aio_bh_poll (ctx=0x560409161980) at util/async.c:118 #7 0x0000560408842af7 in aio_dispatch (ctx=0x560409161980) at util/aio-posix.c:460 #8 0x000056040883e068 in aio_ctx_dispatch (source=0x560409161980, callback=0x0, user_data=0x0) at util/async.c:261 #9 0x00007f10a8fca06d in g_main_context_dispatch () at /lib64/libglib-2.0.so.0 #10 0x0000560408841445 in glib_pollfds_poll () at util/main-loop.c:215 #11 0x00005604088414bf in os_host_main_loop_wait (timeout=0) at util/main-loop.c:238 #12 0x00005604088415c4 in main_loop_wait (nonblocking=0) at util/main-loop.c:514 #13 0x0000560408416b1e in main_loop () at vl.c:1923 #14 0x000056040841e0e8 in main (argc=20, argv=0x7ffc2c3f9c58, envp=0x7ffc2c3f9d00) at vl.c:4578 Fix this by cancelling the BH when the virtio dataplane is stopped. [This is version of the patch was modified as discussed with Philippe on the mailing list thread. --Stefan] Reported-by: Yihuang Yu <yihyu@redhat.com> Suggested-by: Stefan Hajnoczi <stefanha@redhat.com> Fixes: https://bugs.launchpad.net/qemu/+bug/1839428 Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com> Message-Id: <20190816171503.24761-1-philmd@redhat.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
310 lines
8.6 KiB
C
310 lines
8.6 KiB
C
/*
|
|
* Dedicated thread for virtio-blk I/O processing
|
|
*
|
|
* Copyright 2012 IBM, Corp.
|
|
* Copyright 2012 Red Hat, Inc. and/or its affiliates
|
|
*
|
|
* Authors:
|
|
* Stefan Hajnoczi <stefanha@redhat.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*
|
|
*/
|
|
|
|
#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/virtio-blk.h"
|
|
#include "virtio-blk.h"
|
|
#include "block/aio.h"
|
|
#include "hw/virtio/virtio-bus.h"
|
|
#include "qom/object_interfaces.h"
|
|
|
|
struct VirtIOBlockDataPlane {
|
|
bool starting;
|
|
bool stopping;
|
|
|
|
VirtIOBlkConf *conf;
|
|
VirtIODevice *vdev;
|
|
QEMUBH *bh; /* bh for guest notification */
|
|
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
|
|
* (because you don't own the file descriptor or handle; you just
|
|
* use it).
|
|
*/
|
|
IOThread *iothread;
|
|
AioContext *ctx;
|
|
};
|
|
|
|
/* Raise an interrupt to signal guest, if necessary */
|
|
void virtio_blk_data_plane_notify(VirtIOBlockDataPlane *s, VirtQueue *vq)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
memcpy(bitmap, s->batch_notify_vqs, sizeof(bitmap));
|
|
memset(s->batch_notify_vqs, 0, sizeof(bitmap));
|
|
|
|
for (j = 0; j < nvqs; j += BITS_PER_LONG) {
|
|
unsigned long bits = bitmap[j];
|
|
|
|
while (bits != 0) {
|
|
unsigned i = j + ctzl(bits);
|
|
VirtQueue *vq = virtio_get_queue(s->vdev, i);
|
|
|
|
virtio_notify_irqfd(s->vdev, vq);
|
|
|
|
bits &= bits - 1; /* clear right-most bit */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Context: QEMU global mutex held */
|
|
bool virtio_blk_data_plane_create(VirtIODevice *vdev, VirtIOBlkConf *conf,
|
|
VirtIOBlockDataPlane **dataplane,
|
|
Error **errp)
|
|
{
|
|
VirtIOBlockDataPlane *s;
|
|
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
|
|
*dataplane = NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
/* Don't try if transport does not support notifiers. */
|
|
if (!virtio_device_ioeventfd_enabled(vdev)) {
|
|
return false;
|
|
}
|
|
|
|
s = g_new0(VirtIOBlockDataPlane, 1);
|
|
s->vdev = vdev;
|
|
s->conf = conf;
|
|
|
|
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->bh = aio_bh_new(s->ctx, notify_guest_bh, 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;
|
|
}
|
|
|
|
vblk = VIRTIO_BLK(s->vdev);
|
|
assert(!vblk->dataplane_started);
|
|
g_free(s->batch_notify_vqs);
|
|
qemu_bh_delete(s->bh);
|
|
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 */
|
|
int virtio_blk_data_plane_start(VirtIODevice *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);
|
|
unsigned i;
|
|
unsigned nvqs = s->conf->num_queues;
|
|
Error *local_err = NULL;
|
|
int r;
|
|
|
|
if (vblk->dataplane_started || s->starting) {
|
|
return 0;
|
|
}
|
|
|
|
s->starting = true;
|
|
|
|
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, nvqs, true);
|
|
if (r != 0) {
|
|
error_report("virtio-blk failed to set guest notifier (%d), "
|
|
"ensure -accel kvm is set.", r);
|
|
goto fail_guest_notifiers;
|
|
}
|
|
|
|
/* Set up virtqueue notify */
|
|
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->starting = false;
|
|
vblk->dataplane_started = true;
|
|
trace_virtio_blk_data_plane_start(s);
|
|
|
|
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 */
|
|
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);
|
|
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 0;
|
|
|
|
fail_guest_notifiers:
|
|
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(VirtIODevice *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);
|
|
unsigned i;
|
|
unsigned nvqs = s->conf->num_queues;
|
|
|
|
if (!vblk->dataplane_started || s->stopping) {
|
|
return;
|
|
}
|
|
|
|
/* Better luck next time. */
|
|
if (vblk->dataplane_disabled) {
|
|
vblk->dataplane_disabled = false;
|
|
vblk->dataplane_started = false;
|
|
return;
|
|
}
|
|
s->stopping = true;
|
|
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);
|
|
|
|
/* 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);
|
|
|
|
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);
|
|
}
|
|
|
|
qemu_bh_cancel(s->bh);
|
|
notify_guest_bh(s); /* final chance to notify guest */
|
|
|
|
/* Clean up guest notifier (irq) */
|
|
k->set_guest_notifiers(qbus->parent, nvqs, false);
|
|
|
|
vblk->dataplane_started = false;
|
|
s->stopping = false;
|
|
}
|