[S390] dasd deadlock after state change pending interrupt.
The dasd_device_from_cdev function is called from interrupt context to get the struct dasd_device associated with a ccw device. The driver_data of the ccw device points to the dasd_devmap structure which contains the pointer to the dasd_device structure. The lock that protects the dasd_devmap structure is acquire with out irqsave. To prevent the deadlock in dasd_device_from_cdev if it is called from interrupt context the dependency to the dasd_devmap structure needs to be removed. Let the driver_data of the ccw device point to the dasd_device structure directly and use the ccw device lock to protect the access. Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
parent
47addc84b4
commit
a00bfd7147
@ -893,7 +893,7 @@ dasd_handle_killed_request(struct ccw_device *cdev, unsigned long intparm)
|
|||||||
|
|
||||||
device = (struct dasd_device *) cqr->device;
|
device = (struct dasd_device *) cqr->device;
|
||||||
if (device == NULL ||
|
if (device == NULL ||
|
||||||
device != dasd_device_from_cdev(cdev) ||
|
device != dasd_device_from_cdev_locked(cdev) ||
|
||||||
strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) {
|
strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) {
|
||||||
MESSAGE(KERN_DEBUG, "invalid device in request: bus_id %s",
|
MESSAGE(KERN_DEBUG, "invalid device in request: bus_id %s",
|
||||||
cdev->dev.bus_id);
|
cdev->dev.bus_id);
|
||||||
@ -970,7 +970,7 @@ dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
|
|||||||
/* first of all check for state change pending interrupt */
|
/* first of all check for state change pending interrupt */
|
||||||
mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP;
|
mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP;
|
||||||
if ((irb->scsw.dstat & mask) == mask) {
|
if ((irb->scsw.dstat & mask) == mask) {
|
||||||
device = dasd_device_from_cdev(cdev);
|
device = dasd_device_from_cdev_locked(cdev);
|
||||||
if (!IS_ERR(device)) {
|
if (!IS_ERR(device)) {
|
||||||
dasd_handle_state_change_pending(device);
|
dasd_handle_state_change_pending(device);
|
||||||
dasd_put_device(device);
|
dasd_put_device(device);
|
||||||
|
@ -523,17 +523,17 @@ dasd_create_device(struct ccw_device *cdev)
|
|||||||
{
|
{
|
||||||
struct dasd_devmap *devmap;
|
struct dasd_devmap *devmap;
|
||||||
struct dasd_device *device;
|
struct dasd_device *device;
|
||||||
|
unsigned long flags;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
devmap = dasd_devmap_from_cdev(cdev);
|
devmap = dasd_devmap_from_cdev(cdev);
|
||||||
if (IS_ERR(devmap))
|
if (IS_ERR(devmap))
|
||||||
return (void *) devmap;
|
return (void *) devmap;
|
||||||
cdev->dev.driver_data = devmap;
|
|
||||||
|
|
||||||
device = dasd_alloc_device();
|
device = dasd_alloc_device();
|
||||||
if (IS_ERR(device))
|
if (IS_ERR(device))
|
||||||
return device;
|
return device;
|
||||||
atomic_set(&device->ref_count, 2);
|
atomic_set(&device->ref_count, 3);
|
||||||
|
|
||||||
spin_lock(&dasd_devmap_lock);
|
spin_lock(&dasd_devmap_lock);
|
||||||
if (!devmap->device) {
|
if (!devmap->device) {
|
||||||
@ -552,6 +552,11 @@ dasd_create_device(struct ccw_device *cdev)
|
|||||||
dasd_free_device(device);
|
dasd_free_device(device);
|
||||||
return ERR_PTR(rc);
|
return ERR_PTR(rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
|
||||||
|
cdev->dev.driver_data = device;
|
||||||
|
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
|
||||||
|
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,6 +574,7 @@ dasd_delete_device(struct dasd_device *device)
|
|||||||
{
|
{
|
||||||
struct ccw_device *cdev;
|
struct ccw_device *cdev;
|
||||||
struct dasd_devmap *devmap;
|
struct dasd_devmap *devmap;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
/* First remove device pointer from devmap. */
|
/* First remove device pointer from devmap. */
|
||||||
devmap = dasd_find_busid(device->cdev->dev.bus_id);
|
devmap = dasd_find_busid(device->cdev->dev.bus_id);
|
||||||
@ -582,9 +588,16 @@ dasd_delete_device(struct dasd_device *device)
|
|||||||
devmap->device = NULL;
|
devmap->device = NULL;
|
||||||
spin_unlock(&dasd_devmap_lock);
|
spin_unlock(&dasd_devmap_lock);
|
||||||
|
|
||||||
/* Drop ref_count by 2, one for the devmap reference and
|
/* Disconnect dasd_device structure from ccw_device structure. */
|
||||||
* one for the passed reference. */
|
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
|
||||||
atomic_sub(2, &device->ref_count);
|
device->cdev->dev.driver_data = NULL;
|
||||||
|
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drop ref_count by 3, one for the devmap reference, one for
|
||||||
|
* the cdev reference and one for the passed reference.
|
||||||
|
*/
|
||||||
|
atomic_sub(3, &device->ref_count);
|
||||||
|
|
||||||
/* Wait for reference counter to drop to zero. */
|
/* Wait for reference counter to drop to zero. */
|
||||||
wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0);
|
wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0);
|
||||||
@ -593,9 +606,6 @@ dasd_delete_device(struct dasd_device *device)
|
|||||||
cdev = device->cdev;
|
cdev = device->cdev;
|
||||||
device->cdev = NULL;
|
device->cdev = NULL;
|
||||||
|
|
||||||
/* Disconnect dasd_devmap structure from ccw_device structure. */
|
|
||||||
cdev->dev.driver_data = NULL;
|
|
||||||
|
|
||||||
/* Put ccw_device structure. */
|
/* Put ccw_device structure. */
|
||||||
put_device(&cdev->dev);
|
put_device(&cdev->dev);
|
||||||
|
|
||||||
@ -613,23 +623,34 @@ dasd_put_device_wake(struct dasd_device *device)
|
|||||||
wake_up(&dasd_delete_wq);
|
wake_up(&dasd_delete_wq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return dasd_device structure associated with cdev.
|
||||||
|
* This function needs to be called with the ccw device
|
||||||
|
* lock held. It can be used from interrupt context.
|
||||||
|
*/
|
||||||
|
struct dasd_device *
|
||||||
|
dasd_device_from_cdev_locked(struct ccw_device *cdev)
|
||||||
|
{
|
||||||
|
struct dasd_device *device = cdev->dev.driver_data;
|
||||||
|
|
||||||
|
if (!device)
|
||||||
|
return ERR_PTR(-ENODEV);
|
||||||
|
dasd_get_device(device);
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return dasd_device structure associated with cdev.
|
* Return dasd_device structure associated with cdev.
|
||||||
*/
|
*/
|
||||||
struct dasd_device *
|
struct dasd_device *
|
||||||
dasd_device_from_cdev(struct ccw_device *cdev)
|
dasd_device_from_cdev(struct ccw_device *cdev)
|
||||||
{
|
{
|
||||||
struct dasd_devmap *devmap;
|
|
||||||
struct dasd_device *device;
|
struct dasd_device *device;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
device = ERR_PTR(-ENODEV);
|
spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
|
||||||
spin_lock(&dasd_devmap_lock);
|
device = dasd_device_from_cdev_locked(cdev);
|
||||||
devmap = cdev->dev.driver_data;
|
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
|
||||||
if (devmap && devmap->device) {
|
|
||||||
device = devmap->device;
|
|
||||||
dasd_get_device(device);
|
|
||||||
}
|
|
||||||
spin_unlock(&dasd_devmap_lock);
|
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -730,16 +751,17 @@ static ssize_t
|
|||||||
dasd_discipline_show(struct device *dev, struct device_attribute *attr,
|
dasd_discipline_show(struct device *dev, struct device_attribute *attr,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
struct dasd_devmap *devmap;
|
struct dasd_device *device;
|
||||||
char *dname;
|
ssize_t len;
|
||||||
|
|
||||||
spin_lock(&dasd_devmap_lock);
|
device = dasd_device_from_cdev(to_ccwdev(dev));
|
||||||
dname = "none";
|
if (!IS_ERR(device) && device->discipline) {
|
||||||
devmap = dev->driver_data;
|
len = snprintf(buf, PAGE_SIZE, "%s\n",
|
||||||
if (devmap && devmap->device && devmap->device->discipline)
|
device->discipline->name);
|
||||||
dname = devmap->device->discipline->name;
|
dasd_put_device(device);
|
||||||
spin_unlock(&dasd_devmap_lock);
|
} else
|
||||||
return snprintf(buf, PAGE_SIZE, "%s\n", dname);
|
len = snprintf(buf, PAGE_SIZE, "none\n");
|
||||||
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEVICE_ATTR(discipline, 0444, dasd_discipline_show, NULL);
|
static DEVICE_ATTR(discipline, 0444, dasd_discipline_show, NULL);
|
||||||
|
@ -534,6 +534,7 @@ int dasd_add_sysfs_files(struct ccw_device *);
|
|||||||
void dasd_remove_sysfs_files(struct ccw_device *);
|
void dasd_remove_sysfs_files(struct ccw_device *);
|
||||||
|
|
||||||
struct dasd_device *dasd_device_from_cdev(struct ccw_device *);
|
struct dasd_device *dasd_device_from_cdev(struct ccw_device *);
|
||||||
|
struct dasd_device *dasd_device_from_cdev_locked(struct ccw_device *);
|
||||||
struct dasd_device *dasd_device_from_devindex(int);
|
struct dasd_device *dasd_device_from_devindex(int);
|
||||||
|
|
||||||
int dasd_parse(void);
|
int dasd_parse(void);
|
||||||
|
Loading…
Reference in New Issue
Block a user