diff options
author | Anssi Hannula <anssi.hannula@gmail.com> | 2008-10-04 08:44:06 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2008-10-14 17:51:02 -0400 |
commit | f129ea6d1efe0eddcbb1f0faaec5623788ad9e58 (patch) | |
tree | 4a2b72a2f98b19efeb5e28837ed1bfacffbbc961 /drivers/hid | |
parent | dded364bf4e1f0de67d7d7b9e77c06b23a9f081f (diff) |
HID: fix a lockup regression when using force feedback on a PID device
Commit 8006479c9b75fb6594a7b746af3d7f1fbb68f18f introduced a spinlock in
input_dev->event_lock, which is locked when handling input events.
However, the hid-pidff driver sleeps when handling events as it waits for
reports being sent to the device before changing the report contents
again.
This causes a system lockup when trying to use force feedback with a PID
device, a regression introduced in 2.6.24 and 2.6.23.15.
Fix it by extracting the raw report data from struct hid_report
immediately when hid_submit_report() is called, therefore allowing
drivers to change the contents of struct hid_report immediately without
affecting the already-queued transfer.
In hid-pidff, re-add the removed usbhid_wait_io() to
pidff_erase_effect() instead, to prevent a full report queue from causing
the submission to fail, thus not freeing up device memory.
pidff_erase_effect() is not called while dev->event_lock is held.
Signed-off-by: Anssi Hannula <anssi.hannula@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid')
-rw-r--r-- | drivers/hid/usbhid/hid-core.c | 31 | ||||
-rw-r--r-- | drivers/hid/usbhid/hid-pidff.c | 5 | ||||
-rw-r--r-- | drivers/hid/usbhid/usbhid.h | 2 |
3 files changed, 31 insertions, 7 deletions
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 07840df56c63..1ae047cd4fa1 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c | |||
@@ -232,13 +232,16 @@ static void hid_irq_in(struct urb *urb) | |||
232 | static int hid_submit_out(struct hid_device *hid) | 232 | static int hid_submit_out(struct hid_device *hid) |
233 | { | 233 | { |
234 | struct hid_report *report; | 234 | struct hid_report *report; |
235 | char *raw_report; | ||
235 | struct usbhid_device *usbhid = hid->driver_data; | 236 | struct usbhid_device *usbhid = hid->driver_data; |
236 | 237 | ||
237 | report = usbhid->out[usbhid->outtail]; | 238 | report = usbhid->out[usbhid->outtail].report; |
239 | raw_report = usbhid->out[usbhid->outtail].raw_report; | ||
238 | 240 | ||
239 | hid_output_report(report, usbhid->outbuf); | ||
240 | usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); | 241 | usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); |
241 | usbhid->urbout->dev = hid_to_usb_dev(hid); | 242 | usbhid->urbout->dev = hid_to_usb_dev(hid); |
243 | memcpy(usbhid->outbuf, raw_report, usbhid->urbout->transfer_buffer_length); | ||
244 | kfree(raw_report); | ||
242 | 245 | ||
243 | dbg_hid("submitting out urb\n"); | 246 | dbg_hid("submitting out urb\n"); |
244 | 247 | ||
@@ -254,17 +257,20 @@ static int hid_submit_ctrl(struct hid_device *hid) | |||
254 | { | 257 | { |
255 | struct hid_report *report; | 258 | struct hid_report *report; |
256 | unsigned char dir; | 259 | unsigned char dir; |
260 | char *raw_report; | ||
257 | int len; | 261 | int len; |
258 | struct usbhid_device *usbhid = hid->driver_data; | 262 | struct usbhid_device *usbhid = hid->driver_data; |
259 | 263 | ||
260 | report = usbhid->ctrl[usbhid->ctrltail].report; | 264 | report = usbhid->ctrl[usbhid->ctrltail].report; |
265 | raw_report = usbhid->ctrl[usbhid->ctrltail].raw_report; | ||
261 | dir = usbhid->ctrl[usbhid->ctrltail].dir; | 266 | dir = usbhid->ctrl[usbhid->ctrltail].dir; |
262 | 267 | ||
263 | len = ((report->size - 1) >> 3) + 1 + (report->id > 0); | 268 | len = ((report->size - 1) >> 3) + 1 + (report->id > 0); |
264 | if (dir == USB_DIR_OUT) { | 269 | if (dir == USB_DIR_OUT) { |
265 | hid_output_report(report, usbhid->ctrlbuf); | ||
266 | usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0); | 270 | usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0); |
267 | usbhid->urbctrl->transfer_buffer_length = len; | 271 | usbhid->urbctrl->transfer_buffer_length = len; |
272 | memcpy(usbhid->ctrlbuf, raw_report, len); | ||
273 | kfree(raw_report); | ||
268 | } else { | 274 | } else { |
269 | int maxpacket, padlen; | 275 | int maxpacket, padlen; |
270 | 276 | ||
@@ -401,6 +407,7 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns | |||
401 | int head; | 407 | int head; |
402 | unsigned long flags; | 408 | unsigned long flags; |
403 | struct usbhid_device *usbhid = hid->driver_data; | 409 | struct usbhid_device *usbhid = hid->driver_data; |
410 | int len = ((report->size - 1) >> 3) + 1 + (report->id > 0); | ||
404 | 411 | ||
405 | if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) | 412 | if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) |
406 | return; | 413 | return; |
@@ -415,7 +422,14 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns | |||
415 | return; | 422 | return; |
416 | } | 423 | } |
417 | 424 | ||
418 | usbhid->out[usbhid->outhead] = report; | 425 | usbhid->out[usbhid->outhead].raw_report = kmalloc(len, GFP_ATOMIC); |
426 | if (!usbhid->out[usbhid->outhead].raw_report) { | ||
427 | spin_unlock_irqrestore(&usbhid->outlock, flags); | ||
428 | warn("output queueing failed"); | ||
429 | return; | ||
430 | } | ||
431 | hid_output_report(report, usbhid->out[usbhid->outhead].raw_report); | ||
432 | usbhid->out[usbhid->outhead].report = report; | ||
419 | usbhid->outhead = head; | 433 | usbhid->outhead = head; |
420 | 434 | ||
421 | if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) | 435 | if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) |
@@ -434,6 +448,15 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns | |||
434 | return; | 448 | return; |
435 | } | 449 | } |
436 | 450 | ||
451 | if (dir == USB_DIR_OUT) { | ||
452 | usbhid->ctrl[usbhid->ctrlhead].raw_report = kmalloc(len, GFP_ATOMIC); | ||
453 | if (!usbhid->ctrl[usbhid->ctrlhead].raw_report) { | ||
454 | spin_unlock_irqrestore(&usbhid->ctrllock, flags); | ||
455 | warn("control queueing failed"); | ||
456 | return; | ||
457 | } | ||
458 | hid_output_report(report, usbhid->ctrl[usbhid->ctrlhead].raw_report); | ||
459 | } | ||
437 | usbhid->ctrl[usbhid->ctrlhead].report = report; | 460 | usbhid->ctrl[usbhid->ctrlhead].report = report; |
438 | usbhid->ctrl[usbhid->ctrlhead].dir = dir; | 461 | usbhid->ctrl[usbhid->ctrlhead].dir = dir; |
439 | usbhid->ctrlhead = head; | 462 | usbhid->ctrlhead = head; |
diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 011326178c06..484e3eec2f88 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c | |||
@@ -397,7 +397,6 @@ static void pidff_set_condition_report(struct pidff_device *pidff, | |||
397 | effect->u.condition[i].left_saturation); | 397 | effect->u.condition[i].left_saturation); |
398 | pidff_set(&pidff->set_condition[PID_DEAD_BAND], | 398 | pidff_set(&pidff->set_condition[PID_DEAD_BAND], |
399 | effect->u.condition[i].deadband); | 399 | effect->u.condition[i].deadband); |
400 | usbhid_wait_io(pidff->hid); | ||
401 | usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONDITION], | 400 | usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONDITION], |
402 | USB_DIR_OUT); | 401 | USB_DIR_OUT); |
403 | } | 402 | } |
@@ -512,7 +511,6 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n) | |||
512 | pidff->effect_operation[PID_LOOP_COUNT].value[0] = n; | 511 | pidff->effect_operation[PID_LOOP_COUNT].value[0] = n; |
513 | } | 512 | } |
514 | 513 | ||
515 | usbhid_wait_io(pidff->hid); | ||
516 | usbhid_submit_report(pidff->hid, pidff->reports[PID_EFFECT_OPERATION], | 514 | usbhid_submit_report(pidff->hid, pidff->reports[PID_EFFECT_OPERATION], |
517 | USB_DIR_OUT); | 515 | USB_DIR_OUT); |
518 | } | 516 | } |
@@ -548,6 +546,9 @@ static int pidff_erase_effect(struct input_dev *dev, int effect_id) | |||
548 | int pid_id = pidff->pid_id[effect_id]; | 546 | int pid_id = pidff->pid_id[effect_id]; |
549 | 547 | ||
550 | debug("starting to erase %d/%d", effect_id, pidff->pid_id[effect_id]); | 548 | debug("starting to erase %d/%d", effect_id, pidff->pid_id[effect_id]); |
549 | /* Wait for the queue to clear. We do not want a full fifo to | ||
550 | prevent the effect removal. */ | ||
551 | usbhid_wait_io(pidff->hid); | ||
551 | pidff_playback_pid(pidff, pid_id, 0); | 552 | pidff_playback_pid(pidff, pid_id, 0); |
552 | pidff_erase_pid(pidff, pid_id); | 553 | pidff_erase_pid(pidff, pid_id); |
553 | 554 | ||
diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h index b47f991867e9..abedb13c623e 100644 --- a/drivers/hid/usbhid/usbhid.h +++ b/drivers/hid/usbhid/usbhid.h | |||
@@ -67,7 +67,7 @@ struct usbhid_device { | |||
67 | spinlock_t ctrllock; /* Control fifo spinlock */ | 67 | spinlock_t ctrllock; /* Control fifo spinlock */ |
68 | 68 | ||
69 | struct urb *urbout; /* Output URB */ | 69 | struct urb *urbout; /* Output URB */ |
70 | struct hid_report *out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */ | 70 | struct hid_output_fifo out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */ |
71 | unsigned char outhead, outtail; /* Output pipe fifo head & tail */ | 71 | unsigned char outhead, outtail; /* Output pipe fifo head & tail */ |
72 | char *outbuf; /* Output buffer */ | 72 | char *outbuf; /* Output buffer */ |
73 | dma_addr_t outbuf_dma; /* Output buffer dma */ | 73 | dma_addr_t outbuf_dma; /* Output buffer dma */ |