virtio: defer config changed notifications

Defer config changed notifications that arrive during
probe/scan/freeze/restore.

This will allow drivers to set DRIVER_OK earlier, without worrying about
racing with config change interrupts.

This change will also benefit old hypervisors (before 2009)
that send interrupts without checking DRIVER_OK: previously,
the callback could race with driver-specific initialization.

This will also help simplify drivers.

Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Reviewed-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> (cosmetic changes)
This commit is contained in:
Michael S. Tsirkin 2014-10-15 10:21:55 +10:30 committed by Rusty Russell
parent c6716bae52
commit 22b7050a02
2 changed files with 55 additions and 9 deletions

View File

@ -117,6 +117,43 @@ void virtio_check_driver_offered_feature(const struct virtio_device *vdev,
} }
EXPORT_SYMBOL_GPL(virtio_check_driver_offered_feature); EXPORT_SYMBOL_GPL(virtio_check_driver_offered_feature);
static void __virtio_config_changed(struct virtio_device *dev)
{
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
if (!dev->config_enabled)
dev->config_change_pending = true;
else if (drv && drv->config_changed)
drv->config_changed(dev);
}
void virtio_config_changed(struct virtio_device *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->config_lock, flags);
__virtio_config_changed(dev);
spin_unlock_irqrestore(&dev->config_lock, flags);
}
EXPORT_SYMBOL_GPL(virtio_config_changed);
static void virtio_config_disable(struct virtio_device *dev)
{
spin_lock_irq(&dev->config_lock);
dev->config_enabled = false;
spin_unlock_irq(&dev->config_lock);
}
static void virtio_config_enable(struct virtio_device *dev)
{
spin_lock_irq(&dev->config_lock);
dev->config_enabled = true;
if (dev->config_change_pending)
__virtio_config_changed(dev);
dev->config_change_pending = false;
spin_unlock_irq(&dev->config_lock);
}
static int virtio_dev_probe(struct device *_d) static int virtio_dev_probe(struct device *_d)
{ {
int err, i; int err, i;
@ -153,6 +190,8 @@ static int virtio_dev_probe(struct device *_d)
add_status(dev, VIRTIO_CONFIG_S_DRIVER_OK); add_status(dev, VIRTIO_CONFIG_S_DRIVER_OK);
if (drv->scan) if (drv->scan)
drv->scan(dev); drv->scan(dev);
virtio_config_enable(dev);
} }
return err; return err;
@ -163,6 +202,8 @@ static int virtio_dev_remove(struct device *_d)
struct virtio_device *dev = dev_to_virtio(_d); struct virtio_device *dev = dev_to_virtio(_d);
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver); struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
virtio_config_disable(dev);
drv->remove(dev); drv->remove(dev);
/* Driver should have reset device. */ /* Driver should have reset device. */
@ -211,6 +252,10 @@ int register_virtio_device(struct virtio_device *dev)
dev->index = err; dev->index = err;
dev_set_name(&dev->dev, "virtio%u", dev->index); dev_set_name(&dev->dev, "virtio%u", dev->index);
spin_lock_init(&dev->config_lock);
dev->config_enabled = false;
dev->config_change_pending = false;
/* We always start by resetting the device, in case a previous /* We always start by resetting the device, in case a previous
* driver messed it up. This also tests that code path a little. */ * driver messed it up. This also tests that code path a little. */
dev->config->reset(dev); dev->config->reset(dev);
@ -239,20 +284,13 @@ void unregister_virtio_device(struct virtio_device *dev)
} }
EXPORT_SYMBOL_GPL(unregister_virtio_device); EXPORT_SYMBOL_GPL(unregister_virtio_device);
void virtio_config_changed(struct virtio_device *dev)
{
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
if (drv && drv->config_changed)
drv->config_changed(dev);
}
EXPORT_SYMBOL_GPL(virtio_config_changed);
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
int virtio_device_freeze(struct virtio_device *dev) int virtio_device_freeze(struct virtio_device *dev)
{ {
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver); struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
virtio_config_disable(dev);
dev->failed = dev->config->get_status(dev) & VIRTIO_CONFIG_S_FAILED; dev->failed = dev->config->get_status(dev) & VIRTIO_CONFIG_S_FAILED;
if (drv && drv->freeze) if (drv && drv->freeze)
@ -297,6 +335,8 @@ int virtio_device_restore(struct virtio_device *dev)
/* Finally, tell the device we're all set */ /* Finally, tell the device we're all set */
add_status(dev, VIRTIO_CONFIG_S_DRIVER_OK); add_status(dev, VIRTIO_CONFIG_S_DRIVER_OK);
virtio_config_enable(dev);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(virtio_device_restore); EXPORT_SYMBOL_GPL(virtio_device_restore);

View File

@ -79,6 +79,9 @@ bool virtqueue_is_broken(struct virtqueue *vq);
* virtio_device - representation of a device using virtio * virtio_device - representation of a device using virtio
* @index: unique position on the virtio bus * @index: unique position on the virtio bus
* @failed: saved value for CONFIG_S_FAILED bit (for restore) * @failed: saved value for CONFIG_S_FAILED bit (for restore)
* @config_enabled: configuration change reporting enabled
* @config_change_pending: configuration change reported while disabled
* @config_lock: protects configuration change reporting
* @dev: underlying device. * @dev: underlying device.
* @id: the device type identification (used to match it with a driver). * @id: the device type identification (used to match it with a driver).
* @config: the configuration ops for this device. * @config: the configuration ops for this device.
@ -90,6 +93,9 @@ bool virtqueue_is_broken(struct virtqueue *vq);
struct virtio_device { struct virtio_device {
int index; int index;
bool failed; bool failed;
bool config_enabled;
bool config_change_pending;
spinlock_t config_lock;
struct device dev; struct device dev;
struct virtio_device_id id; struct virtio_device_id id;
const struct virtio_config_ops *config; const struct virtio_config_ops *config;