4adea8042f
There's no use to constantly trying to enable dataplane if we failed to set up guest or host notifiers, so fence it off in that case. We'll try again if the device is reinitialized. Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
307 lines
9.3 KiB
C
307 lines
9.3 KiB
C
/*
|
|
* Virtio SCSI dataplane
|
|
*
|
|
* Copyright Red Hat, Inc. 2014
|
|
*
|
|
* Authors:
|
|
* Fam Zheng <famz@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 "hw/virtio/virtio-scsi.h"
|
|
#include "qemu/error-report.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include <hw/scsi/scsi.h>
|
|
#include <block/scsi.h>
|
|
#include <hw/virtio/virtio-bus.h>
|
|
#include "hw/virtio/virtio-access.h"
|
|
#include "stdio.h"
|
|
|
|
/* Context: QEMU global mutex held */
|
|
void virtio_scsi_set_iothread(VirtIOSCSI *s, IOThread *iothread)
|
|
{
|
|
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
|
|
|
|
assert(!s->ctx);
|
|
s->ctx = iothread_get_aio_context(vs->conf.iothread);
|
|
|
|
/* Don't try if transport does not support notifiers. */
|
|
if (!k->set_guest_notifiers || !k->set_host_notifier) {
|
|
fprintf(stderr, "virtio-scsi: Failed to set iothread "
|
|
"(transport does not support notifiers)");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static VirtIOSCSIVring *virtio_scsi_vring_init(VirtIOSCSI *s,
|
|
VirtQueue *vq,
|
|
EventNotifierHandler *handler,
|
|
int n)
|
|
{
|
|
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
VirtIOSCSIVring *r = g_slice_new(VirtIOSCSIVring);
|
|
int rc;
|
|
|
|
/* Set up virtqueue notify */
|
|
rc = k->set_host_notifier(qbus->parent, n, true);
|
|
if (rc != 0) {
|
|
fprintf(stderr, "virtio-scsi: Failed to set host notifier (%d)\n",
|
|
rc);
|
|
s->dataplane_fenced = true;
|
|
return NULL;
|
|
}
|
|
r->host_notifier = *virtio_queue_get_host_notifier(vq);
|
|
r->guest_notifier = *virtio_queue_get_guest_notifier(vq);
|
|
aio_set_event_notifier(s->ctx, &r->host_notifier, handler);
|
|
|
|
r->parent = s;
|
|
|
|
if (!vring_setup(&r->vring, VIRTIO_DEVICE(s), n)) {
|
|
fprintf(stderr, "virtio-scsi: VRing setup failed\n");
|
|
goto fail_vring;
|
|
}
|
|
return r;
|
|
|
|
fail_vring:
|
|
aio_set_event_notifier(s->ctx, &r->host_notifier, NULL);
|
|
k->set_host_notifier(qbus->parent, n, false);
|
|
g_slice_free(VirtIOSCSIVring, r);
|
|
return NULL;
|
|
}
|
|
|
|
VirtIOSCSIReq *virtio_scsi_pop_req_vring(VirtIOSCSI *s,
|
|
VirtIOSCSIVring *vring)
|
|
{
|
|
VirtIOSCSIReq *req = virtio_scsi_init_req(s, NULL);
|
|
int r;
|
|
|
|
req->vring = vring;
|
|
r = vring_pop((VirtIODevice *)s, &vring->vring, &req->elem);
|
|
if (r < 0) {
|
|
virtio_scsi_free_req(req);
|
|
req = NULL;
|
|
}
|
|
return req;
|
|
}
|
|
|
|
void virtio_scsi_vring_push_notify(VirtIOSCSIReq *req)
|
|
{
|
|
vring_push(&req->vring->vring, &req->elem,
|
|
req->qsgl.size + req->resp_iov.size);
|
|
event_notifier_set(&req->vring->guest_notifier);
|
|
}
|
|
|
|
static void virtio_scsi_iothread_handle_ctrl(EventNotifier *notifier)
|
|
{
|
|
VirtIOSCSIVring *vring = container_of(notifier,
|
|
VirtIOSCSIVring, host_notifier);
|
|
VirtIOSCSI *s = VIRTIO_SCSI(vring->parent);
|
|
VirtIOSCSIReq *req;
|
|
|
|
event_notifier_test_and_clear(notifier);
|
|
while ((req = virtio_scsi_pop_req_vring(s, vring))) {
|
|
virtio_scsi_handle_ctrl_req(s, req);
|
|
}
|
|
}
|
|
|
|
static void virtio_scsi_iothread_handle_event(EventNotifier *notifier)
|
|
{
|
|
VirtIOSCSIVring *vring = container_of(notifier,
|
|
VirtIOSCSIVring, host_notifier);
|
|
VirtIOSCSI *s = vring->parent;
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(s);
|
|
|
|
event_notifier_test_and_clear(notifier);
|
|
|
|
if (!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
|
|
return;
|
|
}
|
|
|
|
if (s->events_dropped) {
|
|
virtio_scsi_push_event(s, NULL, VIRTIO_SCSI_T_NO_EVENT, 0);
|
|
}
|
|
}
|
|
|
|
static void virtio_scsi_iothread_handle_cmd(EventNotifier *notifier)
|
|
{
|
|
VirtIOSCSIVring *vring = container_of(notifier,
|
|
VirtIOSCSIVring, host_notifier);
|
|
VirtIOSCSI *s = (VirtIOSCSI *)vring->parent;
|
|
VirtIOSCSIReq *req, *next;
|
|
QTAILQ_HEAD(, VirtIOSCSIReq) reqs = QTAILQ_HEAD_INITIALIZER(reqs);
|
|
|
|
event_notifier_test_and_clear(notifier);
|
|
while ((req = virtio_scsi_pop_req_vring(s, vring))) {
|
|
if (virtio_scsi_handle_cmd_req_prepare(s, req)) {
|
|
QTAILQ_INSERT_TAIL(&reqs, req, next);
|
|
}
|
|
}
|
|
|
|
QTAILQ_FOREACH_SAFE(req, &reqs, next, next) {
|
|
virtio_scsi_handle_cmd_req_submit(s, req);
|
|
}
|
|
}
|
|
|
|
/* assumes s->ctx held */
|
|
static void virtio_scsi_clear_aio(VirtIOSCSI *s)
|
|
{
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
|
|
int i;
|
|
|
|
if (s->ctrl_vring) {
|
|
aio_set_event_notifier(s->ctx, &s->ctrl_vring->host_notifier, NULL);
|
|
}
|
|
if (s->event_vring) {
|
|
aio_set_event_notifier(s->ctx, &s->event_vring->host_notifier, NULL);
|
|
}
|
|
if (s->cmd_vrings) {
|
|
for (i = 0; i < vs->conf.num_queues && s->cmd_vrings[i]; i++) {
|
|
aio_set_event_notifier(s->ctx, &s->cmd_vrings[i]->host_notifier, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void virtio_scsi_vring_teardown(VirtIOSCSI *s)
|
|
{
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(s);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
|
|
int i;
|
|
|
|
if (s->ctrl_vring) {
|
|
vring_teardown(&s->ctrl_vring->vring, vdev, 0);
|
|
}
|
|
if (s->event_vring) {
|
|
vring_teardown(&s->event_vring->vring, vdev, 1);
|
|
}
|
|
if (s->cmd_vrings) {
|
|
for (i = 0; i < vs->conf.num_queues && s->cmd_vrings[i]; i++) {
|
|
vring_teardown(&s->cmd_vrings[i]->vring, vdev, 2 + i);
|
|
}
|
|
free(s->cmd_vrings);
|
|
s->cmd_vrings = NULL;
|
|
}
|
|
}
|
|
|
|
/* Context: QEMU global mutex held */
|
|
void virtio_scsi_dataplane_start(VirtIOSCSI *s)
|
|
{
|
|
int i;
|
|
int rc;
|
|
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
|
|
|
|
if (s->dataplane_started ||
|
|
s->dataplane_starting ||
|
|
s->dataplane_fenced ||
|
|
s->ctx != iothread_get_aio_context(vs->conf.iothread)) {
|
|
return;
|
|
}
|
|
|
|
s->dataplane_starting = true;
|
|
|
|
assert(!s->blocker);
|
|
error_setg(&s->blocker, "block device is in use by data plane");
|
|
/* Set up guest notifier (irq) */
|
|
rc = k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, true);
|
|
if (rc != 0) {
|
|
fprintf(stderr, "virtio-scsi: Failed to set guest notifiers (%d), "
|
|
"ensure -enable-kvm is set\n", rc);
|
|
s->dataplane_fenced = true;
|
|
goto fail_guest_notifiers;
|
|
}
|
|
|
|
aio_context_acquire(s->ctx);
|
|
s->ctrl_vring = virtio_scsi_vring_init(s, vs->ctrl_vq,
|
|
virtio_scsi_iothread_handle_ctrl,
|
|
0);
|
|
if (!s->ctrl_vring) {
|
|
goto fail_vrings;
|
|
}
|
|
s->event_vring = virtio_scsi_vring_init(s, vs->event_vq,
|
|
virtio_scsi_iothread_handle_event,
|
|
1);
|
|
if (!s->event_vring) {
|
|
goto fail_vrings;
|
|
}
|
|
s->cmd_vrings = g_malloc0(sizeof(VirtIOSCSIVring) * vs->conf.num_queues);
|
|
for (i = 0; i < vs->conf.num_queues; i++) {
|
|
s->cmd_vrings[i] =
|
|
virtio_scsi_vring_init(s, vs->cmd_vqs[i],
|
|
virtio_scsi_iothread_handle_cmd,
|
|
i + 2);
|
|
if (!s->cmd_vrings[i]) {
|
|
goto fail_vrings;
|
|
}
|
|
}
|
|
|
|
aio_context_release(s->ctx);
|
|
s->dataplane_starting = false;
|
|
s->dataplane_started = true;
|
|
|
|
fail_vrings:
|
|
virtio_scsi_clear_aio(s);
|
|
aio_context_release(s->ctx);
|
|
virtio_scsi_vring_teardown(s);
|
|
for (i = 0; i < vs->conf.num_queues + 2; i++) {
|
|
k->set_host_notifier(qbus->parent, i, false);
|
|
}
|
|
k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
|
|
fail_guest_notifiers:
|
|
s->dataplane_starting = false;
|
|
}
|
|
|
|
/* Context: QEMU global mutex held */
|
|
void virtio_scsi_dataplane_stop(VirtIOSCSI *s)
|
|
{
|
|
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
|
|
int i;
|
|
|
|
/* Better luck next time. */
|
|
if (s->dataplane_fenced) {
|
|
s->dataplane_fenced = false;
|
|
return;
|
|
}
|
|
if (!s->dataplane_started || s->dataplane_stopping) {
|
|
return;
|
|
}
|
|
error_free(s->blocker);
|
|
s->blocker = NULL;
|
|
s->dataplane_stopping = true;
|
|
assert(s->ctx == iothread_get_aio_context(vs->conf.iothread));
|
|
|
|
aio_context_acquire(s->ctx);
|
|
|
|
aio_set_event_notifier(s->ctx, &s->ctrl_vring->host_notifier, NULL);
|
|
aio_set_event_notifier(s->ctx, &s->event_vring->host_notifier, NULL);
|
|
for (i = 0; i < vs->conf.num_queues; i++) {
|
|
aio_set_event_notifier(s->ctx, &s->cmd_vrings[i]->host_notifier, NULL);
|
|
}
|
|
|
|
blk_drain_all(); /* ensure there are no in-flight requests */
|
|
|
|
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.
|
|
*/
|
|
virtio_scsi_vring_teardown(s);
|
|
|
|
for (i = 0; i < vs->conf.num_queues + 2; i++) {
|
|
k->set_host_notifier(qbus->parent, i, false);
|
|
}
|
|
|
|
/* Clean up guest notifier (irq) */
|
|
k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
|
|
s->dataplane_stopping = false;
|
|
s->dataplane_started = false;
|
|
}
|