diff options
| author | David Herrmann <dh.herrmann@gmail.com> | 2013-10-02 07:47:28 -0400 |
|---|---|---|
| committer | Jiri Kosina <jkosina@suse.cz> | 2013-10-07 11:08:26 -0400 |
| commit | f50f9aabf32db7414551ffdfdccc71be5f3d6e7d (patch) | |
| tree | 1d624ee07097e1548551f77dfa537cff6400619b | |
| parent | 7da7cbbbeb60e0939fecdf9723b388136c175e5c (diff) | |
HID: wiimote: fix FF deadlock
The input core has an internal spinlock that is acquired during event
injection via input_event() and friends but also held during FF callbacks.
That means, there is no way to share a lock between event-injection and FF
handling. Unfortunately, this is what is required for wiimote state
tracking and what we do with state.lock and input->lock.
This deadlock can be triggered when using continuous data reporting and FF
on a wiimote device at the same time. I takes me at least 30m of
stress-testing to trigger it but users reported considerably shorter
times (http://bpaste.net/show/132504/) when using some gaming-console
emulators.
The real problem is that we have two copies of internal state, one in the
wiimote objects and the other in the input device. As the input-lock is
not supposed to be accessed from outside of input-core, we have no other
chance than offloading FF handling into a worker. This actually works
pretty nice and also allows to implictly merge fast rumble changes into a
single request.
Due to the 3-layered workers (rumble+queue+l2cap) this might reduce FF
responsiveness. Initial tests were fine so lets fix the race first and if
it turns out to be too slow we can always handle FF out-of-band and skip
the queue-worker.
Cc: <stable@vger.kernel.org> # 3.11+
Reported-by: Thomas Schneider
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
| -rw-r--r-- | drivers/hid/hid-wiimote-modules.c | 40 | ||||
| -rw-r--r-- | drivers/hid/hid-wiimote.h | 4 |
2 files changed, 32 insertions, 12 deletions
diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c index 2e7d644dba18..71adf9e60b13 100644 --- a/drivers/hid/hid-wiimote-modules.c +++ b/drivers/hid/hid-wiimote-modules.c | |||
| @@ -119,12 +119,22 @@ static const struct wiimod_ops wiimod_keys = { | |||
| 119 | * the rumble motor, this flag shouldn't be set. | 119 | * the rumble motor, this flag shouldn't be set. |
| 120 | */ | 120 | */ |
| 121 | 121 | ||
| 122 | /* used by wiimod_rumble and wiipro_rumble */ | ||
| 123 | static void wiimod_rumble_worker(struct work_struct *work) | ||
| 124 | { | ||
| 125 | struct wiimote_data *wdata = container_of(work, struct wiimote_data, | ||
| 126 | rumble_worker); | ||
| 127 | |||
| 128 | spin_lock_irq(&wdata->state.lock); | ||
| 129 | wiiproto_req_rumble(wdata, wdata->state.cache_rumble); | ||
| 130 | spin_unlock_irq(&wdata->state.lock); | ||
| 131 | } | ||
| 132 | |||
| 122 | static int wiimod_rumble_play(struct input_dev *dev, void *data, | 133 | static int wiimod_rumble_play(struct input_dev *dev, void *data, |
| 123 | struct ff_effect *eff) | 134 | struct ff_effect *eff) |
| 124 | { | 135 | { |
| 125 | struct wiimote_data *wdata = input_get_drvdata(dev); | 136 | struct wiimote_data *wdata = input_get_drvdata(dev); |
| 126 | __u8 value; | 137 | __u8 value; |
| 127 | unsigned long flags; | ||
| 128 | 138 | ||
| 129 | /* | 139 | /* |
| 130 | * The wiimote supports only a single rumble motor so if any magnitude | 140 | * The wiimote supports only a single rumble motor so if any magnitude |
| @@ -137,9 +147,10 @@ static int wiimod_rumble_play(struct input_dev *dev, void *data, | |||
| 137 | else | 147 | else |
| 138 | value = 0; | 148 | value = 0; |
| 139 | 149 | ||
| 140 | spin_lock_irqsave(&wdata->state.lock, flags); | 150 | /* Locking state.lock here might deadlock with input_event() calls. |
| 141 | wiiproto_req_rumble(wdata, value); | 151 | * schedule_work acts as barrier. Merging multiple changes is fine. */ |
| 142 | spin_unlock_irqrestore(&wdata->state.lock, flags); | 152 | wdata->state.cache_rumble = value; |
| 153 | schedule_work(&wdata->rumble_worker); | ||
| 143 | 154 | ||
| 144 | return 0; | 155 | return 0; |
| 145 | } | 156 | } |
| @@ -147,6 +158,8 @@ static int wiimod_rumble_play(struct input_dev *dev, void *data, | |||
| 147 | static int wiimod_rumble_probe(const struct wiimod_ops *ops, | 158 | static int wiimod_rumble_probe(const struct wiimod_ops *ops, |
| 148 | struct wiimote_data *wdata) | 159 | struct wiimote_data *wdata) |
| 149 | { | 160 | { |
| 161 | INIT_WORK(&wdata->rumble_worker, wiimod_rumble_worker); | ||
| 162 | |||
| 150 | set_bit(FF_RUMBLE, wdata->input->ffbit); | 163 | set_bit(FF_RUMBLE, wdata->input->ffbit); |
| 151 | if (input_ff_create_memless(wdata->input, NULL, wiimod_rumble_play)) | 164 | if (input_ff_create_memless(wdata->input, NULL, wiimod_rumble_play)) |
| 152 | return -ENOMEM; | 165 | return -ENOMEM; |
| @@ -159,6 +172,8 @@ static void wiimod_rumble_remove(const struct wiimod_ops *ops, | |||
| 159 | { | 172 | { |
| 160 | unsigned long flags; | 173 | unsigned long flags; |
| 161 | 174 | ||
| 175 | cancel_work_sync(&wdata->rumble_worker); | ||
| 176 | |||
| 162 | spin_lock_irqsave(&wdata->state.lock, flags); | 177 | spin_lock_irqsave(&wdata->state.lock, flags); |
| 163 | wiiproto_req_rumble(wdata, 0); | 178 | wiiproto_req_rumble(wdata, 0); |
| 164 | spin_unlock_irqrestore(&wdata->state.lock, flags); | 179 | spin_unlock_irqrestore(&wdata->state.lock, flags); |
| @@ -1731,7 +1746,6 @@ static int wiimod_pro_play(struct input_dev *dev, void *data, | |||
| 1731 | { | 1746 | { |
| 1732 | struct wiimote_data *wdata = input_get_drvdata(dev); | 1747 | struct wiimote_data *wdata = input_get_drvdata(dev); |
| 1733 | __u8 value; | 1748 | __u8 value; |
| 1734 | unsigned long flags; | ||
| 1735 | 1749 | ||
| 1736 | /* | 1750 | /* |
| 1737 | * The wiimote supports only a single rumble motor so if any magnitude | 1751 | * The wiimote supports only a single rumble motor so if any magnitude |
| @@ -1744,9 +1758,10 @@ static int wiimod_pro_play(struct input_dev *dev, void *data, | |||
| 1744 | else | 1758 | else |
| 1745 | value = 0; | 1759 | value = 0; |
| 1746 | 1760 | ||
| 1747 | spin_lock_irqsave(&wdata->state.lock, flags); | 1761 | /* Locking state.lock here might deadlock with input_event() calls. |
| 1748 | wiiproto_req_rumble(wdata, value); | 1762 | * schedule_work acts as barrier. Merging multiple changes is fine. */ |
| 1749 | spin_unlock_irqrestore(&wdata->state.lock, flags); | 1763 | wdata->state.cache_rumble = value; |
| 1764 | schedule_work(&wdata->rumble_worker); | ||
| 1750 | 1765 | ||
| 1751 | return 0; | 1766 | return 0; |
| 1752 | } | 1767 | } |
| @@ -1756,6 +1771,8 @@ static int wiimod_pro_probe(const struct wiimod_ops *ops, | |||
| 1756 | { | 1771 | { |
| 1757 | int ret, i; | 1772 | int ret, i; |
| 1758 | 1773 | ||
| 1774 | INIT_WORK(&wdata->rumble_worker, wiimod_rumble_worker); | ||
| 1775 | |||
| 1759 | wdata->extension.input = input_allocate_device(); | 1776 | wdata->extension.input = input_allocate_device(); |
| 1760 | if (!wdata->extension.input) | 1777 | if (!wdata->extension.input) |
| 1761 | return -ENOMEM; | 1778 | return -ENOMEM; |
| @@ -1817,12 +1834,13 @@ static void wiimod_pro_remove(const struct wiimod_ops *ops, | |||
| 1817 | if (!wdata->extension.input) | 1834 | if (!wdata->extension.input) |
| 1818 | return; | 1835 | return; |
| 1819 | 1836 | ||
| 1837 | input_unregister_device(wdata->extension.input); | ||
| 1838 | wdata->extension.input = NULL; | ||
| 1839 | cancel_work_sync(&wdata->rumble_worker); | ||
| 1840 | |||
| 1820 | spin_lock_irqsave(&wdata->state.lock, flags); | 1841 | spin_lock_irqsave(&wdata->state.lock, flags); |
| 1821 | wiiproto_req_rumble(wdata, 0); | 1842 | wiiproto_req_rumble(wdata, 0); |
| 1822 | spin_unlock_irqrestore(&wdata->state.lock, flags); | 1843 | spin_unlock_irqrestore(&wdata->state.lock, flags); |
| 1823 | |||
| 1824 | input_unregister_device(wdata->extension.input); | ||
| 1825 | wdata->extension.input = NULL; | ||
| 1826 | } | 1844 | } |
| 1827 | 1845 | ||
| 1828 | static const struct wiimod_ops wiimod_pro = { | 1846 | static const struct wiimod_ops wiimod_pro = { |
diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h index f1474f372c0b..75db0c400037 100644 --- a/drivers/hid/hid-wiimote.h +++ b/drivers/hid/hid-wiimote.h | |||
| @@ -133,13 +133,15 @@ struct wiimote_state { | |||
| 133 | __u8 *cmd_read_buf; | 133 | __u8 *cmd_read_buf; |
| 134 | __u8 cmd_read_size; | 134 | __u8 cmd_read_size; |
| 135 | 135 | ||
| 136 | /* calibration data */ | 136 | /* calibration/cache data */ |
| 137 | __u16 calib_bboard[4][3]; | 137 | __u16 calib_bboard[4][3]; |
| 138 | __u8 cache_rumble; | ||
| 138 | }; | 139 | }; |
| 139 | 140 | ||
| 140 | struct wiimote_data { | 141 | struct wiimote_data { |
| 141 | struct hid_device *hdev; | 142 | struct hid_device *hdev; |
| 142 | struct input_dev *input; | 143 | struct input_dev *input; |
| 144 | struct work_struct rumble_worker; | ||
| 143 | struct led_classdev *leds[4]; | 145 | struct led_classdev *leds[4]; |
| 144 | struct input_dev *accel; | 146 | struct input_dev *accel; |
| 145 | struct input_dev *ir; | 147 | struct input_dev *ir; |
