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 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.