Skip to content

Commit 261daa2

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 e8b4ee3 commit 261daa2

1 file changed

Lines changed: 87 additions & 21 deletions

File tree

drivers/hid/hid-magicmouse.c

Lines changed: 87 additions & 21 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_SEC 60
6667

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

180+
static int magicmouse_enable_multitouch(struct hid_device *hdev);
181+
182+
static int magicmouse_open(struct input_dev *dev)
183+
{
184+
struct hid_device *hdev = input_get_drvdata(dev);
185+
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
186+
int ret;
187+
188+
ret = hid_hw_open(hdev);
189+
if (ret)
190+
return ret;
191+
192+
/*
193+
* Some devices repond with 'invalid report id' when feature
194+
* report switching it into multitouch mode is sent to it.
195+
*
196+
* This results in -EIO from the _raw low-level transport callback,
197+
* but there seems to be no other way of switching the mode.
198+
* Thus the super-ugly hacky success check below.
199+
*/
200+
ret = magicmouse_enable_multitouch(hdev);
201+
if (ret != -EIO && ret < 0) {
202+
hid_err(hdev, "unable to request touch data (%d)\n", ret);
203+
return ret;
204+
}
205+
if (ret == -EIO && (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
206+
hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) {
207+
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
208+
}
209+
210+
/*
211+
* MT enable is usually not required after the first time, so don't
212+
* consider it fatal.
213+
*/
214+
return 0;
215+
}
216+
217+
static void magicmouse_close(struct input_dev *dev)
218+
{
219+
struct hid_device *hdev = input_get_drvdata(dev);
220+
221+
hid_hw_close(hdev);
222+
}
223+
179224
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
180225
{
181226
int touch = -1;
@@ -706,12 +751,19 @@ static int magicmouse_raw_event_mtp(struct hid_device *hdev,
706751
static int magicmouse_raw_event_spi(struct hid_device *hdev,
707752
struct hid_report *report, u8 *data, int size)
708753
{
754+
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
709755
const size_t hdr_sz = sizeof(struct tp_mouse_report);
710756

711-
if (size < hdr_sz)
757+
if (!size)
712758
return 0;
713759

714-
if (data[0] != SPI_REPORT_ID)
760+
if (data[0] == SPI_RESET_REPORT_ID) {
761+
hid_info(hdev, "Touch controller was reset, re-enabling touch mode\n");
762+
schedule_delayed_work(&msc->work, msecs_to_jiffies(10));
763+
return 1;
764+
}
765+
766+
if (data[0] != SPI_REPORT_ID || size < hdr_sz)
715767
return 0;
716768

717769
return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz);
@@ -904,10 +956,17 @@ static int magicmouse_setup_input_usb(struct input_dev *input,
904956
*/
905957
__clear_bit(EV_REP, input->evbit);
906958

959+
/*
960+
* This isn't strictly speaking needed for USB, but enabling MT on
961+
* device open is probably more robust than only doing it once on probe
962+
* even if USB devices are not known to suffer from the SPI reset issue.
963+
*/
964+
input->open = magicmouse_open;
965+
input->close = magicmouse_close;
907966
return 0;
908967
}
909968

910-
static int magicmouse_setup_input_spi(struct input_dev *input,
969+
static int magicmouse_setup_input_mtp(struct input_dev *input,
911970
struct hid_device *hdev)
912971
{
913972
int error;
@@ -980,6 +1039,25 @@ static int magicmouse_setup_input_spi(struct input_dev *input,
9801039
return 0;
9811040
}
9821041

1042+
static int magicmouse_setup_input_spi(struct input_dev *input,
1043+
struct hid_device *hdev)
1044+
{
1045+
int ret = magicmouse_setup_input_mtp(input, hdev);
1046+
if (ret)
1047+
return ret;
1048+
1049+
/*
1050+
* Override the default input->open function to send the MT
1051+
* enable every time the device is opened. This ensures it works
1052+
* even if we missed a reset event due to the device being closed.
1053+
* input->close is overridden for symmetry.
1054+
*/
1055+
input->open = magicmouse_open;
1056+
input->close = magicmouse_close;
1057+
1058+
return 0;
1059+
}
1060+
9831061
static int magicmouse_input_mapping(struct hid_device *hdev,
9841062
struct hid_input *hi, struct hid_field *field,
9851063
struct hid_usage *usage, unsigned long **bit, int *max)
@@ -1041,7 +1119,7 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev)
10411119
feature_size = sizeof(feature_mt_trackpad2_bt);
10421120
feature = feature_mt_trackpad2_bt;
10431121
break;
1044-
default: /* USB_VENDOR_ID_APPLE || SPI_VENDOR_ID_APPLE */
1122+
default: /* USB_VENDOR_ID_APPLE || SPI_VENDOR_ID_APPLE */
10451123
feature_size = sizeof(feature_mt_trackpad2_usb);
10461124
feature = feature_mt_trackpad2_usb;
10471125
}
@@ -1152,7 +1230,7 @@ static int magicmouse_probe(struct hid_device *hdev,
11521230
// conflicts with the report ID.
11531231
if (id->bus == BUS_HOST) {
11541232
msc->input_ops.raw_event = magicmouse_raw_event_mtp;
1155-
msc->input_ops.setup_input = magicmouse_setup_input_spi;
1233+
msc->input_ops.setup_input = magicmouse_setup_input_mtp;
11561234
} else if (id->bus == BUS_SPI) {
11571235
msc->input_ops.raw_event = magicmouse_raw_event_spi;
11581236
msc->input_ops.setup_input = magicmouse_setup_input_spi;
@@ -1246,22 +1324,10 @@ static int magicmouse_probe(struct hid_device *hdev,
12461324
if (id->bus == BUS_HOST)
12471325
return 0;
12481326

1249-
/*
1250-
* Some devices repond with 'invalid report id' when feature
1251-
* report switching it into multitouch mode is sent to it.
1252-
*
1253-
* This results in -EIO from the _raw low-level transport callback,
1254-
* but there seems to be no other way of switching the mode.
1255-
* Thus the super-ugly hacky success check below.
1256-
*/
1257-
ret = magicmouse_enable_multitouch(hdev);
1258-
if (ret != -EIO && ret < 0) {
1259-
hid_err(hdev, "unable to request touch data (%d)\n", ret);
1260-
goto err_stop_hw;
1261-
}
1262-
if (ret == -EIO && (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
1263-
id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) {
1264-
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
1327+
/* SPI devices need to watch for reset events to re-send the MT enable */
1328+
if (id->bus == BUS_SPI) {
1329+
report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_RESET_REPORT_ID, 0);
1330+
report->size = 2;
12651331
}
12661332

12671333
return 0;

0 commit comments

Comments
 (0)