[PATCH 2/3] HID: magicmouse: report battery over Bluetooth
From: Alec Hall <hidden>
Date: 2026-07-14 10:12:46
Also in:
lkml
Subsystem:
hid core layer, the rest · Maintainers:
Jiri Kosina, Benjamin Tissoires, Linus Torvalds
magicmouse_fetch_battery() only runs for USB Magic Mouse 2 / Magic Trackpad 2. Over Bluetooth the kernel never queries the battery and the power_supply reports 0%, even though the device answers a GET_REPORT for battery input report 0x90. (A Magic Trackpad 2 appears to work while charging only because it then pushes battery reports unsolicited; off the charger the reading goes stale.) As with hid-apple, the fetch cannot run from the battery timer over Bluetooth: hid_hw_request() sleeps on the uhid transport, but magicmouse_battery_timer_tick() runs in atomic softirq context. Move the periodic fetch to a delayed work item, add magicmouse_has_battery() and use it to arm the fetch for Magic Mouse 2 / Magic Trackpad 2 regardless of transport, and keep polling once full so the reading stays current over Bluetooth. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Alec Hall <redacted> --- drivers/hid/hid-magicmouse.c | 62 +++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 26 deletions(-)
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 802a3479e24b..6d0e76314b10 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c@@ -123,7 +123,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie * @tracking_ids: Mapping of current touch input data to @touches. * @hdev: Pointer to the underlying HID device. * @work: Workqueue to handle initialization retry for quirky devices. - * @battery_timer: Timer for obtaining battery level information. + * @battery_work: Delayed work for obtaining battery level information. */ struct magicmouse_sc { struct input_dev *input;
@@ -148,7 +148,7 @@ struct magicmouse_sc { struct hid_device *hdev; struct delayed_work work; - struct timer_list battery_timer; + struct delayed_work battery_work; }; static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -828,6 +828,19 @@ static bool is_usb_magictrackpad2(__u32 vendor, __u32 product) product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC; } +/* + * Magic Mouse 2 and Magic Trackpad 2 expose a battery over both USB and + * Bluetooth (the pre-2 Magic Mouse/Trackpad do not). Transport-agnostic: + * over Bluetooth the vendor is BT_VENDOR_ID_APPLE but the product id matches. + */ +static bool magicmouse_has_battery(struct hid_device *hdev) +{ + return hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 || + hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC || + hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 || + hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC; +} + static int magicmouse_fetch_battery(struct hid_device *hdev) { #ifdef CONFIG_HID_BATTERY_STRENGTH
@@ -836,9 +849,7 @@ static int magicmouse_fetch_battery(struct hid_device *hdev) struct hid_battery *bat; bat = hid_get_battery(hdev); - if (!bat || - (!is_usb_magicmouse2(hdev->vendor, hdev->product) && - !is_usb_magictrackpad2(hdev->vendor, hdev->product))) + if (!bat || !magicmouse_has_battery(hdev)) return -1; report_enum = &hdev->report_enum[bat->report_type];
@@ -847,9 +858,6 @@ static int magicmouse_fetch_battery(struct hid_device *hdev) if (!report || report->maxfield < 1) return -1; - if (bat->capacity == bat->max) - return -1; - hid_hw_request(hdev, report, HID_REQ_GET_REPORT); return 0; #else
@@ -857,15 +865,21 @@ static int magicmouse_fetch_battery(struct hid_device *hdev) #endif } -static void magicmouse_battery_timer_tick(struct timer_list *t) +static void magicmouse_battery_work(struct work_struct *work) { - struct magicmouse_sc *msc = timer_container_of(msc, t, battery_timer); + struct magicmouse_sc *msc = container_of(work, struct magicmouse_sc, + battery_work.work); struct hid_device *hdev = msc->hdev; - if (magicmouse_fetch_battery(hdev) == 0) { - mod_timer(&msc->battery_timer, - jiffies + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC)); - } + /* + * Runs in process context (workqueue), so the battery GET_REPORT may + * sleep. This is required for the uhid/Bluetooth transport, whose + * raw_request blocks on userspace -- unlike a timer_list callback, + * which runs in atomic softirq context and would deadlock. + */ + if (magicmouse_fetch_battery(hdev) == 0) + schedule_delayed_work(&msc->battery_work, + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC)); } static int magicmouse_probe(struct hid_device *hdev,
@@ -900,12 +914,10 @@ static int magicmouse_probe(struct hid_device *hdev, return ret; } - if (is_usb_magicmouse2(id->vendor, id->product) || - is_usb_magictrackpad2(id->vendor, id->product)) { - timer_setup(&msc->battery_timer, magicmouse_battery_timer_tick, 0); - mod_timer(&msc->battery_timer, - jiffies + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC)); - magicmouse_fetch_battery(hdev); + if (magicmouse_has_battery(hdev)) { + INIT_DELAYED_WORK(&msc->battery_work, magicmouse_battery_work); + /* Kick an initial fetch; the work re-arms itself each timeout. */ + schedule_delayed_work(&msc->battery_work, 0); } if (is_usb_magicmouse2(id->vendor, id->product) ||
@@ -973,9 +985,8 @@ static int magicmouse_probe(struct hid_device *hdev, return 0; err_stop_hw: - if (is_usb_magicmouse2(id->vendor, id->product) || - is_usb_magictrackpad2(id->vendor, id->product)) - timer_delete_sync(&msc->battery_timer); + if (magicmouse_has_battery(hdev)) + cancel_delayed_work_sync(&msc->battery_work); hid_hw_stop(hdev); return ret;
@@ -987,9 +998,8 @@ static void magicmouse_remove(struct hid_device *hdev) if (msc) { cancel_delayed_work_sync(&msc->work); - if (is_usb_magicmouse2(hdev->vendor, hdev->product) || - is_usb_magictrackpad2(hdev->vendor, hdev->product)) - timer_delete_sync(&msc->battery_timer); + if (magicmouse_has_battery(hdev)) + cancel_delayed_work_sync(&msc->battery_work); } hid_hw_stop(hdev);
--
2.55.0