Skip to content

Commit 972dabd

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 a699164 commit 972dabd

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
@@ -61,6 +61,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
6161
#define MOUSE2_REPORT_ID 0x12
6262
#define DOUBLE_REPORT_ID 0xf7
6363
#define SPI_REPORT_ID 0x02
64+
#define SPI_RESET_REPORT_ID 0x60
6465
#define MTP_REPORT_ID 0x75
6566
#define USB_BATTERY_TIMEOUT_MS 60000
6667

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

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

711-
if (size < hdr_sz)
805+
if (!size)
712806
return 0;
713807

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

717817
return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz);
@@ -901,10 +1001,17 @@ static int magicmouse_setup_input_usb(struct input_dev *input,
9011001
*/
9021002
__clear_bit(EV_REP, input->evbit);
9031003

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

907-
static int magicmouse_setup_input_spi(struct input_dev *input,
1014+
static int magicmouse_setup_input_mtp(struct input_dev *input,
9081015
struct hid_device *hdev)
9091016
{
9101017
int error;
@@ -977,6 +1084,25 @@ static int magicmouse_setup_input_spi(struct input_dev *input,
9771084
return 0;
9781085
}
9791086

1087+
static int magicmouse_setup_input_spi(struct input_dev *input,
1088+
struct hid_device *hdev)
1089+
{
1090+
int ret = magicmouse_setup_input_mtp(input, hdev);
1091+
if (ret)
1092+
return ret;
1093+
1094+
/*
1095+
* Override the default input->open function to send the MT
1096+
* enable every time the device is opened. This ensures it works
1097+
* even if we missed a reset event due to the device being closed.
1098+
* input->close is overridden for symmetry.
1099+
*/
1100+
input->open = magicmouse_open;
1101+
input->close = magicmouse_close;
1102+
1103+
return 0;
1104+
}
1105+
9801106
static int magicmouse_input_mapping(struct hid_device *hdev,
9811107
struct hid_input *hi, struct hid_field *field,
9821108
struct hid_usage *usage, unsigned long **bit, int *max)
@@ -1015,58 +1141,6 @@ static int magicmouse_input_configured(struct hid_device *hdev,
10151141
return 0;
10161142
}
10171143

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

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

12301293
return 0;

0 commit comments

Comments
 (0)