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 1/3] HID: apple: report Magic Keyboard battery over Bluetooth
Date: Tue, 14 Jul 2026 06:12:33 -0400 [thread overview]
Message-ID: <20260714101235.99447-2-signshop.alec@gmail.com> (raw)
In-Reply-To: <20260714101235.99447-1-signshop.alec@gmail.com>
The Magic Keyboard answers a GET_REPORT for battery input report 0x90
over Bluetooth exactly as it does over USB, but battery reporting was
only ever enabled for USB: the Bluetooth device-table entry lacks the
APPLE_RDESC_BATTERY quirk, so apple_fetch_battery() returns early and
the kernel's power_supply is stuck at 0%.
The USB-only restriction is not cosmetic. apple_fetch_battery() issues
its GET_REPORT through hid_hw_request(), and over Bluetooth that goes
through the uhid transport, whose raw_request sleeps waiting on user
space. The fetch was driven from a timer callback
(apple_battery_timer_tick), which runs in atomic softirq context where
sleeping is forbidden, so requesting the battery there deadlocks the
machine:
run_timer_softirq
__run_timer_base
apple_battery_timer_tick
__hid_request
uhid_hid_raw_request /* sleeps in atomic context */
Move the periodic battery fetch to a delayed work item, which runs in
process context and may sleep, and set APPLE_RDESC_BATTERY on the
Bluetooth Magic Keyboard 2021 entry. The descriptor fixup guarded by
the quirk only fires for the 83-byte USB report descriptor, so enabling
the quirk over Bluetooth does not disturb the (different) Bluetooth
descriptor. Also stop returning early once capacity reaches max: over
Bluetooth the device sends no unsolicited updates, so the driver must
keep polling to notice the level fall after a full charge.
Only the plain Magic Keyboard 2021 is enabled and tested here; the
fingerprint, numpad and 2024 Bluetooth variants likely need the same
change but were not available to test.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Alec Hall <signshop.alec@gmail.com>
---
drivers/hid/hid-apple.c | 36 +++++++++++++++++++-----------------
1 file changed, 19 insertions(+), 17 deletions(-)
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index bf7dd0fbf249..e46a59844a30 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -23,6 +23,7 @@
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/timer.h>
+#include <linux/workqueue.h>
#include <linux/string.h>
#include <linux/leds.h>
#include <dt-bindings/leds/common.h>
@@ -116,7 +117,7 @@ struct apple_sc {
unsigned int fn_on;
unsigned int fn_found;
DECLARE_BITMAP(pressed_numlock, KEY_CNT);
- struct timer_list battery_timer;
+ struct delayed_work battery_work;
struct apple_sc_backlight *backlight;
};
@@ -635,9 +636,6 @@ static int apple_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
@@ -645,15 +643,20 @@ static int apple_fetch_battery(struct hid_device *hdev)
#endif
}
-static void apple_battery_timer_tick(struct timer_list *t)
+static void apple_battery_work(struct work_struct *work)
{
- struct apple_sc *asc = timer_container_of(asc, t, battery_timer);
+ struct apple_sc *asc = container_of(work, struct apple_sc, battery_work.work);
struct hid_device *hdev = asc->hdev;
- if (apple_fetch_battery(hdev) == 0) {
- mod_timer(&asc->battery_timer,
- jiffies + secs_to_jiffies(APPLE_BATTERY_TIMEOUT_SEC));
- }
+ /*
+ * Runs in process context (workqueue), so the battery GET_REPORT is
+ * allowed to sleep. This is required for the uhid/Bluetooth transport,
+ * whose raw_request blocks waiting on userspace -- unlike a timer_list
+ * callback, which runs in atomic softirq context and would deadlock.
+ */
+ if (apple_fetch_battery(hdev) == 0)
+ schedule_delayed_work(&asc->battery_work,
+ secs_to_jiffies(APPLE_BATTERY_TIMEOUT_SEC));
}
/*
@@ -968,10 +971,9 @@ static int apple_probe(struct hid_device *hdev,
}
if (quirks & APPLE_RDESC_BATTERY) {
- timer_setup(&asc->battery_timer, apple_battery_timer_tick, 0);
- mod_timer(&asc->battery_timer,
- jiffies + secs_to_jiffies(APPLE_BATTERY_TIMEOUT_SEC));
- apple_fetch_battery(hdev);
+ INIT_DELAYED_WORK(&asc->battery_work, apple_battery_work);
+ /* Kick an initial fetch; the work re-arms itself every timeout. */
+ schedule_delayed_work(&asc->battery_work, 0);
}
if (quirks & APPLE_BACKLIGHT_CTL)
@@ -987,7 +989,7 @@ static int apple_probe(struct hid_device *hdev,
out_err:
if (quirks & APPLE_RDESC_BATTERY)
- timer_delete_sync(&asc->battery_timer);
+ cancel_delayed_work_sync(&asc->battery_work);
hid_hw_stop(hdev);
return ret;
@@ -998,7 +1000,7 @@ static void apple_remove(struct hid_device *hdev)
struct apple_sc *asc = hid_get_drvdata(hdev);
if (asc->quirks & APPLE_RDESC_BATTERY)
- timer_delete_sync(&asc->battery_timer);
+ cancel_delayed_work_sync(&asc->battery_work);
hid_hw_stop(hdev);
}
@@ -1201,7 +1203,7 @@ static const struct hid_device_id apple_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021),
- .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021),
--
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 ` Alec Hall [this message]
2026-07-14 10:12 ` [PATCH 2/3] HID: magicmouse: report " Alec Hall
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-2-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