diff options
author | Arnd Bergmann <arnd@arndb.de> | 2010-07-11 09:34:05 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2010-07-13 17:56:30 -0400 |
commit | bd25f4dd6972755579d0ea50d1a5ace2e9b00d1a (patch) | |
tree | 0aec56e60352d198514f5af933bd0399d413ec35 /drivers/hid | |
parent | 1c5474a65bf15a4cb162dfff86d6d0b5a08a740c (diff) |
HID: hiddev: use usb_find_interface, get rid of BKL
This removes the private hiddev_table in the usbhid
driver and changes it to use usb_find_interface
instead.
The advantage is that we can avoid the race between
usb_register_dev and usb_open and no longer need the
big kernel lock.
This doesn't introduce race condition -- the intf pointer could be
invalidated only in hiddev_disconnect() through usb_deregister_dev(),
but that will block on minor_rwsem and not actually remove the device
until usb_open().
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Jiri Kosina <jkosina@suse.cz>
Cc: "Greg Kroah-Hartman" <gregkh@suse.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid')
-rw-r--r-- | drivers/hid/usbhid/hiddev.c | 54 |
1 files changed, 12 insertions, 42 deletions
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index c24d2fa3e3b6..254a003af048 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c | |||
@@ -67,7 +67,7 @@ struct hiddev_list { | |||
67 | struct mutex thread_lock; | 67 | struct mutex thread_lock; |
68 | }; | 68 | }; |
69 | 69 | ||
70 | static struct hiddev *hiddev_table[HIDDEV_MINORS]; | 70 | static struct usb_driver hiddev_driver; |
71 | 71 | ||
72 | /* | 72 | /* |
73 | * Find a report, given the report's type and ID. The ID can be specified | 73 | * Find a report, given the report's type and ID. The ID can be specified |
@@ -265,22 +265,19 @@ static int hiddev_release(struct inode * inode, struct file * file) | |||
265 | static int hiddev_open(struct inode *inode, struct file *file) | 265 | static int hiddev_open(struct inode *inode, struct file *file) |
266 | { | 266 | { |
267 | struct hiddev_list *list; | 267 | struct hiddev_list *list; |
268 | int res, i; | 268 | struct usb_interface *intf; |
269 | 269 | struct hiddev *hiddev; | |
270 | /* See comment in hiddev_connect() for BKL explanation */ | 270 | int res; |
271 | lock_kernel(); | ||
272 | i = iminor(inode) - HIDDEV_MINOR_BASE; | ||
273 | 271 | ||
274 | if (i >= HIDDEV_MINORS || i < 0 || !hiddev_table[i]) | 272 | intf = usb_find_interface(&hiddev_driver, iminor(inode)); |
273 | if (!intf) | ||
275 | return -ENODEV; | 274 | return -ENODEV; |
275 | hiddev = usb_get_intfdata(intf); | ||
276 | 276 | ||
277 | if (!(list = kzalloc(sizeof(struct hiddev_list), GFP_KERNEL))) | 277 | if (!(list = kzalloc(sizeof(struct hiddev_list), GFP_KERNEL))) |
278 | return -ENOMEM; | 278 | return -ENOMEM; |
279 | mutex_init(&list->thread_lock); | 279 | mutex_init(&list->thread_lock); |
280 | 280 | list->hiddev = hiddev; | |
281 | list->hiddev = hiddev_table[i]; | ||
282 | |||
283 | |||
284 | file->private_data = list; | 281 | file->private_data = list; |
285 | 282 | ||
286 | /* | 283 | /* |
@@ -289,7 +286,7 @@ static int hiddev_open(struct inode *inode, struct file *file) | |||
289 | */ | 286 | */ |
290 | if (list->hiddev->exist) { | 287 | if (list->hiddev->exist) { |
291 | if (!list->hiddev->open++) { | 288 | if (!list->hiddev->open++) { |
292 | res = usbhid_open(hiddev_table[i]->hid); | 289 | res = usbhid_open(hiddev->hid); |
293 | if (res < 0) { | 290 | if (res < 0) { |
294 | res = -EIO; | 291 | res = -EIO; |
295 | goto bail; | 292 | goto bail; |
@@ -301,12 +298,12 @@ static int hiddev_open(struct inode *inode, struct file *file) | |||
301 | } | 298 | } |
302 | 299 | ||
303 | spin_lock_irq(&list->hiddev->list_lock); | 300 | spin_lock_irq(&list->hiddev->list_lock); |
304 | list_add_tail(&list->node, &hiddev_table[i]->list); | 301 | list_add_tail(&list->node, &hiddev->list); |
305 | spin_unlock_irq(&list->hiddev->list_lock); | 302 | spin_unlock_irq(&list->hiddev->list_lock); |
306 | 303 | ||
307 | if (!list->hiddev->open++) | 304 | if (!list->hiddev->open++) |
308 | if (list->hiddev->exist) { | 305 | if (list->hiddev->exist) { |
309 | struct hid_device *hid = hiddev_table[i]->hid; | 306 | struct hid_device *hid = hiddev->hid; |
310 | res = usbhid_get_power(hid); | 307 | res = usbhid_get_power(hid); |
311 | if (res < 0) { | 308 | if (res < 0) { |
312 | res = -EIO; | 309 | res = -EIO; |
@@ -314,13 +311,10 @@ static int hiddev_open(struct inode *inode, struct file *file) | |||
314 | } | 311 | } |
315 | usbhid_open(hid); | 312 | usbhid_open(hid); |
316 | } | 313 | } |
317 | |||
318 | unlock_kernel(); | ||
319 | return 0; | 314 | return 0; |
320 | bail: | 315 | bail: |
321 | file->private_data = NULL; | 316 | file->private_data = NULL; |
322 | kfree(list); | 317 | kfree(list); |
323 | unlock_kernel(); | ||
324 | return res; | 318 | return res; |
325 | } | 319 | } |
326 | 320 | ||
@@ -894,37 +888,14 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) | |||
894 | hid->hiddev = hiddev; | 888 | hid->hiddev = hiddev; |
895 | hiddev->hid = hid; | 889 | hiddev->hid = hid; |
896 | hiddev->exist = 1; | 890 | hiddev->exist = 1; |
897 | 891 | usb_set_intfdata(usbhid->intf, usbhid); | |
898 | /* | ||
899 | * BKL here is used to avoid race after usb_register_dev(). | ||
900 | * Once the device node has been created, open() could happen on it. | ||
901 | * The code below will then fail, as hiddev_table hasn't been | ||
902 | * updated. | ||
903 | * | ||
904 | * The obvious fix -- introducing mutex to guard hiddev_table[] | ||
905 | * doesn't work, as usb_open() and usb_register_dev() both take | ||
906 | * minor_rwsem, thus we'll have ABBA deadlock. | ||
907 | * | ||
908 | * Before BKL pushdown, usb_open() had been acquiring it in right | ||
909 | * order, so _open() was safe to use it to protect from this race. | ||
910 | * Now the order is different, but AB-BA deadlock still doesn't occur | ||
911 | * as BKL is dropped on schedule() (i.e. while sleeping on | ||
912 | * minor_rwsem). Fugly. | ||
913 | */ | ||
914 | lock_kernel(); | ||
915 | retval = usb_register_dev(usbhid->intf, &hiddev_class); | 892 | retval = usb_register_dev(usbhid->intf, &hiddev_class); |
916 | if (retval) { | 893 | if (retval) { |
917 | err_hid("Not able to get a minor for this device."); | 894 | err_hid("Not able to get a minor for this device."); |
918 | hid->hiddev = NULL; | 895 | hid->hiddev = NULL; |
919 | unlock_kernel(); | ||
920 | kfree(hiddev); | 896 | kfree(hiddev); |
921 | return -1; | 897 | return -1; |
922 | } else { | ||
923 | hid->minor = usbhid->intf->minor; | ||
924 | hiddev_table[usbhid->intf->minor - HIDDEV_MINOR_BASE] = hiddev; | ||
925 | } | 898 | } |
926 | unlock_kernel(); | ||
927 | |||
928 | return 0; | 899 | return 0; |
929 | } | 900 | } |
930 | 901 | ||
@@ -942,7 +913,6 @@ void hiddev_disconnect(struct hid_device *hid) | |||
942 | hiddev->exist = 0; | 913 | hiddev->exist = 0; |
943 | mutex_unlock(&hiddev->existancelock); | 914 | mutex_unlock(&hiddev->existancelock); |
944 | 915 | ||
945 | hiddev_table[hiddev->hid->minor - HIDDEV_MINOR_BASE] = NULL; | ||
946 | usb_deregister_dev(usbhid->intf, &hiddev_class); | 916 | usb_deregister_dev(usbhid->intf, &hiddev_class); |
947 | 917 | ||
948 | if (hiddev->open) { | 918 | if (hiddev->open) { |