virtio_ccw: fix hang in set offline processing
authorHeinz Graalfs <graalfs@linux.vnet.ibm.com>
Wed, 5 Mar 2014 14:23:54 +0000 (15:23 +0100)
committerCornelia Huck <cornelia.huck@de.ibm.com>
Thu, 6 Mar 2014 09:22:40 +0000 (10:22 +0100)
During set offline processing virtio_grab_drvdata() incorrectly
calls dev_set_drvdata() to remove the virtio_ccw_device from the
parent ccw_device's driver data. This is wrong and ends up in a
hang during virtio_ccw_reset(), as the interrupt handler still
has need of the virtio_ccw_device.

A new field 'going_away' is introduced in struct virtio_ccw_device
to control the usage of the ccw_device's driver data pointer in
virtio_grab_drvdata().

Signed-off-by: Heinz Graalfs <graalfs@linux.vnet.ibm.com>
Reviewed-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
drivers/s390/kvm/virtio_ccw.c

index 6a2b5fdcd552c63c4c62bf0762aee3115385844b..1e1fc671f89a4a431b4a04f59fea16e302ad3a30 100644 (file)
@@ -61,6 +61,7 @@ struct virtio_ccw_device {
        unsigned long indicators2;
        struct vq_config_block *config_block;
        bool is_thinint;
+       bool going_away;
        void *airq_info;
 };
 
@@ -995,30 +996,39 @@ static struct virtio_ccw_device *virtio_grab_drvdata(struct ccw_device *cdev)
 
        spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
        vcdev = dev_get_drvdata(&cdev->dev);
-       if (!vcdev) {
+       if (!vcdev || vcdev->going_away) {
                spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
                return NULL;
        }
-       dev_set_drvdata(&cdev->dev, NULL);
+       vcdev->going_away = true;
        spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
        return vcdev;
 }
 
 static void virtio_ccw_remove(struct ccw_device *cdev)
 {
+       unsigned long flags;
        struct virtio_ccw_device *vcdev = virtio_grab_drvdata(cdev);
 
        if (vcdev && cdev->online)
                unregister_virtio_device(&vcdev->vdev);
+       spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+       dev_set_drvdata(&cdev->dev, NULL);
+       spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
        cdev->handler = NULL;
 }
 
 static int virtio_ccw_offline(struct ccw_device *cdev)
 {
+       unsigned long flags;
        struct virtio_ccw_device *vcdev = virtio_grab_drvdata(cdev);
 
-       if (vcdev)
+       if (vcdev) {
                unregister_virtio_device(&vcdev->vdev);
+               spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+               dev_set_drvdata(&cdev->dev, NULL);
+               spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+       }
        return 0;
 }