Skip to content

Commit 0ba809f

Browse files
marcanjannau
authored andcommitted
HID: magicmouse: Handle touch controller resets on SPI devices
On at least some SPI devices (e.g. recent Apple Silicon machines), the Broadcom touch controller is prone to crashing. When this happens, the STM eventually notices and resets it. It then notifies the driver via HID report 0x60, and the driver needs to re-enable MT mode to make things work again. This poses an additional issue: the hidinput core will close the low-level transport while the device is closed, which can cause us to miss a reset notification. To fix this, override the input open/close callbacks and send the MT enable every time the HID device is opened, instead of only once on probe. This should increase general robustness, even if the reset mechanism doesn't work for some reason, so it's worth doing it for USB devices too. MTP devices are exempt since they do not require the MT enable at all. Signed-off-by: Hector Martin <marcan@marcan.st>
1 parent 5945bf3 commit 0ba809f

1 file changed

Lines changed: 133 additions & 70 deletions

File tree

drivers/hid/hid-magicmouse.c

Lines changed: 133 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
6060
#define MOUSE2_REPORT_ID 0x12
6161
#define DOUBLE_REPORT_ID 0xf7
6262
#define SPI_REPORT_ID 0x02
63+
#define SPI_RESET_REPORT_ID 0x60
6364
#define MTP_REPORT_ID 0x75
6465
#define USB_BATTERY_TIMEOUT_MS 60000
6566

@@ -173,6 +174,97 @@ struct magicmouse_sc {
173174
struct magicmouse_input_ops input_ops;
174175
};
175176

177+
static int magicmouse_enable_multitouch(struct hid_device *hdev)
178+
{
179+
const u8 *feature;
180+
const u8 feature_mt[] = { 0xD7, 0x01 };
181+
const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 };
182+
const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
183+
const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
184+
u8 *buf;
185+
int ret;
186+
int feature_size;
187+
188+
if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
189+
if (hdev->vendor == BT_VENDOR_ID_APPLE) {
190+
feature_size = sizeof(feature_mt_trackpad2_bt);
191+
feature = feature_mt_trackpad2_bt;
192+
} else { /* USB_VENDOR_ID_APPLE */
193+
feature_size = sizeof(feature_mt_trackpad2_usb);
194+
feature = feature_mt_trackpad2_usb;
195+
}
196+
} else if (hdev->vendor == SPI_VENDOR_ID_APPLE) {
197+
feature_size = sizeof(feature_mt_trackpad2_usb);
198+
feature = feature_mt_trackpad2_usb;
199+
} else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
200+
feature_size = sizeof(feature_mt_mouse2);
201+
feature = feature_mt_mouse2;
202+
} else {
203+
feature_size = sizeof(feature_mt);
204+
feature = feature_mt;
205+
}
206+
207+
buf = kmemdup(feature, feature_size, GFP_KERNEL);
208+
if (!buf)
209+
return -ENOMEM;
210+
211+
ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
212+
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
213+
kfree(buf);
214+
return ret;
215+
}
216+
217+
static void magicmouse_enable_mt_work(struct work_struct *work)
218+
{
219+
struct magicmouse_sc *msc =
220+
container_of(work, struct magicmouse_sc, work.work);
221+
int ret;
222+
223+
ret = magicmouse_enable_multitouch(msc->hdev);
224+
if (ret < 0)
225+
hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
226+
}
227+
228+
static int magicmouse_open(struct input_dev *dev)
229+
{
230+
struct hid_device *hdev = input_get_drvdata(dev);
231+
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
232+
int ret;
233+
234+
ret = hid_hw_open(hdev);
235+
if (ret)
236+
return ret;
237+
238+
/*
239+
* Some devices repond with 'invalid report id' when feature
240+
* report switching it into multitouch mode is sent to it.
241+
*
242+
* This results in -EIO from the _raw low-level transport callback,
243+
* but there seems to be no other way of switching the mode.
244+
* Thus the super-ugly hacky success check below.
245+
*/
246+
ret = magicmouse_enable_multitouch(hdev);
247+
if (ret == -EIO && hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
248+
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
249+
return 0;
250+
}
251+
if (ret < 0)
252+
hid_err(hdev, "unable to request touch data (%d)\n", ret);
253+
254+
/*
255+
* MT enable is usually not required after the first time, so don't
256+
* consider it fatal.
257+
*/
258+
return 0;
259+
}
260+
261+
static void magicmouse_close(struct input_dev *dev)
262+
{
263+
struct hid_device *hdev = input_get_drvdata(dev);
264+
265+
hid_hw_close(hdev);
266+
}
267+
176268
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
177269
{
178270
int touch = -1;
@@ -694,12 +786,19 @@ static int magicmouse_raw_event_mtp(struct hid_device *hdev,
694786
static int magicmouse_raw_event_spi(struct hid_device *hdev,
695787
struct hid_report *report, u8 *data, int size)
696788
{
789+
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
697790
const size_t hdr_sz = sizeof(struct tp_mouse_report);
698791

699-
if (size < hdr_sz)
792+
if (!size)
700793
return 0;
701794

702-
if (data[0] != TRACKPAD2_USB_REPORT_ID)
795+
if (data[0] == SPI_RESET_REPORT_ID) {
796+
hid_info(hdev, "Touch controller was reset, re-enabling touch mode\n");
797+
schedule_delayed_work(&msc->work, msecs_to_jiffies(10));
798+
return 1;
799+
}
800+
801+
if (data[0] != TRACKPAD2_USB_REPORT_ID || size < hdr_sz)
703802
return 0;
704803

705804
return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz);
@@ -881,10 +980,17 @@ static int magicmouse_setup_input_usb(struct input_dev *input,
881980
*/
882981
__clear_bit(EV_REP, input->evbit);
883982

983+
/*
984+
* This isn't strictly speaking needed for USB, but enabling MT on
985+
* device open is probably more robust than only doing it once on probe
986+
* even if USB devices are not known to suffer from the SPI reset issue.
987+
*/
988+
input->open = magicmouse_open;
989+
input->close = magicmouse_close;
884990
return 0;
885991
}
886992

887-
static int magicmouse_setup_input_spi(struct input_dev *input,
993+
static int magicmouse_setup_input_mtp(struct input_dev *input,
888994
struct hid_device *hdev)
889995
{
890996
int error;
@@ -957,6 +1063,25 @@ static int magicmouse_setup_input_spi(struct input_dev *input,
9571063
return 0;
9581064
}
9591065

1066+
static int magicmouse_setup_input_spi(struct input_dev *input,
1067+
struct hid_device *hdev)
1068+
{
1069+
int ret = magicmouse_setup_input_mtp(input, hdev);
1070+
if (ret)
1071+
return ret;
1072+
1073+
/*
1074+
* Override the default input->open function to send the MT
1075+
* enable every time the device is opened. This ensures it works
1076+
* even if we missed a reset event due to the device being closed.
1077+
* input->close is overridden for symmetry.
1078+
*/
1079+
input->open = magicmouse_open;
1080+
input->close = magicmouse_close;
1081+
1082+
return 0;
1083+
}
1084+
9601085
static int magicmouse_input_mapping(struct hid_device *hdev,
9611086
struct hid_input *hi, struct hid_field *field,
9621087
struct hid_usage *usage, unsigned long **bit, int *max)
@@ -993,57 +1118,6 @@ static int magicmouse_input_configured(struct hid_device *hdev,
9931118
return 0;
9941119
}
9951120

996-
static int magicmouse_enable_multitouch(struct hid_device *hdev)
997-
{
998-
const u8 *feature;
999-
const u8 feature_mt[] = { 0xD7, 0x01 };
1000-
const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 };
1001-
const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
1002-
const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
1003-
u8 *buf;
1004-
int ret;
1005-
int feature_size;
1006-
1007-
if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
1008-
if (hdev->vendor == BT_VENDOR_ID_APPLE) {
1009-
feature_size = sizeof(feature_mt_trackpad2_bt);
1010-
feature = feature_mt_trackpad2_bt;
1011-
} else { /* USB_VENDOR_ID_APPLE */
1012-
feature_size = sizeof(feature_mt_trackpad2_usb);
1013-
feature = feature_mt_trackpad2_usb;
1014-
}
1015-
} else if (hdev->vendor == SPI_VENDOR_ID_APPLE) {
1016-
feature_size = sizeof(feature_mt_trackpad2_usb);
1017-
feature = feature_mt_trackpad2_usb;
1018-
} else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
1019-
feature_size = sizeof(feature_mt_mouse2);
1020-
feature = feature_mt_mouse2;
1021-
} else {
1022-
feature_size = sizeof(feature_mt);
1023-
feature = feature_mt;
1024-
}
1025-
1026-
buf = kmemdup(feature, feature_size, GFP_KERNEL);
1027-
if (!buf)
1028-
return -ENOMEM;
1029-
1030-
ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
1031-
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
1032-
kfree(buf);
1033-
return ret;
1034-
}
1035-
1036-
static void magicmouse_enable_mt_work(struct work_struct *work)
1037-
{
1038-
struct magicmouse_sc *msc =
1039-
container_of(work, struct magicmouse_sc, work.work);
1040-
int ret;
1041-
1042-
ret = magicmouse_enable_multitouch(msc->hdev);
1043-
if (ret < 0)
1044-
hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
1045-
}
1046-
10471121
static int magicmouse_fetch_battery(struct hid_device *hdev)
10481122
{
10491123
#ifdef CONFIG_HID_BATTERY_STRENGTH
@@ -1103,7 +1177,7 @@ static int magicmouse_probe(struct hid_device *hdev,
11031177
// conflicts with the report ID.
11041178
if (id->bus == BUS_HOST) {
11051179
msc->input_ops.raw_event = magicmouse_raw_event_mtp;
1106-
msc->input_ops.setup_input = magicmouse_setup_input_spi;
1180+
msc->input_ops.setup_input = magicmouse_setup_input_mtp;
11071181
} else if (id->bus == BUS_SPI) {
11081182
msc->input_ops.raw_event = magicmouse_raw_event_spi;
11091183
msc->input_ops.setup_input = magicmouse_setup_input_spi;
@@ -1183,21 +1257,10 @@ static int magicmouse_probe(struct hid_device *hdev,
11831257
if (id->bus == BUS_HOST)
11841258
return 0;
11851259

1186-
/*
1187-
* Some devices repond with 'invalid report id' when feature
1188-
* report switching it into multitouch mode is sent to it.
1189-
*
1190-
* This results in -EIO from the _raw low-level transport callback,
1191-
* but there seems to be no other way of switching the mode.
1192-
* Thus the super-ugly hacky success check below.
1193-
*/
1194-
ret = magicmouse_enable_multitouch(hdev);
1195-
if (ret != -EIO && ret < 0) {
1196-
hid_err(hdev, "unable to request touch data (%d)\n", ret);
1197-
goto err_stop_hw;
1198-
}
1199-
if (ret == -EIO && id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
1200-
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
1260+
/* SPI devices need to watch for reset events to re-send the MT enable */
1261+
if (id->bus == BUS_SPI) {
1262+
report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_RESET_REPORT_ID, 0);
1263+
report->size = 2;
12011264
}
12021265

12031266
return 0;

0 commit comments

Comments
 (0)