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: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-14 10:12 [PATCH 0/3] HID: Apple Magic Keyboard/Trackpad battery over Bluetooth 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-14 10:25 ` sashiko-bot
2026-07-14 17:17 ` 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.