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 */ |
