diff options
author | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2006-09-20 09:59:05 -0400 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2006-09-20 09:59:05 -0400 |
commit | a00bfd7147c0c5c04a59f7adcb0e6d8948b90a6e (patch) | |
tree | ffe3eb5ede49bf5a14fe31f3270d917fc4bc9c7a /drivers | |
parent | 47addc84b450fd5e391ab118e178645cb0bbd89d (diff) |
[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>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/s390/block/dasd.c | 4 | ||||
-rw-r--r-- | drivers/s390/block/dasd_devmap.c | 74 | ||||
-rw-r--r-- | drivers/s390/block/dasd_int.h | 1 |
3 files changed, 51 insertions, 28 deletions
diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c index 25c1ef6dfd44..3cd87f85f702 100644 --- a/drivers/s390/block/dasd.c +++ b/drivers/s390/block/dasd.c | |||
@@ -893,7 +893,7 @@ dasd_handle_killed_request(struct ccw_device *cdev, unsigned long intparm) | |||
893 | 893 | ||
894 | device = (struct dasd_device *) cqr->device; | 894 | device = (struct dasd_device *) cqr->device; |
895 | if (device == NULL || | 895 | if (device == NULL || |
896 | device != dasd_device_from_cdev(cdev) || | 896 | device != dasd_device_from_cdev_locked(cdev) || |
897 | strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) { | 897 | strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) { |
898 | MESSAGE(KERN_DEBUG, "invalid device in request: bus_id %s", | 898 | MESSAGE(KERN_DEBUG, "invalid device in request: bus_id %s", |
899 | cdev->dev.bus_id); | 899 | cdev->dev.bus_id); |
@@ -970,7 +970,7 @@ dasd_int_handler(struct ccw_device *cdev, unsigned long intparm, | |||
970 | /* first of all check for state change pending interrupt */ | 970 | /* first of all check for state change pending interrupt */ |
971 | mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP; | 971 | mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP; |
972 | if ((irb->scsw.dstat & mask) == mask) { | 972 | if ((irb->scsw.dstat & mask) == mask) { |
973 | device = dasd_device_from_cdev(cdev); | 973 | device = dasd_device_from_cdev_locked(cdev); |
974 | if (!IS_ERR(device)) { | 974 | if (!IS_ERR(device)) { |
975 | dasd_handle_state_change_pending(device); | 975 | dasd_handle_state_change_pending(device); |
976 | dasd_put_device(device); | 976 | dasd_put_device(device); |
diff --git a/drivers/s390/block/dasd_devmap.c b/drivers/s390/block/dasd_devmap.c index 9af02c79ce8a..80cf0999465a 100644 --- a/drivers/s390/block/dasd_devmap.c +++ b/drivers/s390/block/dasd_devmap.c | |||
@@ -523,17 +523,17 @@ dasd_create_device(struct ccw_device *cdev) | |||
523 | { | 523 | { |
524 | struct dasd_devmap *devmap; | 524 | struct dasd_devmap *devmap; |
525 | struct dasd_device *device; | 525 | struct dasd_device *device; |
526 | unsigned long flags; | ||
526 | int rc; | 527 | int rc; |
527 | 528 | ||
528 | devmap = dasd_devmap_from_cdev(cdev); | 529 | devmap = dasd_devmap_from_cdev(cdev); |
529 | if (IS_ERR(devmap)) | 530 | if (IS_ERR(devmap)) |
530 | return (void *) devmap; | 531 | return (void *) devmap; |
531 | cdev->dev.driver_data = devmap; | ||
532 | 532 | ||
533 | device = dasd_alloc_device(); | 533 | device = dasd_alloc_device(); |
534 | if (IS_ERR(device)) | 534 | if (IS_ERR(device)) |
535 | return device; | 535 | return device; |
536 | atomic_set(&device->ref_count, 2); | 536 | atomic_set(&device->ref_count, 3); |
537 | 537 | ||
538 | spin_lock(&dasd_devmap_lock); | 538 | spin_lock(&dasd_devmap_lock); |
539 | if (!devmap->device) { | 539 | if (!devmap->device) { |
@@ -552,6 +552,11 @@ dasd_create_device(struct ccw_device *cdev) | |||
552 | dasd_free_device(device); | 552 | dasd_free_device(device); |
553 | return ERR_PTR(rc); | 553 | return ERR_PTR(rc); |
554 | } | 554 | } |
555 | |||
556 | spin_lock_irqsave(get_ccwdev_lock(cdev), flags); | ||
557 | cdev->dev.driver_data = device; | ||
558 | spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); | ||
559 | |||
555 | return device; | 560 | return device; |
556 | } | 561 | } |
557 | 562 | ||
@@ -569,6 +574,7 @@ dasd_delete_device(struct dasd_device *device) | |||
569 | { | 574 | { |
570 | struct ccw_device *cdev; | 575 | struct ccw_device *cdev; |
571 | struct dasd_devmap *devmap; | 576 | struct dasd_devmap *devmap; |
577 | unsigned long flags; | ||
572 | 578 | ||
573 | /* First remove device pointer from devmap. */ | 579 | /* First remove device pointer from devmap. */ |
574 | devmap = dasd_find_busid(device->cdev->dev.bus_id); | 580 | devmap = dasd_find_busid(device->cdev->dev.bus_id); |
@@ -582,9 +588,16 @@ dasd_delete_device(struct dasd_device *device) | |||
582 | devmap->device = NULL; | 588 | devmap->device = NULL; |
583 | spin_unlock(&dasd_devmap_lock); | 589 | spin_unlock(&dasd_devmap_lock); |
584 | 590 | ||
585 | /* Drop ref_count by 2, one for the devmap reference and | 591 | /* Disconnect dasd_device structure from ccw_device structure. */ |
586 | * one for the passed reference. */ | 592 | spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); |
587 | atomic_sub(2, &device->ref_count); | 593 | device->cdev->dev.driver_data = NULL; |
594 | spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); | ||
595 | |||
596 | /* | ||
597 | * Drop ref_count by 3, one for the devmap reference, one for | ||
598 | * the cdev reference and one for the passed reference. | ||
599 | */ | ||
600 | atomic_sub(3, &device->ref_count); | ||
588 | 601 | ||
589 | /* Wait for reference counter to drop to zero. */ | 602 | /* Wait for reference counter to drop to zero. */ |
590 | wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0); | 603 | wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0); |
@@ -593,9 +606,6 @@ dasd_delete_device(struct dasd_device *device) | |||
593 | cdev = device->cdev; | 606 | cdev = device->cdev; |
594 | device->cdev = NULL; | 607 | device->cdev = NULL; |
595 | 608 | ||
596 | /* Disconnect dasd_devmap structure from ccw_device structure. */ | ||
597 | cdev->dev.driver_data = NULL; | ||
598 | |||
599 | /* Put ccw_device structure. */ | 609 | /* Put ccw_device structure. */ |
600 | put_device(&cdev->dev); | 610 | put_device(&cdev->dev); |
601 | 611 | ||
@@ -615,21 +625,32 @@ dasd_put_device_wake(struct dasd_device *device) | |||
615 | 625 | ||
616 | /* | 626 | /* |
617 | * Return dasd_device structure associated with cdev. | 627 | * Return dasd_device structure associated with cdev. |
628 | * This function needs to be called with the ccw device | ||
629 | * lock held. It can be used from interrupt context. | ||
630 | */ | ||
631 | struct dasd_device * | ||
632 | dasd_device_from_cdev_locked(struct ccw_device *cdev) | ||
633 | { | ||
634 | struct dasd_device *device = cdev->dev.driver_data; | ||
635 | |||
636 | if (!device) | ||
637 | return ERR_PTR(-ENODEV); | ||
638 | dasd_get_device(device); | ||
639 | return device; | ||
640 | } | ||
641 | |||
642 | /* | ||
643 | * Return dasd_device structure associated with cdev. | ||
618 | */ | 644 | */ |
619 | struct dasd_device * | 645 | struct dasd_device * |
620 | dasd_device_from_cdev(struct ccw_device *cdev) | 646 | dasd_device_from_cdev(struct ccw_device *cdev) |
621 | { | 647 | { |
622 | struct dasd_devmap *devmap; | ||
623 | struct dasd_device *device; | 648 | struct dasd_device *device; |
649 | unsigned long flags; | ||
624 | 650 | ||
625 | device = ERR_PTR(-ENODEV); | 651 | spin_lock_irqsave(get_ccwdev_lock(cdev), flags); |
626 | spin_lock(&dasd_devmap_lock); | 652 | device = dasd_device_from_cdev_locked(cdev); |
627 | devmap = cdev->dev.driver_data; | 653 | spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); |
628 | if (devmap && devmap->device) { | ||
629 | device = devmap->device; | ||
630 | dasd_get_device(device); | ||
631 | } | ||
632 | spin_unlock(&dasd_devmap_lock); | ||
633 | return device; | 654 | return device; |
634 | } | 655 | } |
635 | 656 | ||
@@ -730,16 +751,17 @@ static ssize_t | |||
730 | dasd_discipline_show(struct device *dev, struct device_attribute *attr, | 751 | dasd_discipline_show(struct device *dev, struct device_attribute *attr, |
731 | char *buf) | 752 | char *buf) |
732 | { | 753 | { |
733 | struct dasd_devmap *devmap; | 754 | struct dasd_device *device; |
734 | char *dname; | 755 | ssize_t len; |
735 | 756 | ||
736 | spin_lock(&dasd_devmap_lock); | 757 | device = dasd_device_from_cdev(to_ccwdev(dev)); |
737 | dname = "none"; | 758 | if (!IS_ERR(device) && device->discipline) { |
738 | devmap = dev->driver_data; | 759 | len = snprintf(buf, PAGE_SIZE, "%s\n", |
739 | if (devmap && devmap->device && devmap->device->discipline) | 760 | device->discipline->name); |
740 | dname = devmap->device->discipline->name; | 761 | dasd_put_device(device); |
741 | spin_unlock(&dasd_devmap_lock); | 762 | } else |
742 | return snprintf(buf, PAGE_SIZE, "%s\n", dname); | 763 | len = snprintf(buf, PAGE_SIZE, "none\n"); |
764 | return len; | ||
743 | } | 765 | } |
744 | 766 | ||
745 | static DEVICE_ATTR(discipline, 0444, dasd_discipline_show, NULL); | 767 | static DEVICE_ATTR(discipline, 0444, dasd_discipline_show, NULL); |
diff --git a/drivers/s390/block/dasd_int.h b/drivers/s390/block/dasd_int.h index 3ccf06d28ba1..9f52004f6fc2 100644 --- a/drivers/s390/block/dasd_int.h +++ b/drivers/s390/block/dasd_int.h | |||
@@ -534,6 +534,7 @@ int dasd_add_sysfs_files(struct ccw_device *); | |||
534 | void dasd_remove_sysfs_files(struct ccw_device *); | 534 | void dasd_remove_sysfs_files(struct ccw_device *); |
535 | 535 | ||
536 | struct dasd_device *dasd_device_from_cdev(struct ccw_device *); | 536 | struct dasd_device *dasd_device_from_cdev(struct ccw_device *); |
537 | struct dasd_device *dasd_device_from_cdev_locked(struct ccw_device *); | ||
537 | struct dasd_device *dasd_device_from_devindex(int); | 538 | struct dasd_device *dasd_device_from_devindex(int); |
538 | 539 | ||
539 | int dasd_parse(void); | 540 | int dasd_parse(void); |