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>
This commit is contained in:
parent
1f3aba377d
commit
6798e245a3
@ -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);
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user