diff options
Diffstat (limited to 'drivers/s390/char/vmur.c')
-rw-r--r-- | drivers/s390/char/vmur.c | 250 |
1 files changed, 164 insertions, 86 deletions
diff --git a/drivers/s390/char/vmur.c b/drivers/s390/char/vmur.c index 04b19bdc09da..d70a6e65bf14 100644 --- a/drivers/s390/char/vmur.c +++ b/drivers/s390/char/vmur.c | |||
@@ -14,6 +14,7 @@ | |||
14 | #include <asm/cio.h> | 14 | #include <asm/cio.h> |
15 | #include <asm/ccwdev.h> | 15 | #include <asm/ccwdev.h> |
16 | #include <asm/debug.h> | 16 | #include <asm/debug.h> |
17 | #include <asm/diag.h> | ||
17 | 18 | ||
18 | #include "vmur.h" | 19 | #include "vmur.h" |
19 | 20 | ||
@@ -68,8 +69,26 @@ static struct ccw_driver ur_driver = { | |||
68 | .set_offline = ur_set_offline, | 69 | .set_offline = ur_set_offline, |
69 | }; | 70 | }; |
70 | 71 | ||
72 | static DEFINE_MUTEX(vmur_mutex); | ||
73 | |||
71 | /* | 74 | /* |
72 | * 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 | ||
73 | */ | 92 | */ |
74 | static struct urdev *urdev_alloc(struct ccw_device *cdev) | 93 | static struct urdev *urdev_alloc(struct ccw_device *cdev) |
75 | { | 94 | { |
@@ -78,42 +97,61 @@ static struct urdev *urdev_alloc(struct ccw_device *cdev) | |||
78 | urd = kzalloc(sizeof(struct urdev), GFP_KERNEL); | 97 | urd = kzalloc(sizeof(struct urdev), GFP_KERNEL); |
79 | if (!urd) | 98 | if (!urd) |
80 | return NULL; | 99 | return NULL; |
81 | urd->cdev = cdev; | ||
82 | urd->reclen = cdev->id.driver_info; | 100 | urd->reclen = cdev->id.driver_info; |
83 | ccw_device_get_id(cdev, &urd->dev_id); | 101 | ccw_device_get_id(cdev, &urd->dev_id); |
84 | mutex_init(&urd->io_mutex); | 102 | mutex_init(&urd->io_mutex); |
85 | 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); | ||
86 | return urd; | 107 | return urd; |
87 | } | 108 | } |
88 | 109 | ||
89 | static void urdev_free(struct urdev *urd) | 110 | static void urdev_free(struct urdev *urd) |
90 | { | 111 | { |
112 | TRACE("urdev_free: %p\n", urd); | ||
113 | if (urd->cdev) | ||
114 | put_device(&urd->cdev->dev); | ||
91 | kfree(urd); | 115 | kfree(urd); |
92 | } | 116 | } |
93 | 117 | ||
94 | /* | 118 | static void urdev_get(struct urdev *urd) |
95 | * This is how the character device driver gets a reference to a | 119 | { |
96 | * ur device. When this call returns successfully, a reference has | 120 | atomic_inc(&urd->ref_count); |
97 | * been taken (by get_device) on the underlying kobject. The recipient | 121 | } |
98 | * of this urdev pointer must eventually drop it with urdev_put(urd) | 122 | |
99 | * which does the corresponding put_device(). | 123 | static struct urdev *urdev_get_from_cdev(struct ccw_device *cdev) |
100 | */ | 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 | |||
101 | static struct urdev *urdev_get_from_devno(u16 devno) | 136 | static struct urdev *urdev_get_from_devno(u16 devno) |
102 | { | 137 | { |
103 | char bus_id[16]; | 138 | char bus_id[16]; |
104 | struct ccw_device *cdev; | 139 | struct ccw_device *cdev; |
140 | struct urdev *urd; | ||
105 | 141 | ||
106 | sprintf(bus_id, "0.0.%04x", devno); | 142 | sprintf(bus_id, "0.0.%04x", devno); |
107 | cdev = get_ccwdev_by_busid(&ur_driver, bus_id); | 143 | cdev = get_ccwdev_by_busid(&ur_driver, bus_id); |
108 | if (!cdev) | 144 | if (!cdev) |
109 | return NULL; | 145 | return NULL; |
110 | 146 | urd = urdev_get_from_cdev(cdev); | |
111 | return cdev->dev.driver_data; | 147 | put_device(&cdev->dev); |
148 | return urd; | ||
112 | } | 149 | } |
113 | 150 | ||
114 | static void urdev_put(struct urdev *urd) | 151 | static void urdev_put(struct urdev *urd) |
115 | { | 152 | { |
116 | put_device(&urd->cdev->dev); | 153 | if (atomic_dec_and_test(&urd->ref_count)) |
154 | urdev_free(urd); | ||
117 | } | 155 | } |
118 | 156 | ||
119 | /* | 157 | /* |
@@ -245,6 +283,7 @@ static void ur_int_handler(struct ccw_device *cdev, unsigned long intparm, | |||
245 | return; | 283 | return; |
246 | } | 284 | } |
247 | urd = cdev->dev.driver_data; | 285 | urd = cdev->dev.driver_data; |
286 | BUG_ON(!urd); | ||
248 | /* On special conditions irb is an error pointer */ | 287 | /* On special conditions irb is an error pointer */ |
249 | if (IS_ERR(irb)) | 288 | if (IS_ERR(irb)) |
250 | urd->io_request_rc = PTR_ERR(irb); | 289 | urd->io_request_rc = PTR_ERR(irb); |
@@ -262,9 +301,15 @@ static void ur_int_handler(struct ccw_device *cdev, unsigned long intparm, | |||
262 | static ssize_t ur_attr_reclen_show(struct device *dev, | 301 | static ssize_t ur_attr_reclen_show(struct device *dev, |
263 | struct device_attribute *attr, char *buf) | 302 | struct device_attribute *attr, char *buf) |
264 | { | 303 | { |
265 | struct urdev *urd = dev->driver_data; | 304 | struct urdev *urd; |
305 | int rc; | ||
266 | 306 | ||
267 | 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; | ||
268 | } | 313 | } |
269 | 314 | ||
270 | static DEVICE_ATTR(reclen, 0444, ur_attr_reclen_show, NULL); | 315 | static DEVICE_ATTR(reclen, 0444, ur_attr_reclen_show, NULL); |
@@ -379,31 +424,6 @@ static ssize_t ur_write(struct file *file, const char __user *udata, | |||
379 | return do_write(urf->urd, udata, count, urf->dev_reclen, ppos); | 424 | return do_write(urf->urd, udata, count, urf->dev_reclen, ppos); |
380 | } | 425 | } |
381 | 426 | ||
382 | static int do_diag_14(unsigned long rx, unsigned long ry1, | ||
383 | unsigned long subcode) | ||
384 | { | ||
385 | register unsigned long _ry1 asm("2") = ry1; | ||
386 | register unsigned long _ry2 asm("3") = subcode; | ||
387 | int rc = 0; | ||
388 | |||
389 | asm volatile( | ||
390 | #ifdef CONFIG_64BIT | ||
391 | " sam31\n" | ||
392 | " diag %2,2,0x14\n" | ||
393 | " sam64\n" | ||
394 | #else | ||
395 | " diag %2,2,0x14\n" | ||
396 | #endif | ||
397 | " ipm %0\n" | ||
398 | " srl %0,28\n" | ||
399 | : "=d" (rc), "+d" (_ry2) | ||
400 | : "d" (rx), "d" (_ry1) | ||
401 | : "cc"); | ||
402 | |||
403 | TRACE("diag 14: subcode=0x%lx, cc=%i\n", subcode, rc); | ||
404 | return rc; | ||
405 | } | ||
406 | |||
407 | /* | 427 | /* |
408 | * diagnose code 0x14 subcode 0x0028 - position spool file to designated | 428 | * diagnose code 0x14 subcode 0x0028 - position spool file to designated |
409 | * record | 429 | * record |
@@ -415,7 +435,7 @@ static int diag_position_to_record(int devno, int record) | |||
415 | { | 435 | { |
416 | int cc; | 436 | int cc; |
417 | 437 | ||
418 | cc = do_diag_14(record, devno, 0x28); | 438 | cc = diag14(record, devno, 0x28); |
419 | switch (cc) { | 439 | switch (cc) { |
420 | case 0: | 440 | case 0: |
421 | return 0; | 441 | return 0; |
@@ -440,7 +460,7 @@ static int diag_read_file(int devno, char *buf) | |||
440 | { | 460 | { |
441 | int cc; | 461 | int cc; |
442 | 462 | ||
443 | cc = do_diag_14((unsigned long) buf, devno, 0x00); | 463 | cc = diag14((unsigned long) buf, devno, 0x00); |
444 | switch (cc) { | 464 | switch (cc) { |
445 | case 0: | 465 | case 0: |
446 | return 0; | 466 | return 0; |
@@ -533,7 +553,7 @@ static int diag_read_next_file_info(struct file_control_block *buf, int spid) | |||
533 | { | 553 | { |
534 | int cc; | 554 | int cc; |
535 | 555 | ||
536 | cc = do_diag_14((unsigned long) buf, spid, 0xfff); | 556 | cc = diag14((unsigned long) buf, spid, 0xfff); |
537 | switch (cc) { | 557 | switch (cc) { |
538 | case 0: | 558 | case 0: |
539 | return 0; | 559 | return 0; |
@@ -750,64 +770,63 @@ static struct file_operations ur_fops = { | |||
750 | 770 | ||
751 | /* | 771 | /* |
752 | * ccw_device infrastructure: | 772 | * ccw_device infrastructure: |
753 | * 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 |
754 | * creates the struct urdev, the device attributes, sets up | 774 | * attributes, sets up the interrupt handler and validates the virtual |
755 | * the interrupt handler and validates the virtual unit record device. | 775 | * unit record device. |
756 | * ur_remove removes the device attributes, frees the struct urdev | 776 | * ur_remove removes the device attributes and drops the reference to |
757 | * 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. | ||
758 | */ | 784 | */ |
759 | static int ur_probe(struct ccw_device *cdev) | 785 | static int ur_probe(struct ccw_device *cdev) |
760 | { | 786 | { |
761 | struct urdev *urd; | 787 | struct urdev *urd; |
762 | int rc; | 788 | int rc; |
763 | 789 | ||
764 | TRACE("ur_probe: cdev=%p state=%d\n", cdev, *(int *) cdev->private); | 790 | TRACE("ur_probe: cdev=%p\n", cdev); |
765 | |||
766 | if (!get_device(&cdev->dev)) | ||
767 | return -ENODEV; | ||
768 | 791 | ||
792 | mutex_lock(&vmur_mutex); | ||
769 | urd = urdev_alloc(cdev); | 793 | urd = urdev_alloc(cdev); |
770 | if (!urd) { | 794 | if (!urd) { |
771 | rc = -ENOMEM; | 795 | rc = -ENOMEM; |
772 | goto fail; | 796 | goto fail_unlock; |
773 | } | 797 | } |
798 | |||
774 | rc = ur_create_attributes(&cdev->dev); | 799 | rc = ur_create_attributes(&cdev->dev); |
775 | if (rc) { | 800 | if (rc) { |
776 | rc = -ENOMEM; | 801 | rc = -ENOMEM; |
777 | goto fail; | 802 | goto fail_urdev_put; |
778 | } | 803 | } |
779 | cdev->dev.driver_data = urd; | ||
780 | cdev->handler = ur_int_handler; | 804 | cdev->handler = ur_int_handler; |
781 | 805 | ||
782 | /* validate virtual unit record device */ | 806 | /* validate virtual unit record device */ |
783 | urd->class = get_urd_class(urd); | 807 | urd->class = get_urd_class(urd); |
784 | if (urd->class < 0) { | 808 | if (urd->class < 0) { |
785 | rc = urd->class; | 809 | rc = urd->class; |
786 | goto fail; | 810 | goto fail_remove_attr; |
787 | } | 811 | } |
788 | 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)) { |
789 | rc = -ENOTSUPP; | 813 | rc = -ENOTSUPP; |
790 | goto fail; | 814 | goto fail_remove_attr; |
791 | } | 815 | } |
816 | spin_lock_irq(get_ccwdev_lock(cdev)); | ||
817 | cdev->dev.driver_data = urd; | ||
818 | spin_unlock_irq(get_ccwdev_lock(cdev)); | ||
792 | 819 | ||
820 | mutex_unlock(&vmur_mutex); | ||
793 | return 0; | 821 | return 0; |
794 | 822 | ||
795 | fail: | 823 | fail_remove_attr: |
796 | urdev_free(urd); | ||
797 | put_device(&cdev->dev); | ||
798 | return rc; | ||
799 | } | ||
800 | |||
801 | static void ur_remove(struct ccw_device *cdev) | ||
802 | { | ||
803 | struct urdev *urd = cdev->dev.driver_data; | ||
804 | |||
805 | TRACE("ur_remove\n"); | ||
806 | if (cdev->online) | ||
807 | ur_set_offline(cdev); | ||
808 | ur_remove_attributes(&cdev->dev); | 824 | ur_remove_attributes(&cdev->dev); |
809 | urdev_free(urd); | 825 | fail_urdev_put: |
810 | put_device(&cdev->dev); | 826 | urdev_put(urd); |
827 | fail_unlock: | ||
828 | mutex_unlock(&vmur_mutex); | ||
829 | return rc; | ||
811 | } | 830 | } |
812 | 831 | ||
813 | static int ur_set_online(struct ccw_device *cdev) | 832 | static int ur_set_online(struct ccw_device *cdev) |
@@ -816,20 +835,29 @@ static int ur_set_online(struct ccw_device *cdev) | |||
816 | int minor, major, rc; | 835 | int minor, major, rc; |
817 | char node_id[16]; | 836 | char node_id[16]; |
818 | 837 | ||
819 | TRACE("ur_set_online: cdev=%p state=%d\n", cdev, | 838 | TRACE("ur_set_online: cdev=%p\n", cdev); |
820 | *(int *) cdev->private); | ||
821 | 839 | ||
822 | if (!try_module_get(ur_driver.owner)) | 840 | mutex_lock(&vmur_mutex); |
823 | 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 | } | ||
824 | 853 | ||
825 | urd = (struct urdev *) cdev->dev.driver_data; | ||
826 | minor = urd->dev_id.devno; | 854 | minor = urd->dev_id.devno; |
827 | major = MAJOR(ur_first_dev_maj_min); | 855 | major = MAJOR(ur_first_dev_maj_min); |
828 | 856 | ||
829 | urd->char_device = cdev_alloc(); | 857 | urd->char_device = cdev_alloc(); |
830 | if (!urd->char_device) { | 858 | if (!urd->char_device) { |
831 | rc = -ENOMEM; | 859 | rc = -ENOMEM; |
832 | goto fail_module_put; | 860 | goto fail_urdev_put; |
833 | } | 861 | } |
834 | 862 | ||
835 | cdev_init(urd->char_device, &ur_fops); | 863 | cdev_init(urd->char_device, &ur_fops); |
@@ -858,29 +886,79 @@ static int ur_set_online(struct ccw_device *cdev) | |||
858 | TRACE("ur_set_online: device_create rc=%d\n", rc); | 886 | TRACE("ur_set_online: device_create rc=%d\n", rc); |
859 | goto fail_free_cdev; | 887 | goto fail_free_cdev; |
860 | } | 888 | } |
861 | 889 | urdev_put(urd); | |
890 | mutex_unlock(&vmur_mutex); | ||
862 | return 0; | 891 | return 0; |
863 | 892 | ||
864 | fail_free_cdev: | 893 | fail_free_cdev: |
865 | cdev_del(urd->char_device); | 894 | cdev_del(urd->char_device); |
866 | fail_module_put: | 895 | urd->char_device = NULL; |
867 | module_put(ur_driver.owner); | 896 | fail_urdev_put: |
868 | 897 | urdev_put(urd); | |
898 | fail_unlock: | ||
899 | mutex_unlock(&vmur_mutex); | ||
869 | return rc; | 900 | return rc; |
870 | } | 901 | } |
871 | 902 | ||
872 | static int ur_set_offline(struct ccw_device *cdev) | 903 | static int ur_set_offline_force(struct ccw_device *cdev, int force) |
873 | { | 904 | { |
874 | struct urdev *urd; | 905 | struct urdev *urd; |
906 | int rc; | ||
875 | 907 | ||
876 | TRACE("ur_set_offline: cdev=%p cdev->private=%p state=%d\n", | 908 | TRACE("ur_set_offline: cdev=%p\n", cdev); |
877 | cdev, cdev->private, *(int *) cdev->private); | 909 | urd = urdev_get_from_cdev(cdev); |
878 | 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 | } | ||
879 | device_destroy(vmur_class, urd->char_device->dev); | 924 | device_destroy(vmur_class, urd->char_device->dev); |
880 | cdev_del(urd->char_device); | 925 | cdev_del(urd->char_device); |
881 | module_put(ur_driver.owner); | 926 | urd->char_device = NULL; |
927 | rc = 0; | ||
882 | 928 | ||
883 | 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); | ||
884 | } | 962 | } |
885 | 963 | ||
886 | /* | 964 | /* |