mirror of https://gitee.com/openkylin/linux.git
HID: sony: Send FF commands in non-atomic context
The ff_memless has a timer running which gets run in an atomic context and calls the play_effect callback. The callback function for sony uses the hid_output_raw_report (overwritten by sixaxis_usb_output_raw_report) function to handle differences in the control message format. It is not safe for an atomic context because it may sleep later in usb_start_wait_urb. This "scheduling while atomic" can cause the system to lock up. A workaround is to make the force feedback state update using work_queues and use the play_effect function only to enqueue the work item. Reported-by: Simon Wood <simon@mungewell.org> Reported-by: David Herrmann <dh.herrmann@gmail.com> Signed-off-by: Sven Eckelmann <sven@narfation.org> Signed-off-by: Simon Wood <simon@mungewell.org> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
This commit is contained in:
parent
e0da5c9a49
commit
9f323b6811
|
@ -225,6 +225,13 @@ static const unsigned int buzz_keymap[] = {
|
||||||
struct sony_sc {
|
struct sony_sc {
|
||||||
unsigned long quirks;
|
unsigned long quirks;
|
||||||
|
|
||||||
|
#ifdef CONFIG_SONY_FF
|
||||||
|
struct work_struct rumble_worker;
|
||||||
|
struct hid_device *hdev;
|
||||||
|
__u8 left;
|
||||||
|
__u8 right;
|
||||||
|
#endif
|
||||||
|
|
||||||
void *extra;
|
void *extra;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -615,9 +622,9 @@ static void buzz_remove(struct hid_device *hdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_SONY_FF
|
#ifdef CONFIG_SONY_FF
|
||||||
static int sony_play_effect(struct input_dev *dev, void *data,
|
static void sony_rumble_worker(struct work_struct *work)
|
||||||
struct ff_effect *effect)
|
|
||||||
{
|
{
|
||||||
|
struct sony_sc *sc = container_of(work, struct sony_sc, rumble_worker);
|
||||||
unsigned char buf[] = {
|
unsigned char buf[] = {
|
||||||
0x01,
|
0x01,
|
||||||
0x00, 0xff, 0x00, 0xff, 0x00,
|
0x00, 0xff, 0x00, 0xff, 0x00,
|
||||||
|
@ -628,21 +635,28 @@ static int sony_play_effect(struct input_dev *dev, void *data,
|
||||||
0xff, 0x27, 0x10, 0x00, 0x32,
|
0xff, 0x27, 0x10, 0x00, 0x32,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00
|
0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
};
|
};
|
||||||
__u8 left;
|
|
||||||
__u8 right;
|
buf[3] = sc->right;
|
||||||
|
buf[5] = sc->left;
|
||||||
|
|
||||||
|
sc->hdev->hid_output_raw_report(sc->hdev, buf, sizeof(buf),
|
||||||
|
HID_OUTPUT_REPORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sony_play_effect(struct input_dev *dev, void *data,
|
||||||
|
struct ff_effect *effect)
|
||||||
|
{
|
||||||
struct hid_device *hid = input_get_drvdata(dev);
|
struct hid_device *hid = input_get_drvdata(dev);
|
||||||
|
struct sony_sc *sc = hid_get_drvdata(hid);
|
||||||
|
|
||||||
if (effect->type != FF_RUMBLE)
|
if (effect->type != FF_RUMBLE)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
left = effect->u.rumble.strong_magnitude / 256;
|
sc->left = effect->u.rumble.strong_magnitude / 256;
|
||||||
right = effect->u.rumble.weak_magnitude ? 1 : 0;
|
sc->right = effect->u.rumble.weak_magnitude ? 1 : 0;
|
||||||
|
|
||||||
buf[3] = right;
|
schedule_work(&sc->rumble_worker);
|
||||||
buf[5] = left;
|
return 0;
|
||||||
|
|
||||||
return hid->hid_output_raw_report(hid, buf, sizeof(buf),
|
|
||||||
HID_OUTPUT_REPORT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sony_init_ff(struct hid_device *hdev)
|
static int sony_init_ff(struct hid_device *hdev)
|
||||||
|
@ -650,16 +664,31 @@ static int sony_init_ff(struct hid_device *hdev)
|
||||||
struct hid_input *hidinput = list_entry(hdev->inputs.next,
|
struct hid_input *hidinput = list_entry(hdev->inputs.next,
|
||||||
struct hid_input, list);
|
struct hid_input, list);
|
||||||
struct input_dev *input_dev = hidinput->input;
|
struct input_dev *input_dev = hidinput->input;
|
||||||
|
struct sony_sc *sc = hid_get_drvdata(hdev);
|
||||||
|
|
||||||
|
sc->hdev = hdev;
|
||||||
|
INIT_WORK(&sc->rumble_worker, sony_rumble_worker);
|
||||||
|
|
||||||
input_set_capability(input_dev, EV_FF, FF_RUMBLE);
|
input_set_capability(input_dev, EV_FF, FF_RUMBLE);
|
||||||
return input_ff_create_memless(input_dev, NULL, sony_play_effect);
|
return input_ff_create_memless(input_dev, NULL, sony_play_effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sony_destroy_ff(struct hid_device *hdev)
|
||||||
|
{
|
||||||
|
struct sony_sc *sc = hid_get_drvdata(hdev);
|
||||||
|
|
||||||
|
cancel_work_sync(&sc->rumble_worker);
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
static int sony_init_ff(struct hid_device *hdev)
|
static int sony_init_ff(struct hid_device *hdev)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sony_destroy_ff(struct hid_device *hdev)
|
||||||
|
{
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||||
|
@ -728,6 +757,8 @@ static void sony_remove(struct hid_device *hdev)
|
||||||
if (sc->quirks & BUZZ_CONTROLLER)
|
if (sc->quirks & BUZZ_CONTROLLER)
|
||||||
buzz_remove(hdev);
|
buzz_remove(hdev);
|
||||||
|
|
||||||
|
sony_destroy_ff(hdev);
|
||||||
|
|
||||||
hid_hw_stop(hdev);
|
hid_hw_stop(hdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue