Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH] HID: magicmouse: enable battery polling for 2024 Magic Trackpad
@ 2026-04-11 16:38 Dmitri Ollari
  2026-05-12 15:36 ` Jiri Kosina
  2026-05-12 19:36 ` [PATCH v2] HID: magicmouse: fix battery reporting for 2024 Magic Trackpad USB-C Dmitri Ollari
  0 siblings, 2 replies; 3+ messages in thread
From: Dmitri Ollari @ 2026-04-11 16:38 UTC (permalink / raw)
  To: linux-input; +Cc: jikos, Dmitri Ollari


[-- Attachment #1.1: Type: text/plain, Size: 6501 bytes --]

The 2024 Magic Trackpad USB-C (PID 0x0324) does not report battery
strength via HID descriptor fields over Bluetooth. Instead it requires
an explicit HID_REQ_GET_REPORT request to retrieve the battery level.

This patch makes the following changes:

1. Replace the battery_timer (timer_list) with battery_work (delayed_work)
   so that HID_REQ_GET_REPORT can be issued from a sleepable context.
   Timers run in atomic context and cannot block, which caused deadlocks
   on the Bluetooth transport path.

2. Extend the fetch guard and probe scheduling block to include the 2024
   Magic Trackpad USB-C when connected over Bluetooth (vendor 0x004C,
   product 0x0324 via BT_VENDOR_ID_APPLE).

3. Schedule battery_work immediately at probe (delay=0) instead of
   issuing a direct magicmouse_fetch_battery() call. The direct call
   bypassed the cold-start correction logic and could publish a stale
   value before the work handler had a chance to validate it.

4. Add a cold-start
 double-poll: the device may return a stale battery
   value (e.g. 4%) on the very first GET_REPORT after power-on. On the
   first successful poll battery_validated is set and a second poll is
   scheduled 3 seconds later to obtain the real value. Subsequent polls
   use the normal 60-second interval.

5. Remove the early-return guard that skipped polling when
   battery_capacity equalled battery_max. This prevented the second
   corrective poll from firing when the first stale response happened
   to equal 100.

Signed-off-by: Dmitri Ollari <dmitri.ollari@protonmail.com>
---
 hid-magicmouse.c | 55 +++++++++++++++++++++++++++++-------------------
 1 file changed, 33 insertions(+), 22 deletions(-)

diff --git a/hid-magicmouse.c b/hid-magicmouse.c
index 9eadf32..bc9c467 100644
--- a/hid-magicmouse.c
+++ b/hid-magicmouse.c
@@ -123,7 +123,10 @@ 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 periodic battery level polling.
+ * @battery_validated: Set after the first successful poll; gates the
+ *	second poll that corrects the stale value the device may report
+ *	on cold start.
  */
 struct magicmouse_sc {
 	struct input_dev *input;
@@ -148,7 +151,8 @@ struct magicmouse_sc {
 
 	struct hid_device *hdev;
 	struct delayed_work work;
-	struct timer_list battery_timer;
+	struct delayed_work battery_work;
+	bool battery_validated;
 };
 
 static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -820,7 +824,8 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
 
 	if (!hdev->battery ||
 	    (!is_usb_magicmouse2(hdev->vendor, hdev->product) &&
-	     !is_usb_magictrackpad2(hdev->vendor, hdev->p
roduct)))
+	     !is_usb_magictrackpad2(hdev->vendor, hdev->product) &&
+	     hdev->product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC)) /* 2024 Magic Trackpad USB-C over Bluetooth */
 		return -1;
 
 	report_enum = &hdev->report_enum[hdev->battery_report_type];
@@ -829,9 +834,6 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
 	if (!report || report->maxfield < 1)
 		return -1;
 
-	if (hdev->battery_capacity == hdev->battery_max)
-		return -1;
-
 	hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
 	return 0;
 #else
@@ -839,14 +841,23 @@ 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;
 
 	i
f (magicmouse_fetch_battery(hdev) == 0) {
-		mod_timer(&msc->battery_timer,
-			  jiffies + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
+		if (!msc->battery_validated) {
+			/* The device may return a stale value (e.g. 4%) on the
+			 * first GET_REPORT after cold start. Schedule a second
+			 * poll shortly after to get the real value, then settle
+			 * into the normal 60s interval.
+			 */
+			msc->battery_validated = true;
+			schedule_delayed_work(&msc->battery_work, secs_to_jiffies(3));
+		} else {
+			schedule_delayed_work(&msc->battery_work, secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
+		}
 	}
 }
 
@@ -866,6 +877,7 @@ static int magicmouse_probe(struct hid_device *hdev,
 	msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
 	msc->hdev = hdev;
 	INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work);
+	INIT_DELAYED_WORK(&msc->battery_work, magicmouse_battery_work);
 
 	msc->quirks = id->driver_data;
 	hid_set_drvdata(hdev, msc);
@@ -883,11 +895,13 @@ static int
 magicmouse_probe(struct hid_device *hdev,
 	}
 
 	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);
+	    is_usb_magictrackpad2(id->vendor, id->product) ||
+	    id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
+		/* Schedule immediately so battery_work runs ASAP, sets battery_validated,
+		 * then reschedules every 60s. Avoids direct fetch which bypasses
+		 * battery_validated and would publish a stale startup value.
+		 */
+		schedule_delayed_work(&msc->battery_work, 0);
 	}
 
 	if (is_usb_magicmouse2(id->vendor, id->product) ||
@@ -955,10 +969,8 @@ static int magicmouse_probe(struct hid_device *hdev,
 
 	return 0;
 err_stop_hw:
-	if (is_usb_magicmouse2(id->vendor, id->product) ||
-	    is_usb_magi
ctrackpad2(id->vendor, id->product))
-		timer_delete_sync(&msc->battery_timer);
-
+	/* Clean up battery work on error */
+	cancel_delayed_work_sync(&msc->battery_work);
 	hid_hw_stop(hdev);
 	return ret;
 }
@@ -969,9 +981,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);
+		/* Cancel battery polling on device removal */
+		cancel_delayed_work_sync(&msc->battery_work);
 	}
 
 	hid_hw_stop(hdev);
-- 
2.53.0


[-- Attachment #1.2: publickey - dmitri.ollari@protonmail.com - 0xF53BC391.asc --]
[-- Type: application/pgp-keys, Size: 722 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 322 bytes --]

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH] HID: magicmouse: enable battery polling for 2024 Magic Trackpad
  2026-04-11 16:38 [PATCH] HID: magicmouse: enable battery polling for 2024 Magic Trackpad Dmitri Ollari
@ 2026-05-12 15:36 ` Jiri Kosina
  2026-05-12 19:36 ` [PATCH v2] HID: magicmouse: fix battery reporting for 2024 Magic Trackpad USB-C Dmitri Ollari
  1 sibling, 0 replies; 3+ messages in thread
From: Jiri Kosina @ 2026-05-12 15:36 UTC (permalink / raw)
  To: Dmitri Ollari; +Cc: linux-input

On Sat, 11 Apr 2026, Dmitri Ollari wrote:

> The 2024 Magic Trackpad USB-C (PID 0x0324) does not report battery
> strength via HID descriptor fields over Bluetooth. Instead it requires
> an explicit HID_REQ_GET_REPORT request to retrieve the battery level.
> 
> This patch makes the following changes:
> 
> 1. Replace the battery_timer (timer_list) with battery_work (delayed_work)
>    so that HID_REQ_GET_REPORT can be issued from a sleepable context.
>    Timers run in atomic context and cannot block, which caused deadlocks
>    on the Bluetooth transport path.
> 
> 2. Extend the fetch guard and probe scheduling block to include the 2024
>    Magic Trackpad USB-C when connected over Bluetooth (vendor 0x004C,
>    product 0x0324 via BT_VENDOR_ID_APPLE).
> 
> 3. Schedule battery_work immediately at probe (delay=0) instead of
>    issuing a direct magicmouse_fetch_battery() call. The direct call
>    bypassed the cold-start correction logic and could publish a stale
>    value before the work handler had a chance to validate it.
> 
> 4. Add a cold-start
>  double-poll: the device may return a stale battery
>    value (e.g. 4%) on the very first GET_REPORT after power-on. On the
>    first successful poll battery_validated is set and a second poll is
>    scheduled 3 seconds later to obtain the real value. Subsequent polls
>    use the normal 60-second interval.
> 
> 5. Remove the early-return guard that skipped polling when
>    battery_capacity equalled battery_max. This prevented the second
>    corrective poll from firing when the first stale response happened
>    to equal 100.
> 
> Signed-off-by: Dmitri Ollari <dmitri.ollari@protonmail.com>

Dmitri,

thanks for the patch.

It has however been badly line-wrapped and whitespace-damaged by your mail 
client.
Can you please look into fixing it and resubmit?

Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v2] HID: magicmouse: fix battery reporting for 2024 Magic Trackpad USB-C
  2026-04-11 16:38 [PATCH] HID: magicmouse: enable battery polling for 2024 Magic Trackpad Dmitri Ollari
  2026-05-12 15:36 ` Jiri Kosina
@ 2026-05-12 19:36 ` Dmitri Ollari
  1 sibling, 0 replies; 3+ messages in thread
From: Dmitri Ollari @ 2026-05-12 19:36 UTC (permalink / raw)
  To: linux-input; +Cc: jikos, Dmitri Ollari

The 2024 Magic Trackpad USB-C (PID 0x0324) does not report battery
strength via HID descriptor fields over Bluetooth. Instead it requires
an explicit HID_REQ_GET_REPORT request to retrieve the battery level.

Replace the battery_timer (timer_list) with battery_work (delayed_work)
so that HID_REQ_GET_REPORT can be issued from a sleepable context.
Timers run in atomic context and cannot block, which caused deadlocks
on the Bluetooth transport path.

Extend the fetch guard and probe scheduling block to include the 2024
Magic Trackpad USB-C when connected over Bluetooth.

Schedule battery_work immediately at probe (delay=0) instead of
issuing a direct magicmouse_fetch_battery() call. The direct call
bypassed the cold-start correction logic and could publish a stale
value before the work handler had a chance to validate it.

Add a cold-start double-poll: the device may return a stale battery
value (e.g. 4%) on the very first GET_REPORT after power-on. On the
first successful poll battery_validated is set and a second poll is
scheduled 3 seconds later to obtain the real value. Subsequent polls
use the normal 60-second interval.

Remove the early-return guard that skipped polling when
battery_capacity equalled battery_max. This prevented the second
corrective poll from firing when the first stale response happened
to equal 100.

Signed-off-by: Dmitri Ollari <dmitri.ollari@protonmail.com>
---
v2:
  - Rebased onto v7.1-rc3
  - Fixed email line-wrapping and whitespace damage
  - Removed PGP signing

 drivers/hid/hid-magicmouse.c | 57 ++++++++++++++++++++++--------------
 1 file changed, 35 insertions(+), 22 deletions(-)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index e70bd3dc07ab..1d7c84ecadbb 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -123,7 +123,10 @@ 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 periodic battery level polling.
+ * @battery_validated: Set after the first successful poll; gates the
+ *	second poll that corrects the stale value the device may report
+ *	on cold start.
  */
 struct magicmouse_sc {
 	struct input_dev *input;
@@ -148,7 +151,8 @@ struct magicmouse_sc {
 
 	struct hid_device *hdev;
 	struct delayed_work work;
-	struct timer_list battery_timer;
+	struct delayed_work battery_work;
+	bool battery_validated;
 };
 
 static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -822,7 +826,8 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
 	bat = hid_get_battery(hdev);
 	if (!bat ||
 	    (!is_usb_magicmouse2(hdev->vendor, hdev->product) &&
-	     !is_usb_magictrackpad2(hdev->vendor, hdev->product)))
+	     !is_usb_magictrackpad2(hdev->vendor, hdev->product) &&
+	     hdev->product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC))
 		return -1;
 
 	report_enum = &hdev->report_enum[bat->report_type];
@@ -831,9 +836,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
@@ -841,14 +843,25 @@ 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));
+		if (!msc->battery_validated) {
+			/*
+			 * The device may return a stale value (e.g. 4%) on the
+			 * first GET_REPORT after cold start. Schedule a second
+			 * poll shortly after to get the real value, then settle
+			 * into the normal 60s interval.
+			 */
+			msc->battery_validated = true;
+			schedule_delayed_work(&msc->battery_work, secs_to_jiffies(3));
+		} else {
+			schedule_delayed_work(&msc->battery_work,
+					      secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
+		}
 	}
 }
 
@@ -868,6 +881,7 @@ static int magicmouse_probe(struct hid_device *hdev,
 	msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
 	msc->hdev = hdev;
 	INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work);
+	INIT_DELAYED_WORK(&msc->battery_work, magicmouse_battery_work);
 
 	msc->quirks = id->driver_data;
 	hid_set_drvdata(hdev, msc);
@@ -885,11 +899,15 @@ static int magicmouse_probe(struct hid_device *hdev,
 	}
 
 	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);
+	    is_usb_magictrackpad2(id->vendor, id->product) ||
+	    id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
+		/*
+		 * Schedule immediately so battery_work runs ASAP, sets
+		 * battery_validated, then reschedules every 60s. Avoids
+		 * direct fetch which bypasses battery_validated and would
+		 * publish a stale startup value.
+		 */
+		schedule_delayed_work(&msc->battery_work, 0);
 	}
 
 	if (is_usb_magicmouse2(id->vendor, id->product) ||
@@ -957,10 +975,7 @@ 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);
-
+	cancel_delayed_work_sync(&msc->battery_work);
 	hid_hw_stop(hdev);
 	return ret;
 }
@@ -971,9 +986,7 @@ 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);
+		cancel_delayed_work_sync(&msc->battery_work);
 	}
 
 	hid_hw_stop(hdev);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-05-12 19:42 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-11 16:38 [PATCH] HID: magicmouse: enable battery polling for 2024 Magic Trackpad Dmitri Ollari
2026-05-12 15:36 ` Jiri Kosina
2026-05-12 19:36 ` [PATCH v2] HID: magicmouse: fix battery reporting for 2024 Magic Trackpad USB-C Dmitri Ollari

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox