Re: [PATCH v2] HID: input: read battery capacity from its actual report offset
From: Jiri Kosina
Date: Mon Aug 03 2026 - 14:44:39 EST
On Tue, 28 Jul 2026, Jose Villaseñor Montfort wrote:
> hidinput_query_battery_capacity() assumes the state-of-charge value is
> the first byte following the report ID (buf[1]) and ignores where the
> battery field actually sits within the report.
>
> An Apple Magic Trackpad 2 precedes the AbsoluteStateOfCharge byte with a
> byte of status flags in its battery reports, so this query returns the
> flags byte instead of the charge level.
>
> The device happens to make that easy to observe, because it exposes the
> same cell twice: its report descriptor declares AbsoluteStateOfCharge in
> two reports (0x90 and 0x9b), so hidinput_setup_battery() registers two
> power supplies. Only the first one is refreshed by hid-magicmouse -- it
> uses hid_get_battery(), which returns the first battery of the list --
> and that refresh goes through the report event path, which parses the
> field correctly. Nothing ever reports the second one, so every read of
> its capacity takes the query path above. On a USB-C Magic Trackpad over
> USB, on an unpatched 7.1.5:
>
> hid-<serial>-battery-144 = 100% (Charging) <- report event path
> hid-<serial>-battery-155 = 3% (Discharging) <- query path
>
> Both are the same physical battery. A raw HIDIOCGINPUT of the two
> reports at that same moment:
>
> report 0x90 -> [90 03 64]
> report 0x9b -> [9b 03 64 64 00 00 10 00 00 00 00 00 00 00]
> ^flags ^SoC = 0x64 = 100%
>
> The device answers correctly in both cases; only the offset the kernel
> reads the capacity from is wrong. 0x03 is the flags byte (present,
> charging), reported as "3%".
>
> Bluetooth takes the same query path for its capacity, where the trackpad
> reported a bogus near-constant ~4% -- 0b100, the FullyCharged flag --
> regardless of the real charge.
>
> Store the battery field's offset within the report at setup time and use
> it when querying, so the capacity is read from its real position. The
> report event path already parses the field correctly through the HID
> core; only the explicit GET_REPORT query was wrong.
>
> Devices whose capacity field is the first field in the report have a
> report_offset of 0 and are unaffected (buf[1 + 0] == buf[1]).
>
> Fixes: 581c4484769e ("HID: input: map digitizer battery usage")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Jose Villaseñor Montfort <pepemontfort@xxxxxxxxx>
> ---
> No code changes since v1, only the commit message and tags.
>
> Changes in v2:
> - Added Fixes: 581c4484769e, which introduced
> hidinput_query_battery_capacity() with the hardcoded buf[1], and a
> stable tag.
> - Dropped the claim in v1 that USB is unaffected. It is not: the
> trackpad's descriptor declares AbsoluteStateOfCharge in two reports,
> hidinput_setup_battery() registers a power supply for each, and
> hid-magicmouse only ever refreshes the first one (hid_get_battery()
> returns the head of the list). The second power supply therefore
> serves every read from the broken query path, and shows 3% -- the
> flags byte -- permanently, on USB, next to the first one showing the
> correct 100%. That replaces the v1 example, since it puts the working
> and the broken path on the same cell at the same instant.
> - v1: https://lore.kernel.org/linux-input/20260702192139.114809-1-pepemontfort@xxxxxxxxx/
Applied, thanks.
--
Jiri Kosina
SUSE Labs