Commit f129ea6d authored by Anssi Hannula's avatar Anssi Hannula Committed by Jiri Kosina

HID: fix a lockup regression when using force feedback on a PID device

Commit 8006479c 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: default avatarAnssi Hannula <anssi.hannula@gmail.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent dded364b
...@@ -232,13 +232,16 @@ static void hid_irq_in(struct urb *urb) ...@@ -232,13 +232,16 @@ static void hid_irq_in(struct urb *urb)
static int hid_submit_out(struct hid_device *hid) static int hid_submit_out(struct hid_device *hid)
{ {
struct hid_report *report; struct hid_report *report;
char *raw_report;
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
report = usbhid->out[usbhid->outtail]; report = usbhid->out[usbhid->outtail].report;
raw_report = usbhid->out[usbhid->outtail].raw_report;
hid_output_report(report, usbhid->outbuf);
usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0);
usbhid->urbout->dev = hid_to_usb_dev(hid); usbhid->urbout->dev = hid_to_usb_dev(hid);
memcpy(usbhid->outbuf, raw_report, usbhid->urbout->transfer_buffer_length);
kfree(raw_report);
dbg_hid("submitting out urb\n"); dbg_hid("submitting out urb\n");
...@@ -254,17 +257,20 @@ static int hid_submit_ctrl(struct hid_device *hid) ...@@ -254,17 +257,20 @@ static int hid_submit_ctrl(struct hid_device *hid)
{ {
struct hid_report *report; struct hid_report *report;
unsigned char dir; unsigned char dir;
char *raw_report;
int len; int len;
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
report = usbhid->ctrl[usbhid->ctrltail].report; report = usbhid->ctrl[usbhid->ctrltail].report;
raw_report = usbhid->ctrl[usbhid->ctrltail].raw_report;
dir = usbhid->ctrl[usbhid->ctrltail].dir; dir = usbhid->ctrl[usbhid->ctrltail].dir;
len = ((report->size - 1) >> 3) + 1 + (report->id > 0); len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
if (dir == USB_DIR_OUT) { if (dir == USB_DIR_OUT) {
hid_output_report(report, usbhid->ctrlbuf);
usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0); usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0);
usbhid->urbctrl->transfer_buffer_length = len; usbhid->urbctrl->transfer_buffer_length = len;
memcpy(usbhid->ctrlbuf, raw_report, len);
kfree(raw_report);
} else { } else {
int maxpacket, padlen; int maxpacket, padlen;
...@@ -401,6 +407,7 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns ...@@ -401,6 +407,7 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
int head; int head;
unsigned long flags; unsigned long flags;
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
int len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN)
return; return;
...@@ -415,7 +422,14 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns ...@@ -415,7 +422,14 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
return; return;
} }
usbhid->out[usbhid->outhead] = report; usbhid->out[usbhid->outhead].raw_report = kmalloc(len, GFP_ATOMIC);
if (!usbhid->out[usbhid->outhead].raw_report) {
spin_unlock_irqrestore(&usbhid->outlock, flags);
warn("output queueing failed");
return;
}
hid_output_report(report, usbhid->out[usbhid->outhead].raw_report);
usbhid->out[usbhid->outhead].report = report;
usbhid->outhead = head; usbhid->outhead = head;
if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) 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,6 +448,15 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
return; return;
} }
if (dir == USB_DIR_OUT) {
usbhid->ctrl[usbhid->ctrlhead].raw_report = kmalloc(len, GFP_ATOMIC);
if (!usbhid->ctrl[usbhid->ctrlhead].raw_report) {
spin_unlock_irqrestore(&usbhid->ctrllock, flags);
warn("control queueing failed");
return;
}
hid_output_report(report, usbhid->ctrl[usbhid->ctrlhead].raw_report);
}
usbhid->ctrl[usbhid->ctrlhead].report = report; usbhid->ctrl[usbhid->ctrlhead].report = report;
usbhid->ctrl[usbhid->ctrlhead].dir = dir; usbhid->ctrl[usbhid->ctrlhead].dir = dir;
usbhid->ctrlhead = head; usbhid->ctrlhead = head;
......
...@@ -397,7 +397,6 @@ static void pidff_set_condition_report(struct pidff_device *pidff, ...@@ -397,7 +397,6 @@ static void pidff_set_condition_report(struct pidff_device *pidff,
effect->u.condition[i].left_saturation); effect->u.condition[i].left_saturation);
pidff_set(&pidff->set_condition[PID_DEAD_BAND], pidff_set(&pidff->set_condition[PID_DEAD_BAND],
effect->u.condition[i].deadband); effect->u.condition[i].deadband);
usbhid_wait_io(pidff->hid);
usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONDITION], usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONDITION],
USB_DIR_OUT); USB_DIR_OUT);
} }
...@@ -512,7 +511,6 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n) ...@@ -512,7 +511,6 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
pidff->effect_operation[PID_LOOP_COUNT].value[0] = n; pidff->effect_operation[PID_LOOP_COUNT].value[0] = n;
} }
usbhid_wait_io(pidff->hid);
usbhid_submit_report(pidff->hid, pidff->reports[PID_EFFECT_OPERATION], usbhid_submit_report(pidff->hid, pidff->reports[PID_EFFECT_OPERATION],
USB_DIR_OUT); USB_DIR_OUT);
} }
...@@ -548,6 +546,9 @@ static int pidff_erase_effect(struct input_dev *dev, int effect_id) ...@@ -548,6 +546,9 @@ static int pidff_erase_effect(struct input_dev *dev, int effect_id)
int pid_id = pidff->pid_id[effect_id]; int pid_id = pidff->pid_id[effect_id];
debug("starting to erase %d/%d", effect_id, pidff->pid_id[effect_id]); debug("starting to erase %d/%d", effect_id, pidff->pid_id[effect_id]);
/* Wait for the queue to clear. We do not want a full fifo to
prevent the effect removal. */
usbhid_wait_io(pidff->hid);
pidff_playback_pid(pidff, pid_id, 0); pidff_playback_pid(pidff, pid_id, 0);
pidff_erase_pid(pidff, pid_id); pidff_erase_pid(pidff, pid_id);
......
...@@ -67,7 +67,7 @@ struct usbhid_device { ...@@ -67,7 +67,7 @@ struct usbhid_device {
spinlock_t ctrllock; /* Control fifo spinlock */ spinlock_t ctrllock; /* Control fifo spinlock */
struct urb *urbout; /* Output URB */ struct urb *urbout; /* Output URB */
struct hid_report *out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */ struct hid_output_fifo out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */
unsigned char outhead, outtail; /* Output pipe fifo head & tail */ unsigned char outhead, outtail; /* Output pipe fifo head & tail */
char *outbuf; /* Output buffer */ char *outbuf; /* Output buffer */
dma_addr_t outbuf_dma; /* Output buffer dma */ dma_addr_t outbuf_dma; /* Output buffer dma */
......
...@@ -388,6 +388,12 @@ struct hid_report_enum { ...@@ -388,6 +388,12 @@ struct hid_report_enum {
struct hid_control_fifo { struct hid_control_fifo {
unsigned char dir; unsigned char dir;
struct hid_report *report; struct hid_report *report;
char *raw_report;
};
struct hid_output_fifo {
struct hid_report *report;
char *raw_report;
}; };
#define HID_CLAIMED_INPUT 1 #define HID_CLAIMED_INPUT 1
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment