virtio_ccw: introduce device_lost in virtio_ccw_device

When a device is lost, the common I/O layer calls the notification
handler with CIO_GONE: In that event, flag device_lost as true.

In case the device had been flagged as lost when the remove/offline callbacks
are called, call the new virtio_break_device() function prior to invoking
device_unregister(). This avoids hangs of I/O triggered via the device
unregistration callbacks.

Signed-off-by: Heinz Graalfs <graalfs@linux.vnet.ibm.com>
Reviewed-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Heinz Graalfs 2014-04-28 11:24:05 +09:30 committed by Rusty Russell
parent e2dcdfe95c
commit e75279c4fb

View file

@ -27,6 +27,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/kvm_para.h> #include <linux/kvm_para.h>
#include <linux/notifier.h>
#include <asm/setup.h> #include <asm/setup.h>
#include <asm/irq.h> #include <asm/irq.h>
#include <asm/cio.h> #include <asm/cio.h>
@ -62,6 +63,7 @@ struct virtio_ccw_device {
struct vq_config_block *config_block; struct vq_config_block *config_block;
bool is_thinint; bool is_thinint;
bool going_away; bool going_away;
bool device_lost;
void *airq_info; void *airq_info;
}; };
@ -1010,11 +1012,14 @@ static void virtio_ccw_remove(struct ccw_device *cdev)
unsigned long flags; unsigned long flags;
struct virtio_ccw_device *vcdev = virtio_grab_drvdata(cdev); struct virtio_ccw_device *vcdev = virtio_grab_drvdata(cdev);
if (vcdev && cdev->online) if (vcdev && cdev->online) {
if (vcdev->device_lost)
virtio_break_device(&vcdev->vdev);
unregister_virtio_device(&vcdev->vdev); unregister_virtio_device(&vcdev->vdev);
spin_lock_irqsave(get_ccwdev_lock(cdev), flags); spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
dev_set_drvdata(&cdev->dev, NULL); dev_set_drvdata(&cdev->dev, NULL);
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
}
cdev->handler = NULL; cdev->handler = NULL;
} }
@ -1023,12 +1028,14 @@ static int virtio_ccw_offline(struct ccw_device *cdev)
unsigned long flags; unsigned long flags;
struct virtio_ccw_device *vcdev = virtio_grab_drvdata(cdev); struct virtio_ccw_device *vcdev = virtio_grab_drvdata(cdev);
if (vcdev) { if (!vcdev)
unregister_virtio_device(&vcdev->vdev); return 0;
spin_lock_irqsave(get_ccwdev_lock(cdev), flags); if (vcdev->device_lost)
dev_set_drvdata(&cdev->dev, NULL); virtio_break_device(&vcdev->vdev);
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); 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; return 0;
} }
@ -1096,8 +1103,26 @@ out_free:
static int virtio_ccw_cio_notify(struct ccw_device *cdev, int event) static int virtio_ccw_cio_notify(struct ccw_device *cdev, int event)
{ {
/* TODO: Check whether we need special handling here. */ int rc;
return 0; struct virtio_ccw_device *vcdev = dev_get_drvdata(&cdev->dev);
/*
* Make sure vcdev is set
* i.e. set_offline/remove callback not already running
*/
if (!vcdev)
return NOTIFY_DONE;
switch (event) {
case CIO_GONE:
vcdev->device_lost = true;
rc = NOTIFY_DONE;
break;
default:
rc = NOTIFY_DONE;
break;
}
return rc;
} }
static struct ccw_device_id virtio_ids[] = { static struct ccw_device_id virtio_ids[] = {