diff options
Diffstat (limited to 'drivers/s390/char/vmur.c')
-rw-r--r-- | drivers/s390/char/vmur.c | 218 |
1 files changed, 160 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 | ||
72 | static 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 | */ |
75 | static struct urdev *urdev_alloc(struct ccw_device *cdev) | 93 | static 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 | ||
90 | static void urdev_free(struct urdev *urd) | 110 | static 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 | /* | 118 | static 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(). | 123 | static 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 | |||
102 | static struct urdev *urdev_get_from_devno(u16 devno) | 136 | static 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 | ||
115 | static void urdev_put(struct urdev *urd) | 151 | static 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, | |||
263 | static ssize_t ur_attr_reclen_show(struct device *dev, | 301 | static 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 | ||
271 | static DEVICE_ATTR(reclen, 0444, ur_attr_reclen_show, NULL); | 315 | static 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 | */ |
735 | static int ur_probe(struct ccw_device *cdev) | 785 | static 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 | ||
771 | fail: | 823 | fail_remove_attr: |
772 | urdev_free(urd); | ||
773 | put_device(&cdev->dev); | ||
774 | return rc; | ||
775 | } | ||
776 | |||
777 | static 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); | 825 | fail_urdev_put: |
786 | put_device(&cdev->dev); | 826 | urdev_put(urd); |
827 | fail_unlock: | ||
828 | mutex_unlock(&vmur_mutex); | ||
829 | return rc; | ||
787 | } | 830 | } |
788 | 831 | ||
789 | static int ur_set_online(struct ccw_device *cdev) | 832 | static 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 | ||
840 | fail_free_cdev: | 893 | fail_free_cdev: |
841 | cdev_del(urd->char_device); | 894 | cdev_del(urd->char_device); |
842 | fail_module_put: | 895 | urd->char_device = NULL; |
843 | module_put(ur_driver.owner); | 896 | fail_urdev_put: |
844 | 897 | urdev_put(urd); | |
898 | fail_unlock: | ||
899 | mutex_unlock(&vmur_mutex); | ||
845 | return rc; | 900 | return rc; |
846 | } | 901 | } |
847 | 902 | ||
848 | static int ur_set_offline(struct ccw_device *cdev) | 903 | static 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; | 929 | fail_urdev_put: |
930 | urdev_put(urd); | ||
931 | return rc; | ||
932 | } | ||
933 | |||
934 | static 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 | |||
944 | static 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 | /* |