Skip to content

Commit 4c8feb2

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 bf5ed82 commit 4c8feb2

1 file changed

Lines changed: 134 additions & 71 deletions

File tree

drivers/hid/hid-magicmouse.c

Lines changed: 134 additions & 71 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

@@ -175,6 +176,98 @@ struct magicmouse_sc {
175176
struct magicmouse_input_ops input_ops;
176177
};
177178

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

710-
if (size < hdr_sz)
804+
if (!size)
711805
return 0;
712806

713-
if (data[0] != TRACKPAD2_USB_REPORT_ID)
807+
if (data[0] == SPI_RESET_REPORT_ID) {
808+
hid_info(hdev, "Touch controller was reset, re-enabling touch mode\n");
809+
schedule_delayed_work(&msc->work, msecs_to_jiffies(10));
810+
return 1;
811+
}
812+
813+
if (data[0] != TRACKPAD2_USB_REPORT_ID || size < hdr_sz)
714814
return 0;
715815

716816
return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz);
@@ -897,10 +997,17 @@ static int magicmouse_setup_input_usb(struct input_dev *input,
897997
*/
898998
__clear_bit(EV_REP, input->evbit);
899999

1000+
/*
1001+
* This isn't strictly speaking needed for USB, but enabling MT on
1002+
* device open is probably more robust than only doing it once on probe
1003+
* even if USB devices are not known to suffer from the SPI reset issue.
1004+
*/
1005+
input->open = magicmouse_open;
1006+
input->close = magicmouse_close;
9001007
return 0;
9011008
}
9021009

903-
static int magicmouse_setup_input_spi(struct input_dev *input,
1010+
static int magicmouse_setup_input_mtp(struct input_dev *input,
9041011
struct hid_device *hdev)
9051012
{
9061013
int error;
@@ -973,6 +1080,25 @@ static int magicmouse_setup_input_spi(struct input_dev *input,
9731080
return 0;
9741081
}
9751082

1083+
static int magicmouse_setup_input_spi(struct input_dev *input,
1084+
struct hid_device *hdev)
1085+
{
1086+
int ret = magicmouse_setup_input_mtp(input, hdev);
1087+
if (ret)
1088+
return ret;
1089+
1090+
/*
1091+
* Override the default input->open function to send the MT
1092+
* enable every time the device is opened. This ensures it works
1093+
* even if we missed a reset event due to the device being closed.
1094+
* input->close is overridden for symmetry.
1095+
*/
1096+
input->open = magicmouse_open;
1097+
input->close = magicmouse_close;
1098+
1099+
return 0;
1100+
}
1101+
9761102
static int magicmouse_input_mapping(struct hid_device *hdev,
9771103
struct hid_input *hi, struct hid_field *field,
9781104
struct hid_usage *usage, unsigned long **bit, int *max)
@@ -1011,58 +1137,6 @@ static int magicmouse_input_configured(struct hid_device *hdev,
10111137
return 0;
10121138
}
10131139

1014-
static int magicmouse_enable_multitouch(struct hid_device *hdev)
1015-
{
1016-
const u8 *feature;
1017-
const u8 feature_mt[] = { 0xD7, 0x01 };
1018-
const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 };
1019-
const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
1020-
const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
1021-
u8 *buf;
1022-
int ret;
1023-
int feature_size;
1024-
1025-
if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
1026-
hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
1027-
if (hdev->vendor == BT_VENDOR_ID_APPLE) {
1028-
feature_size = sizeof(feature_mt_trackpad2_bt);
1029-
feature = feature_mt_trackpad2_bt;
1030-
} else { /* USB_VENDOR_ID_APPLE */
1031-
feature_size = sizeof(feature_mt_trackpad2_usb);
1032-
feature = feature_mt_trackpad2_usb;
1033-
}
1034-
} else if (hdev->vendor == SPI_VENDOR_ID_APPLE) {
1035-
feature_size = sizeof(feature_mt_trackpad2_usb);
1036-
feature = feature_mt_trackpad2_usb;
1037-
} else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
1038-
feature_size = sizeof(feature_mt_mouse2);
1039-
feature = feature_mt_mouse2;
1040-
} else {
1041-
feature_size = sizeof(feature_mt);
1042-
feature = feature_mt;
1043-
}
1044-
1045-
buf = kmemdup(feature, feature_size, GFP_KERNEL);
1046-
if (!buf)
1047-
return -ENOMEM;
1048-
1049-
ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
1050-
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
1051-
kfree(buf);
1052-
return ret;
1053-
}
1054-
1055-
static void magicmouse_enable_mt_work(struct work_struct *work)
1056-
{
1057-
struct magicmouse_sc *msc =
1058-
container_of(work, struct magicmouse_sc, work.work);
1059-
int ret;
1060-
1061-
ret = magicmouse_enable_multitouch(msc->hdev);
1062-
if (ret < 0)
1063-
hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
1064-
}
1065-
10661140
static int magicmouse_fetch_battery(struct hid_device *hdev)
10671141
{
10681142
#ifdef CONFIG_HID_BATTERY_STRENGTH
@@ -1123,7 +1197,7 @@ static int magicmouse_probe(struct hid_device *hdev,
11231197
// conflicts with the report ID.
11241198
if (id->bus == BUS_HOST) {
11251199
msc->input_ops.raw_event = magicmouse_raw_event_mtp;
1126-
msc->input_ops.setup_input = magicmouse_setup_input_spi;
1200+
msc->input_ops.setup_input = magicmouse_setup_input_mtp;
11271201
} else if (id->bus == BUS_SPI) {
11281202
msc->input_ops.raw_event = magicmouse_raw_event_spi;
11291203
msc->input_ops.setup_input = magicmouse_setup_input_spi;
@@ -1206,21 +1280,10 @@ static int magicmouse_probe(struct hid_device *hdev,
12061280
if (id->bus == BUS_HOST)
12071281
return 0;
12081282

1209-
/*
1210-
* Some devices repond with 'invalid report id' when feature
1211-
* report switching it into multitouch mode is sent to it.
1212-
*
1213-
* This results in -EIO from the _raw low-level transport callback,
1214-
* but there seems to be no other way of switching the mode.
1215-
* Thus the super-ugly hacky success check below.
1216-
*/
1217-
ret = magicmouse_enable_multitouch(hdev);
1218-
if (ret != -EIO && ret < 0) {
1219-
hid_err(hdev, "unable to request touch data (%d)\n", ret);
1220-
goto err_stop_hw;
1221-
}
1222-
if (ret == -EIO && id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
1223-
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
1283+
/* SPI devices need to watch for reset events to re-send the MT enable */
1284+
if (id->bus == BUS_SPI) {
1285+
report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_RESET_REPORT_ID, 0);
1286+
report->size = 2;
12241287
}
12251288

12261289
return 0;

0 commit comments

Comments
 (0)