Commit 6798e245 authored by Cornelia Huck's avatar Cornelia Huck Committed by Michael S. Tsirkin
Browse files

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: default avatarCornelia Huck <cornelia.huck@de.ibm.com>
Reviewed-by: default avatarFam Zheng <famz@redhat.com>
Reviewed-by: default avatarStefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: default avatarMichael S. Tsirkin <mst@redhat.com>
Signed-off-by: default avatarMichael S. Tsirkin <mst@redhat.com>
parent 1f3aba37
Loading
Loading
Loading
Loading
+132 −0
Original line number Diff line number Diff line
@@ -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);
+30 −0
Original line number Diff line number Diff line
@@ -70,6 +70,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()?)
@@ -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 */