aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid/usbhid
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2014-09-02 11:39:15 -0400
committerJiri Kosina <jkosina@suse.cz>2014-09-03 17:37:38 -0400
commit8f507ef522d55a6e2f9e11a1c1163a92756da044 (patch)
tree7f053757c34528cbf56aac2ffc49fe7eb48623eb /drivers/hid/usbhid
parent79346d620e9de87912de73337f6df8b7f9a46888 (diff)
HID: usbhid: improve handling of Clear-Halt and reset
This patch changes the way usbhid carries out Clear-Halt and reset. Currently, after a Clear-Halt on the interrupt-IN endpoint, the driver immediately restarts the interrupt URB, even if the Clear-Halt failed. This doesn't work out well when the reason for the failure was that the device was disconnected (when a low- or full-speed device is connected through a hub to an EHCI controller, transfer errors caused by disconnection are reported as stalls by the hub). Instead now the driver will attempt a reset after a failed Clear-Halt. The way resets are carried out is also changed. Now the driver will call usb_queue_reset_device() instead of calling usb_reset_device() directly. This avoids a deadlock that would arise when a device is unplugged: The hid_reset() routine runs as a workqueue item, a reset attempt after the device has been unplugged will fail, failure will cause usbhid to be unbound, and the disconnect routine will try to do cancel_work_sync(). The usb_queue_reset_device() implementation is carefully written to handle scenarios like this one properly. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid/usbhid')
-rw-r--r--drivers/hid/usbhid/hid-core.c34
1 files changed, 9 insertions, 25 deletions
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index 79cf503e37bf..80c50763b3f8 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -116,40 +116,24 @@ static void hid_reset(struct work_struct *work)
116 struct usbhid_device *usbhid = 116 struct usbhid_device *usbhid =
117 container_of(work, struct usbhid_device, reset_work); 117 container_of(work, struct usbhid_device, reset_work);
118 struct hid_device *hid = usbhid->hid; 118 struct hid_device *hid = usbhid->hid;
119 int rc = 0; 119 int rc;
120 120
121 if (test_bit(HID_CLEAR_HALT, &usbhid->iofl)) { 121 if (test_bit(HID_CLEAR_HALT, &usbhid->iofl)) {
122 dev_dbg(&usbhid->intf->dev, "clear halt\n"); 122 dev_dbg(&usbhid->intf->dev, "clear halt\n");
123 rc = usb_clear_halt(hid_to_usb_dev(hid), usbhid->urbin->pipe); 123 rc = usb_clear_halt(hid_to_usb_dev(hid), usbhid->urbin->pipe);
124 clear_bit(HID_CLEAR_HALT, &usbhid->iofl); 124 clear_bit(HID_CLEAR_HALT, &usbhid->iofl);
125 hid_start_in(hid);
126 }
127
128 else if (test_bit(HID_RESET_PENDING, &usbhid->iofl)) {
129 dev_dbg(&usbhid->intf->dev, "resetting device\n");
130 rc = usb_lock_device_for_reset(hid_to_usb_dev(hid), usbhid->intf);
131 if (rc == 0) { 125 if (rc == 0) {
132 rc = usb_reset_device(hid_to_usb_dev(hid)); 126 hid_start_in(hid);
133 usb_unlock_device(hid_to_usb_dev(hid)); 127 } else {
128 dev_dbg(&usbhid->intf->dev,
129 "clear-halt failed: %d\n", rc);
130 set_bit(HID_RESET_PENDING, &usbhid->iofl);
134 } 131 }
135 clear_bit(HID_RESET_PENDING, &usbhid->iofl);
136 } 132 }
137 133
138 switch (rc) { 134 if (test_bit(HID_RESET_PENDING, &usbhid->iofl)) {
139 case 0: 135 dev_dbg(&usbhid->intf->dev, "resetting device\n");
140 if (!test_bit(HID_IN_RUNNING, &usbhid->iofl)) 136 usb_queue_reset_device(usbhid->intf);
141 hid_io_error(hid);
142 break;
143 default:
144 hid_err(hid, "can't reset device, %s-%s/input%d, status %d\n",
145 hid_to_usb_dev(hid)->bus->bus_name,
146 hid_to_usb_dev(hid)->devpath,
147 usbhid->ifnum, rc);
148 /* FALLTHROUGH */
149 case -EHOSTUNREACH:
150 case -ENODEV:
151 case -EINTR:
152 break;
153 } 137 }
154} 138}
155 139