aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCornelia Huck <cornelia.huck@de.ibm.com>2016-06-10 11:04:09 +0200
committerMichael S. Tsirkin <mst@redhat.com>2016-06-24 08:47:35 +0300
commit6798e245a3c7e6f34f66a548a108cfa8eba79b7d (patch)
tree0ac9e4552c3e1eaf438ac6add0b9521f74f2d323
parent1f3aba377d2a531453f018c70de2580a142c74c9 (diff)
downloadqemu-6798e245a3c7e6f34f66a548a108cfa8eba79b7d.zip
qemu-6798e245a3c7e6f34f66a548a108cfa8eba79b7d.tar.gz
qemu-6798e245a3c7e6f34f66a548a108cfa8eba79b7d.tar.bz2
virtio-bus: common ioeventfd infrastructure
Introduce a set of ioeventfd callbacks on the virtio-bus level that can be implemented by the individual transports. At the virtio-bus level, do common handling for host notifiers (which is actually most of it). Two things of note: - When setting the host notifier, we only switch from/to the generic ioeventfd handler. This fixes a latent bug where we had no ioeventfd assigned for a certain window. - We always iterate over all possible virtio queues, even though ccw (currently) has a lower limit. It does not really matter here. Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com> Reviewed-by: Fam Zheng <famz@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
-rw-r--r--hw/virtio/virtio-bus.c132
-rw-r--r--include/hw/virtio/virtio-bus.h30
2 files changed, 162 insertions, 0 deletions
diff --git a/hw/virtio/virtio-bus.c b/hw/virtio/virtio-bus.c
index 574f0e2..1313760 100644
--- a/hw/virtio/virtio-bus.c
+++ b/hw/virtio/virtio-bus.c
@@ -146,6 +146,138 @@ void virtio_bus_set_vdev_config(VirtioBusState *bus, uint8_t *config)
}
}
+/*
+ * This function handles both assigning the ioeventfd handler and
+ * registering it with the kernel.
+ * assign: register/deregister ioeventfd with the kernel
+ * set_handler: use the generic ioeventfd handler
+ */
+static int set_host_notifier_internal(DeviceState *proxy, VirtioBusState *bus,
+ int n, bool assign, bool set_handler)
+{
+ VirtIODevice *vdev = virtio_bus_get_device(bus);
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(bus);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+ EventNotifier *notifier = virtio_queue_get_host_notifier(vq);
+ int r = 0;
+
+ if (assign) {
+ r = event_notifier_init(notifier, 1);
+ if (r < 0) {
+ error_report("%s: unable to init event notifier: %d", __func__, r);
+ return r;
+ }
+ virtio_queue_set_host_notifier_fd_handler(vq, true, set_handler);
+ r = k->ioeventfd_assign(proxy, notifier, n, assign);
+ if (r < 0) {
+ error_report("%s: unable to assign ioeventfd: %d", __func__, r);
+ virtio_queue_set_host_notifier_fd_handler(vq, false, false);
+ event_notifier_cleanup(notifier);
+ return r;
+ }
+ } else {
+ virtio_queue_set_host_notifier_fd_handler(vq, false, false);
+ k->ioeventfd_assign(proxy, notifier, n, assign);
+ event_notifier_cleanup(notifier);
+ }
+ return r;
+}
+
+void virtio_bus_start_ioeventfd(VirtioBusState *bus)
+{
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(bus);
+ DeviceState *proxy = DEVICE(BUS(bus)->parent);
+ VirtIODevice *vdev;
+ int n, r;
+
+ if (!k->ioeventfd_started || k->ioeventfd_started(proxy)) {
+ return;
+ }
+ if (k->ioeventfd_disabled(proxy)) {
+ return;
+ }
+ vdev = virtio_bus_get_device(bus);
+ for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+ r = set_host_notifier_internal(proxy, bus, n, true, true);
+ if (r < 0) {
+ goto assign_error;
+ }
+ }
+ k->ioeventfd_set_started(proxy, true, false);
+ return;
+
+assign_error:
+ while (--n >= 0) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+
+ r = set_host_notifier_internal(proxy, bus, n, false, false);
+ assert(r >= 0);
+ }
+ k->ioeventfd_set_started(proxy, false, true);
+ error_report("%s: failed. Fallback to userspace (slower).", __func__);
+}
+
+void virtio_bus_stop_ioeventfd(VirtioBusState *bus)
+{
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(bus);
+ DeviceState *proxy = DEVICE(BUS(bus)->parent);
+ VirtIODevice *vdev;
+ int n, r;
+
+ if (!k->ioeventfd_started || !k->ioeventfd_started(proxy)) {
+ return;
+ }
+ vdev = virtio_bus_get_device(bus);
+ for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
+ if (!virtio_queue_get_num(vdev, n)) {
+ continue;
+ }
+ r = set_host_notifier_internal(proxy, bus, n, false, false);
+ assert(r >= 0);
+ }
+ k->ioeventfd_set_started(proxy, false, false);
+}
+
+/*
+ * This function switches from/to the generic ioeventfd handler.
+ * assign==false means 'use generic ioeventfd handler'.
+ */
+int virtio_bus_set_host_notifier(VirtioBusState *bus, int n, bool assign)
+{
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(bus);
+ DeviceState *proxy = DEVICE(BUS(bus)->parent);
+ VirtIODevice *vdev = virtio_bus_get_device(bus);
+ VirtQueue *vq = virtio_get_queue(vdev, n);
+
+ if (!k->ioeventfd_started) {
+ return -ENOSYS;
+ }
+ if (assign) {
+ /*
+ * Stop using the generic ioeventfd, we are doing eventfd handling
+ * ourselves below
+ */
+ k->ioeventfd_set_disabled(proxy, true);
+ }
+ /*
+ * Just switch the handler, don't deassign the ioeventfd.
+ * Otherwise, there's a window where we don't have an
+ * ioeventfd and we may end up with a notification where
+ * we don't expect one.
+ */
+ virtio_queue_set_host_notifier_fd_handler(vq, assign, !assign);
+ if (!assign) {
+ /* Use generic ioeventfd handler again. */
+ k->ioeventfd_set_disabled(proxy, false);
+ }
+ return 0;
+}
+
static char *virtio_bus_get_dev_path(DeviceState *dev)
{
BusState *bus = qdev_get_parent_bus(dev);
diff --git a/include/hw/virtio/virtio-bus.h b/include/hw/virtio/virtio-bus.h
index 3f2c136..9637f80 100644
--- a/include/hw/virtio/virtio-bus.h
+++ b/include/hw/virtio/virtio-bus.h
@@ -71,6 +71,29 @@ typedef struct VirtioBusClass {
void (*device_unplugged)(DeviceState *d);
int (*query_nvectors)(DeviceState *d);
/*
+ * ioeventfd handling: if the transport implements ioeventfd_started,
+ * it must implement the other ioeventfd callbacks as well
+ */
+ /* Returns true if the ioeventfd has been started for the device. */
+ bool (*ioeventfd_started)(DeviceState *d);
+ /*
+ * Sets the 'ioeventfd started' state after the ioeventfd has been
+ * started/stopped for the device. err signifies whether an error
+ * had occurred.
+ */
+ void (*ioeventfd_set_started)(DeviceState *d, bool started, bool err);
+ /* Returns true if the ioeventfd has been disabled for the device. */
+ bool (*ioeventfd_disabled)(DeviceState *d);
+ /* Sets the 'ioeventfd disabled' state for the device. */
+ void (*ioeventfd_set_disabled)(DeviceState *d, bool disabled);
+ /*
+ * Assigns/deassigns the ioeventfd backing for the transport on
+ * the device for queue number n. Returns an error value on
+ * failure.
+ */
+ int (*ioeventfd_assign)(DeviceState *d, EventNotifier *notifier,
+ int n, bool assign);
+ /*
* Does the transport have variable vring alignment?
* (ie can it ever call virtio_queue_set_align()?)
* Note that changing this will break migration for this transport.
@@ -111,4 +134,11 @@ static inline VirtIODevice *virtio_bus_get_device(VirtioBusState *bus)
return (VirtIODevice *)qdev;
}
+/* Start the ioeventfd. */
+void virtio_bus_start_ioeventfd(VirtioBusState *bus);
+/* Stop the ioeventfd. */
+void virtio_bus_stop_ioeventfd(VirtioBusState *bus);
+/* Switch from/to the generic ioeventfd handler */
+int virtio_bus_set_host_notifier(VirtioBusState *bus, int n, bool assign);
+
#endif /* VIRTIO_BUS_H */