aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/s390
diff options
context:
space:
mode:
authorMichael Holzheu <holzheu@de.ibm.com>2007-08-22 07:51:41 -0400
committerMartin Schwidefsky <schwidefsky@de.ibm.com>2007-08-22 07:51:48 -0400
commit8127a1f80a002d02a30909ddf6187faedf89e00a (patch)
tree754edbf66f6d27b25ac6816e986669cca14c504a /drivers/s390
parent0a87c5cfc0bb0c1bdcc1cc9fd82e4a1711fac512 (diff)
[S390] vmur: fix reference counting for vmur device structure
When a vmur device is removed due to a detach of the device, currently the ur device structure is freed. Unfortunately it can happen, that there is still a user of the device structure, when the character device is open during the detach process. To fix this, reference counting for the vmur structure is introduced. In addition to that, the online, offline, probe and remove functions are serialized now using a global mutex. Signed-off-by: Michael Holzheu <holzheu@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Diffstat (limited to 'drivers/s390')
-rw-r--r--drivers/s390/char/vmur.c218
-rw-r--r--drivers/s390/char/vmur.h1
2 files changed, 161 insertions, 58 deletions
diff --git a/drivers/s390/char/vmur.c b/drivers/s390/char/vmur.c
index 2d96c958df64..d70a6e65bf14 100644
--- a/drivers/s390/char/vmur.c
+++ b/drivers/s390/char/vmur.c
@@ -69,8 +69,26 @@ static struct ccw_driver ur_driver = {
69 .set_offline = ur_set_offline, 69 .set_offline = ur_set_offline,
70}; 70};
71 71
72static DEFINE_MUTEX(vmur_mutex);
73
72/* 74/*
73 * Allocation, freeing, getting and putting of urdev structures 75 * Allocation, freeing, getting and putting of urdev structures
76 *
77 * Each ur device (urd) contains a reference to its corresponding ccw device
78 * (cdev) using the urd->cdev pointer. Each ccw device has a reference to the
79 * ur device using the cdev->dev.driver_data pointer.
80 *
81 * urd references:
82 * - ur_probe gets a urd reference, ur_remove drops the reference
83 * (cdev->dev.driver_data)
84 * - ur_open gets a urd reference, ur_relase drops the reference
85 * (urf->urd)
86 *
87 * cdev references:
88 * - urdev_alloc get a cdev reference (urd->cdev)
89 * - urdev_free drops the cdev reference (urd->cdev)
90 *
91 * Setting and clearing of cdev->dev.driver_data is protected by the ccwdev lock
74 */ 92 */
75static struct urdev *urdev_alloc(struct ccw_device *cdev) 93static struct urdev *urdev_alloc(struct ccw_device *cdev)
76{ 94{
@@ -79,42 +97,61 @@ static struct urdev *urdev_alloc(struct ccw_device *cdev)
79 urd = kzalloc(sizeof(struct urdev), GFP_KERNEL); 97 urd = kzalloc(sizeof(struct urdev), GFP_KERNEL);
80 if (!urd) 98 if (!urd)
81 return NULL; 99 return NULL;
82 urd->cdev = cdev;
83 urd->reclen = cdev->id.driver_info; 100 urd->reclen = cdev->id.driver_info;
84 ccw_device_get_id(cdev, &urd->dev_id); 101 ccw_device_get_id(cdev, &urd->dev_id);
85 mutex_init(&urd->io_mutex); 102 mutex_init(&urd->io_mutex);
86 mutex_init(&urd->open_mutex); 103 mutex_init(&urd->open_mutex);
104 atomic_set(&urd->ref_count, 1);
105 urd->cdev = cdev;
106 get_device(&cdev->dev);
87 return urd; 107 return urd;
88} 108}
89 109
90static void urdev_free(struct urdev *urd) 110static void urdev_free(struct urdev *urd)
91{ 111{
112 TRACE("urdev_free: %p\n", urd);
113 if (urd->cdev)
114 put_device(&urd->cdev->dev);
92 kfree(urd); 115 kfree(urd);
93} 116}
94 117
95/* 118static void urdev_get(struct urdev *urd)
96 * This is how the character device driver gets a reference to a 119{
97 * ur device. When this call returns successfully, a reference has 120 atomic_inc(&urd->ref_count);
98 * been taken (by get_device) on the underlying kobject. The recipient 121}
99 * of this urdev pointer must eventually drop it with urdev_put(urd) 122
100 * which does the corresponding put_device(). 123static struct urdev *urdev_get_from_cdev(struct ccw_device *cdev)
101 */ 124{
125 struct urdev *urd;
126 unsigned long flags;
127
128 spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
129 urd = cdev->dev.driver_data;
130 if (urd)
131 urdev_get(urd);
132 spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
133 return urd;
134}
135
102static struct urdev *urdev_get_from_devno(u16 devno) 136static struct urdev *urdev_get_from_devno(u16 devno)
103{ 137{
104 char bus_id[16]; 138 char bus_id[16];
105 struct ccw_device *cdev; 139 struct ccw_device *cdev;
140 struct urdev *urd;
106 141
107 sprintf(bus_id, "0.0.%04x", devno); 142 sprintf(bus_id, "0.0.%04x", devno);
108 cdev = get_ccwdev_by_busid(&ur_driver, bus_id); 143 cdev = get_ccwdev_by_busid(&ur_driver, bus_id);
109 if (!cdev) 144 if (!cdev)
110 return NULL; 145 return NULL;
111 146 urd = urdev_get_from_cdev(cdev);
112 return cdev->dev.driver_data; 147 put_device(&cdev->dev);
148 return urd;
113} 149}
114 150
115static void urdev_put(struct urdev *urd) 151static void urdev_put(struct urdev *urd)
116{ 152{
117 put_device(&urd->cdev->dev); 153 if (atomic_dec_and_test(&urd->ref_count))
154 urdev_free(urd);
118} 155}
119 156
120/* 157/*
@@ -246,6 +283,7 @@ static void ur_int_handler(struct ccw_device *cdev, unsigned long intparm,
246 return; 283 return;
247 } 284 }
248 urd = cdev->dev.driver_data; 285 urd = cdev->dev.driver_data;
286 BUG_ON(!urd);
249 /* On special conditions irb is an error pointer */ 287 /* On special conditions irb is an error pointer */
250 if (IS_ERR(irb)) 288 if (IS_ERR(irb))
251 urd->io_request_rc = PTR_ERR(irb); 289 urd->io_request_rc = PTR_ERR(irb);
@@ -263,9 +301,15 @@ static void ur_int_handler(struct ccw_device *cdev, unsigned long intparm,
263static ssize_t ur_attr_reclen_show(struct device *dev, 301static ssize_t ur_attr_reclen_show(struct device *dev,
264 struct device_attribute *attr, char *buf) 302 struct device_attribute *attr, char *buf)
265{ 303{
266 struct urdev *urd = dev->driver_data; 304 struct urdev *urd;
305 int rc;
267 306
268 return sprintf(buf, "%zu\n", urd->reclen); 307 urd = urdev_get_from_cdev(to_ccwdev(dev));
308 if (!urd)
309 return -ENODEV;
310 rc = sprintf(buf, "%zu\n", urd->reclen);
311 urdev_put(urd);
312 return rc;
269} 313}
270 314
271static DEVICE_ATTR(reclen, 0444, ur_attr_reclen_show, NULL); 315static DEVICE_ATTR(reclen, 0444, ur_attr_reclen_show, NULL);
@@ -726,64 +770,63 @@ static struct file_operations ur_fops = {
726 770
727/* 771/*
728 * ccw_device infrastructure: 772 * ccw_device infrastructure:
729 * ur_probe gets its own ref to the device (i.e. get_device), 773 * ur_probe creates the struct urdev (with refcount = 1), the device
730 * creates the struct urdev, the device attributes, sets up 774 * attributes, sets up the interrupt handler and validates the virtual
731 * the interrupt handler and validates the virtual unit record device. 775 * unit record device.
732 * ur_remove removes the device attributes, frees the struct urdev 776 * ur_remove removes the device attributes and drops the reference to
733 * and drops (put_device) the ref to the device we got in ur_probe. 777 * struct urdev.
778 *
779 * ur_probe, ur_remove, ur_set_online and ur_set_offline are serialized
780 * by the vmur_mutex lock.
781 *
782 * urd->char_device is used as indication that the online function has
783 * been completed successfully.
734 */ 784 */
735static int ur_probe(struct ccw_device *cdev) 785static int ur_probe(struct ccw_device *cdev)
736{ 786{
737 struct urdev *urd; 787 struct urdev *urd;
738 int rc; 788 int rc;
739 789
740 TRACE("ur_probe: cdev=%p state=%d\n", cdev, *(int *) cdev->private); 790 TRACE("ur_probe: cdev=%p\n", cdev);
741
742 if (!get_device(&cdev->dev))
743 return -ENODEV;
744 791
792 mutex_lock(&vmur_mutex);
745 urd = urdev_alloc(cdev); 793 urd = urdev_alloc(cdev);
746 if (!urd) { 794 if (!urd) {
747 rc = -ENOMEM; 795 rc = -ENOMEM;
748 goto fail; 796 goto fail_unlock;
749 } 797 }
798
750 rc = ur_create_attributes(&cdev->dev); 799 rc = ur_create_attributes(&cdev->dev);
751 if (rc) { 800 if (rc) {
752 rc = -ENOMEM; 801 rc = -ENOMEM;
753 goto fail; 802 goto fail_urdev_put;
754 } 803 }
755 cdev->dev.driver_data = urd;
756 cdev->handler = ur_int_handler; 804 cdev->handler = ur_int_handler;
757 805
758 /* validate virtual unit record device */ 806 /* validate virtual unit record device */
759 urd->class = get_urd_class(urd); 807 urd->class = get_urd_class(urd);
760 if (urd->class < 0) { 808 if (urd->class < 0) {
761 rc = urd->class; 809 rc = urd->class;
762 goto fail; 810 goto fail_remove_attr;
763 } 811 }
764 if ((urd->class != DEV_CLASS_UR_I) && (urd->class != DEV_CLASS_UR_O)) { 812 if ((urd->class != DEV_CLASS_UR_I) && (urd->class != DEV_CLASS_UR_O)) {
765 rc = -ENOTSUPP; 813 rc = -ENOTSUPP;
766 goto fail; 814 goto fail_remove_attr;
767 } 815 }
816 spin_lock_irq(get_ccwdev_lock(cdev));
817 cdev->dev.driver_data = urd;
818 spin_unlock_irq(get_ccwdev_lock(cdev));
768 819
820 mutex_unlock(&vmur_mutex);
769 return 0; 821 return 0;
770 822
771fail: 823fail_remove_attr:
772 urdev_free(urd);
773 put_device(&cdev->dev);
774 return rc;
775}
776
777static void ur_remove(struct ccw_device *cdev)
778{
779 struct urdev *urd = cdev->dev.driver_data;
780
781 TRACE("ur_remove\n");
782 if (cdev->online)
783 ur_set_offline(cdev);
784 ur_remove_attributes(&cdev->dev); 824 ur_remove_attributes(&cdev->dev);
785 urdev_free(urd); 825fail_urdev_put:
786 put_device(&cdev->dev); 826 urdev_put(urd);
827fail_unlock:
828 mutex_unlock(&vmur_mutex);
829 return rc;
787} 830}
788 831
789static int ur_set_online(struct ccw_device *cdev) 832static int ur_set_online(struct ccw_device *cdev)
@@ -792,20 +835,29 @@ static int ur_set_online(struct ccw_device *cdev)
792 int minor, major, rc; 835 int minor, major, rc;
793 char node_id[16]; 836 char node_id[16];
794 837
795 TRACE("ur_set_online: cdev=%p state=%d\n", cdev, 838 TRACE("ur_set_online: cdev=%p\n", cdev);
796 *(int *) cdev->private);
797 839
798 if (!try_module_get(ur_driver.owner)) 840 mutex_lock(&vmur_mutex);
799 return -EINVAL; 841 urd = urdev_get_from_cdev(cdev);
842 if (!urd) {
843 /* ur_remove already deleted our urd */
844 rc = -ENODEV;
845 goto fail_unlock;
846 }
847
848 if (urd->char_device) {
849 /* Another ur_set_online was faster */
850 rc = -EBUSY;
851 goto fail_urdev_put;
852 }
800 853
801 urd = (struct urdev *) cdev->dev.driver_data;
802 minor = urd->dev_id.devno; 854 minor = urd->dev_id.devno;
803 major = MAJOR(ur_first_dev_maj_min); 855 major = MAJOR(ur_first_dev_maj_min);
804 856
805 urd->char_device = cdev_alloc(); 857 urd->char_device = cdev_alloc();
806 if (!urd->char_device) { 858 if (!urd->char_device) {
807 rc = -ENOMEM; 859 rc = -ENOMEM;
808 goto fail_module_put; 860 goto fail_urdev_put;
809 } 861 }
810 862
811 cdev_init(urd->char_device, &ur_fops); 863 cdev_init(urd->char_device, &ur_fops);
@@ -834,29 +886,79 @@ static int ur_set_online(struct ccw_device *cdev)
834 TRACE("ur_set_online: device_create rc=%d\n", rc); 886 TRACE("ur_set_online: device_create rc=%d\n", rc);
835 goto fail_free_cdev; 887 goto fail_free_cdev;
836 } 888 }
837 889 urdev_put(urd);
890 mutex_unlock(&vmur_mutex);
838 return 0; 891 return 0;
839 892
840fail_free_cdev: 893fail_free_cdev:
841 cdev_del(urd->char_device); 894 cdev_del(urd->char_device);
842fail_module_put: 895 urd->char_device = NULL;
843 module_put(ur_driver.owner); 896fail_urdev_put:
844 897 urdev_put(urd);
898fail_unlock:
899 mutex_unlock(&vmur_mutex);
845 return rc; 900 return rc;
846} 901}
847 902
848static int ur_set_offline(struct ccw_device *cdev) 903static int ur_set_offline_force(struct ccw_device *cdev, int force)
849{ 904{
850 struct urdev *urd; 905 struct urdev *urd;
906 int rc;
851 907
852 TRACE("ur_set_offline: cdev=%p cdev->private=%p state=%d\n", 908 TRACE("ur_set_offline: cdev=%p\n", cdev);
853 cdev, cdev->private, *(int *) cdev->private); 909 urd = urdev_get_from_cdev(cdev);
854 urd = (struct urdev *) cdev->dev.driver_data; 910 if (!urd)
911 /* ur_remove already deleted our urd */
912 return -ENODEV;
913 if (!urd->char_device) {
914 /* Another ur_set_offline was faster */
915 rc = -EBUSY;
916 goto fail_urdev_put;
917 }
918 if (!force && (atomic_read(&urd->ref_count) > 2)) {
919 /* There is still a user of urd (e.g. ur_open) */
920 TRACE("ur_set_offline: BUSY\n");
921 rc = -EBUSY;
922 goto fail_urdev_put;
923 }
855 device_destroy(vmur_class, urd->char_device->dev); 924 device_destroy(vmur_class, urd->char_device->dev);
856 cdev_del(urd->char_device); 925 cdev_del(urd->char_device);
857 module_put(ur_driver.owner); 926 urd->char_device = NULL;
927 rc = 0;
858 928
859 return 0; 929fail_urdev_put:
930 urdev_put(urd);
931 return rc;
932}
933
934static int ur_set_offline(struct ccw_device *cdev)
935{
936 int rc;
937
938 mutex_lock(&vmur_mutex);
939 rc = ur_set_offline_force(cdev, 0);
940 mutex_unlock(&vmur_mutex);
941 return rc;
942}
943
944static void ur_remove(struct ccw_device *cdev)
945{
946 unsigned long flags;
947
948 TRACE("ur_remove\n");
949
950 mutex_lock(&vmur_mutex);
951
952 if (cdev->online)
953 ur_set_offline_force(cdev, 1);
954 ur_remove_attributes(&cdev->dev);
955
956 spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
957 urdev_put(cdev->dev.driver_data);
958 cdev->dev.driver_data = NULL;
959 spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
960
961 mutex_unlock(&vmur_mutex);
860} 962}
861 963
862/* 964/*
diff --git a/drivers/s390/char/vmur.h b/drivers/s390/char/vmur.h
index 2b3c564e0472..fa959644735a 100644
--- a/drivers/s390/char/vmur.h
+++ b/drivers/s390/char/vmur.h
@@ -70,6 +70,7 @@ struct urdev {
70 size_t reclen; /* Record length for *write* CCWs */ 70 size_t reclen; /* Record length for *write* CCWs */
71 int class; /* VM device class */ 71 int class; /* VM device class */
72 int io_request_rc; /* return code from I/O request */ 72 int io_request_rc; /* return code from I/O request */
73 atomic_t ref_count; /* reference counter */
73}; 74};
74 75
75/* 76/*