Commit 9f323b68 authored by Sven Eckelmann's avatar Sven Eckelmann Committed by Jiri Kosina

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: default avatarSimon Wood <simon@mungewell.org>
Reported-by: default avatarDavid Herrmann <dh.herrmann@gmail.com>
Signed-off-by: default avatarSven Eckelmann <sven@narfation.org>
Signed-off-by: default avatarSimon Wood <simon@mungewell.org>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent e0da5c9a
...@@ -225,6 +225,13 @@ static const unsigned int buzz_keymap[] = { ...@@ -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) ...@@ -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, ...@@ -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;
buf[5] = left;
return hid->hid_output_raw_report(hid, buf, sizeof(buf), schedule_work(&sc->rumble_worker);
HID_OUTPUT_REPORT); return 0;
} }
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) ...@@ -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) ...@@ -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);
} }
......
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