From: Alec Hall <signshop.alec@gmail.com>
To: Jiri Kosina <jikos@kernel.org>,
Benjamin Tissoires <bentiss@kernel.org>,
linux-input@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, Alec Hall <signshop.alec@gmail.com>
Subject: [PATCH 2/3] HID: magicmouse: report battery over Bluetooth
Date: Tue, 14 Jul 2026 06:12:34 -0400 [thread overview]
Message-ID: <20260714101235.99447-3-signshop.alec@gmail.com> (raw)
In-Reply-To: <20260714101235.99447-1-signshop.alec@gmail.com>
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 <signshop.alec@gmail.com>
---
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
next prev parent reply other threads:[~2026-07-14 10:12 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-14 10:12 [PATCH 0/3] HID: Apple Magic Keyboard/Trackpad " Alec Hall
2026-07-14 10:12 ` [PATCH 1/3] HID: apple: report Magic Keyboard " Alec Hall
2026-07-14 10:12 ` Alec Hall [this message]
2026-07-14 10:12 ` [PATCH 3/3] HID: magicmouse: report charge status " Alec Hall
2026-07-15 6:20 ` Jose Villaseñor Montfort
2026-07-15 19:22 ` Alec Hall
2026-07-15 20:03 ` Jose Villaseñor Montfort
2026-07-15 22:18 ` Alec Hall
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260714101235.99447-3-signshop.alec@gmail.com \
--to=signshop.alec@gmail.com \
--cc=bentiss@kernel.org \
--cc=jikos@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox