From: sashiko-bot@kernel.org
To: "Qiling Zhu" <kmatzkaczor244@gmail.com>
Cc: linux-leds@vger.kernel.org, lee@kernel.org
Subject: Re: [RFC PATCH 1/1] platform/x86: thinkpad_acpi: Add support for auto keyboard backlight
Date: Sun, 01 Nov 2026 10:22:23 +0000 [thread overview]
Message-ID: <sashiko-outbox-192354@kernel.org> (raw)
In-Reply-To: <20261101100123.28749-2-kmatzkaczor244@gmail.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Medium] platform/x86: thinkpad_acpi: circular Kconfig dependency on THINKPAD_ACPI
- [Medium] platform/x86: thinkpad_acpi: auto level 3 exposed as LED brightness > max
- [Medium] platform/x86: thinkpad_acpi: procfs write in auto mode leaves trigger stuck
- [Medium] platform/x86: thinkpad_acpi: race in kbdlight_auto_trigger_activate() work
- [Medium] platform/x86: thinkpad_acpi: kbdlight_suspend() breaks auto trigger state
- [Medium] platform/x86: thinkpad_acpi: unchecked level 3 support in kbdlight_set_level()
--
commit 0036d7647f18bf2aea756a93b29c68f5fa29627e
Author: Qiling Zhu <kmatzkaczor244@gmail.com>
platform/x86: thinkpad_acpi: Add support for auto keyboard backlight
This commit adds support for the EC-controlled automatic keyboard backlight
mode (level 3) on ThinkPads by registering a private "tpacpi-kbd-auto" LED
hardware control trigger and notifying the LED core when Fn+Space cycles
into or out of auto mode.
> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> index 5165949930739..74fc66a44c1e2 100644
> --- a/drivers/platform/x86/lenovo/Kconfig
> +++ b/drivers/platform/x86/lenovo/Kconfig
> @@ -75,6 +75,7 @@ config THINKPAD_ACPI
> depends on RFKILL || RFKILL = n
> depends on ACPI_VIDEO || ACPI_VIDEO = n
> depends on BACKLIGHT_CLASS_DEVICE
> + depends on LEDS_TRIGGERS_HW_CHANGED
[Severity: Medium]
Does adding "depends on LEDS_TRIGGERS_HW_CHANGED" to THINKPAD_ACPI in
drivers/platform/x86/lenovo/Kconfig introduce a circular Kconfig dependency?
In drivers/platform/x86/lenovo/Kconfig, THINKPAD_ACPI selects NEW_LEDS and
LEDS_CLASS:
select NEW_LEDS
select LEDS_CLASS
Meanwhile, in drivers/leds/Kconfig and drivers/leds/trigger/Kconfig,
LEDS_TRIGGERS_HW_CHANGED is defined inside "if NEW_LEDS" and "if
LEDS_TRIGGERS", where LEDS_TRIGGERS depends on LEDS_CLASS. This forms a
dependency cycle (THINKPAD_ACPI -> LEDS_TRIGGERS_HW_CHANGED -> LEDS_TRIGGERS
-> LEDS_CLASS -> THINKPAD_ACPI) that causes Kconfig dependency checking to
fail with a recursive dependency error.
Additionally, LEDS_TRIGGERS_HW_CHANGED defaults to
LEDS_BRIGHTNESS_HW_CHANGED (which defaults to n), so depending on it hides
THINKPAD_ACPI whenever LEDS_TRIGGERS_HW_CHANGED is disabled, even though
include/linux/leds.h provides a stub for
led_trigger_notify_hw_control_changed() when CONFIG_LEDS_TRIGGERS_HW_CHANGED
is not set. Since struct led_trigger and devm_led_trigger_register() are
guarded by CONFIG_LEDS_TRIGGERS, should THINKPAD_ACPI select LEDS_TRIGGERS
(or LEDS_TRIGGERS_HW_CHANGED) instead of depending on
LEDS_TRIGGERS_HW_CHANGED?
> diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
> index 4f81855ad721c..cd5c17d5f0877 100644
> --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c
> +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
[ ... ]
> @@ -5197,13 +5200,33 @@ static enum led_brightness kbdlight_sysfs_get(struct led_classdev *led_cdev)
> return level;
> }
>
> +static struct led_hw_trigger_type kbdlight_auto_trigger_type;
> +
> +static int kbdlight_auto_trigger_activate(struct led_classdev *led_cdev)
> +{
> + return kbdlight_set_level(TPACPI_KBD_LIGHT_AUTO_LEVEL);
> +}
[Severity: Medium]
Can the asynchronous trigger_hw_changed_work race with a subsequent Fn+Space
press and overwrite the EC level back to TPACPI_KBD_LIGHT_AUTO_LEVEL (3)?
When Fn+Space cycles the EC from level 2 to level 3,
kbdlight_notify_change() sets kbdlight_brightness = 3 and calls
led_trigger_notify_hw_control_changed(led_cdev, true), which queues
led_cdev->trigger_hw_changed_work.
When led_trigger_hw_control_changed_worker() runs, it snapshots
activate = READ_ONCE(led_cdev->trigger_hw_changed) (true) and calls
__led_trigger_set(led_cdev, hc_trig, true), which sleeps in
synchronize_rcu() and flush_work(&led_cdev->set_brightness_work) before
invoking kbdlight_auto_trigger_activate().
If the user presses Fn+Space again to cycle from level 3 (auto) to level 0
(off) during this window:
1. If the EC transitions to level 0 before kbdlight_auto_trigger_activate()
runs and before the second TP_HKEY_EV_KBD_LIGHT event is handled,
kbdlight_auto_trigger_activate() calls kbdlight_set_level(3), forcing the
EC back to level 3 and keeping kbdlight_brightness at 3, so the second
kbdlight_notify_change() call sees kbdlight_brightness == 3 and drops the
transition to 0.
2. If the second kbdlight_notify_change() runs after the worker has already
read activate = true (for instance while __led_trigger_set() is in
synchronize_rcu()), kbdlight_notify_change() updates kbdlight_brightness
to 0, sets trigger_hw_changed = false, and re-queues
trigger_hw_changed_work. The first worker then calls
kbdlight_auto_trigger_activate() -> kbdlight_set_level(3), writing level
3 back to the EC and setting kbdlight_brightness = 3. The re-queued
worker then runs with activate = false and calls
__led_trigger_set(led_cdev, NULL, true), which detaches the trigger in
software without changing the hardware, leaving the EC stuck in auto mode
(level 3) while sysfs shows the trigger as [none].
Should kbdlight_auto_trigger_activate() avoid writing to the EC when the
activation was initiated by hardware (or when kbdlight_brightness is already
TPACPI_KBD_LIGHT_AUTO_LEVEL)?
> +
> +static bool kbdlight_auto_trigger_hw_offloaded(struct led_classdev *led_cdev)
> +{
> + return kbdlight_get_level() == TPACPI_KBD_LIGHT_AUTO_LEVEL;
> +}
[Severity: Medium]
How does kbdlight_auto_trigger_hw_offloaded() interact with
kbdlight_suspend() and kbdlight_resume() when the keyboard backlight is in
auto mode?
During system suspend, kbdlight_suspend() runs:
led_cdev = &tpacpi_led_kbdlight.led_classdev;
led_update_brightness(led_cdev);
led_classdev_suspend(led_cdev);
led_classdev_suspend() sets LED_SUSPENDED, calls
led_set_brightness_nopm(led_cdev, 0), and flushes set_brightness_work, which
calls kbdlight_sysfs_set(led_cdev, 0) -> kbdlight_set_level(0). This sets
both the EC hardware level and kbdlight_brightness to 0 while
led_cdev->trigger remains kbdlight_auto_trigger.
While suspended, and during resume after kbdlight_resume() ->
led_classdev_resume() clears LED_SUSPENDED and queues set_brightness_work
before that work executes, the EC level is 0, so
kbdlight_auto_trigger_hw_offloaded() returns false even though
tpacpi-kbd-auto is still the active trigger:
- Reading /sys/class/leds/tpacpi::kbd_backlight/brightness sees
led_trigger_is_hw_controlled() == false, calls
led_update_brightness(led_cdev) (overwriting led_cdev->brightness with 0),
and returns 0 instead of -ENODATA.
- Writing a non-zero brightness (1 or 2) to sysfs brightness calls
led_trigger_remove_hw_control(), which checks
__led_trigger_is_hw_controlled() -> kbdlight_auto_trigger_hw_offloaded(),
gets false, and fails to detach tpacpi-kbd-auto.
- Because kbdlight_suspend() overwrote kbdlight_brightness with 0 via
kbdlight_set_level(0), if the backlight exits auto mode to level 0 across
suspend/resume, kbdlight_notify_change() sees kbdlight_brightness == 0 and
does not notify the LED core that auto mode ended.
> +
> +static struct led_trigger kbdlight_auto_trigger = {
> + .name = "tpacpi-kbd-auto",
> + .trigger_type = &kbdlight_auto_trigger_type,
> + .activate = kbdlight_auto_trigger_activate,
> + .hw_offloaded = kbdlight_auto_trigger_hw_offloaded,
> +};
> +
> static struct tpacpi_led_classdev tpacpi_led_kbdlight = {
> .led_classdev = {
> .name = "tpacpi::kbd_backlight",
> .max_brightness = 2,
> - .flags = LED_BRIGHT_HW_CHANGED,
> + .flags = LED_BRIGHT_HW_CHANGED | LED_TRIG_HW_CHANGED,
> .brightness_set_blocking = &kbdlight_sysfs_set,
> .brightness_get = &kbdlight_sysfs_get,
> + .trigger_type = &kbdlight_auto_trigger_type,
> }
> };
>
> @@ -5224,6 +5247,26 @@ static int __init kbdlight_init(struct ibm_init_struct *iibm)
> kbdlight_brightness = kbdlight_sysfs_get(NULL);
> tp_features.kbdlight = 1;
>
> + /*
> + * Register the auto-mode trigger before the LED classdev: if the
> + * hardware is already in auto mode, the trigger can then be set as
> + * the default one and gets activated during LED classdev
> + * registration.
> + */
> + rc = devm_led_trigger_register(&tpacpi_pdev->dev,
> + &kbdlight_auto_trigger);
> + if (rc) {
> + tp_features.kbdlight = 0;
> + return rc;
> + }
> +
> + tpacpi_led_kbdlight.led_classdev.hw_control_trigger =
> + kbdlight_auto_trigger.name;
[Severity: Medium]
What happens on ThinkPad models that have a keyboard backlight (so
kbdlight_is_supported() returns true) but do not support ALS auto mode (level
3) when userspace selects the tpacpi-kbd-auto trigger?
kbdlight_init() unconditionally registers kbdlight_auto_trigger on all
models with a keyboard backlight. When userspace writes "tpacpi-kbd-auto" to
/sys/class/leds/tpacpi::kbd_backlight/trigger,
kbdlight_auto_trigger_activate() calls
kbdlight_set_level(TPACPI_KBD_LIGHT_AUTO_LEVEL):
static int kbdlight_set_level(int level)
{
if (!hkey_handle)
return -ENXIO;
guard(mutex)(&kbdlight_mutex);
if (!acpi_evalf(hkey_handle, NULL, "MLCS", "dd", level))
return -EIO;
kbdlight_brightness = level;
return 0;
}
Unlike kbdlight_get_level() and kbdlight_is_supported(), kbdlight_set_level()
passes NULL for the MLCS return value (so a negative ACPI method error status
is ignored) and does not verify via kbdlight_get_level() that the EC
actually entered level 3. If the EC rejects or ignores level 3:
1. kbdlight_set_level(3) still sets kbdlight_brightness = 3 and returns 0,
so __led_trigger_set() marks tpacpi-kbd-auto as the active trigger.
2. kbdlight_auto_trigger_hw_offloaded() checks
kbdlight_get_level() == TPACPI_KBD_LIGHT_AUTO_LEVEL, which returns false
because the hardware stayed at its previous level (0, 1, or 2).
3. Subsequent non-zero writes to sysfs brightness call
led_trigger_remove_hw_control(), which checks
__led_trigger_is_hw_controlled(), gets false, and fails to remove the
active tpacpi-kbd-auto trigger.
4. When TP_HKEY_EV_KBD_LIGHT fires, kbdlight_notify_change() compares the
stale kbdlight_brightness (3) against kbdlight_sysfs_get(NULL) and treats
it as a hardware-initiated transition out of auto mode.
Should kbdlight_set_level() (or kbdlight_auto_trigger_activate()) check the
MLCS return status and verify that kbdlight_get_level() transitioned to the
requested level before updating kbdlight_brightness and succeeding?
[ ... ]
> @@ -5313,6 +5356,47 @@ static void kbdlight_resume(void)
> led_classdev_resume(&tpacpi_led_kbdlight.led_classdev);
> }
>
> +/* Notify the LED core when the hardware cycles into or out of auto mode. */
> +static void kbdlight_notify_hw_control(struct led_classdev *led_cdev,
> + int old_level, int new_level)
> +{
> + bool was_auto = old_level == TPACPI_KBD_LIGHT_AUTO_LEVEL;
> + bool is_auto = new_level == TPACPI_KBD_LIGHT_AUTO_LEVEL;
> +
> + if (is_auto != was_auto)
> + led_trigger_notify_hw_control_changed(led_cdev, is_auto);
> +}
[Severity: Medium]
If the keyboard backlight is currently in auto mode (level 3) and a fixed
brightness level (0, 1, or 2) is written via /proc/acpi/ibm/kbdlight, doesn't
the tpacpi-kbd-auto trigger remain stuck as the active trigger?
Writing 0, 1, or 2 to /proc/acpi/ibm/kbdlight calls kbdlight_write() ->
kbdlight_set_level_and_update(level) -> kbdlight_set_level(level).
kbdlight_set_level() evaluates MLCS to switch the EC out of auto mode and
updates kbdlight_brightness = level under kbdlight_mutex, without calling
kbdlight_notify_hw_control() or removing the hardware control trigger.
When the EC then raises TP_HKEY_EV_KBD_LIGHT for that MLCS call,
kbdlight_notify_change() sees brightness == kbdlight_brightness and returns
early without calling kbdlight_notify_hw_control().
Furthermore, because the EC is now at level 0, 1, or 2,
kbdlight_auto_trigger_hw_offloaded() returns false. If userspace then writes
a non-zero brightness (1 or 2) to
/sys/class/leds/tpacpi::kbd_backlight/brightness, brightness_store() calls
led_trigger_remove_hw_control(), which checks
__led_trigger_is_hw_controlled() -> kbdlight_auto_trigger_hw_offloaded(),
sees false, and leaves tpacpi-kbd-auto attached (the same thing can also
happen if userspace writes 1 or 2 to sysfs brightness in the asynchronous
window after Fn+Space exits auto mode before trigger_hw_changed_work runs).
Should kbdlight_set_level_and_update() (or kbdlight_set_level()) notify or
remove the hardware control trigger when transitioning out of
TPACPI_KBD_LIGHT_AUTO_LEVEL?
> +
> +/*
> + * The EC cycles the keyboard backlight level (and auto mode) itself on
> + * Fn+Space; check the current level and update the LED core state.
> + */
> +static void kbdlight_notify_change(void)
> +{
> + struct led_classdev *led_cdev = &tpacpi_led_kbdlight.led_classdev;
> + enum led_brightness brightness;
> +
> + guard(mutex)(&kbdlight_mutex);
> +
> + /*
> + * Check the brightness actually changed, setting the brightness
> + * through kbdlight_set_level() also triggers this event.
> + */
> + brightness = kbdlight_sysfs_get(NULL);
> + if (kbdlight_brightness == brightness)
> + return;
> +
> + /*
> + * Calling kbdlight_notify_hw_control() is safe while holding
> + * kbdlight_mutex: the notification is only queued, and the worker
> + * re-acquires the mutex (if at all) after this critical section has
> + * been left.
> + */
> + kbdlight_notify_hw_control(led_cdev, kbdlight_brightness, brightness);
> + kbdlight_brightness = brightness;
> + led_classdev_notify_brightness_hw_changed(led_cdev, brightness);
> +}
[Severity: Medium]
Doesn't this expose TPACPI_KBD_LIGHT_AUTO_LEVEL (3), which exceeds
max_brightness (2), through both brightness_hw_changed and brightness?
The commit message notes that max_brightness stays 2 and auto mode is not
multiplexed into the brightness value. However:
1. In kbdlight_notify_change(), when Fn+Space cycles the EC into auto mode,
kbdlight_sysfs_get(NULL) returns TPACPI_KBD_LIGHT_AUTO_LEVEL (3), and
led_classdev_notify_brightness_hw_changed(led_cdev, brightness) is called
unconditionally with brightness == 3. This stores 3 in
led_cdev->brightness_hw_changed and notifies userspace pollers (such as
upower) of a brightness value greater than max_brightness.
2. Because kbdlight_sysfs_get() (the led_cdev->brightness_get callback)
returns kbdlight_get_level() verbatim, led_update_brightness() sets
led_cdev->brightness = 3 during led_classdev_register() when booting in
auto mode (before led_trigger_set_default() activates the trigger), during
kbdlight_suspend() when suspending in auto mode, and whenever userspace
reads /sys/class/leds/tpacpi::kbd_backlight/brightness while the EC is at
level 3 and led_trigger_is_hw_controlled() is false (such as in the
asynchronous window after Fn+Space before trigger_hw_changed_work runs, or
when a software trigger is attached). When Fn+Space later cycles from auto
mode (3) to off (0), __led_trigger_set(led_cdev, NULL, true) does not
update led_cdev->brightness, leaving it at 3.
Should kbdlight_notify_change() skip
led_classdev_notify_brightness_hw_changed() when entering
TPACPI_KBD_LIGHT_AUTO_LEVEL, and should kbdlight_sysfs_get() avoid returning
TPACPI_KBD_LIGHT_AUTO_LEVEL as an enum led_brightness to the LED core?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261101100123.28749-1-kmatzkaczor244@gmail.com?part=1
next prev parent reply other threads:[~2026-11-01 10:22 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-01 10:01 [RFC PATCH 0/1] platform/x86: thinkpad_acpi: Add support for auto keyboard backlight Qiling Zhu
2026-11-01 10:01 ` [RFC PATCH 1/1] " Qiling Zhu
2026-11-01 10:22 ` sashiko-bot [this message]
2026-11-01 18:24 ` Rong Zhang
2026-11-01 17:57 ` [RFC PATCH 0/1] " Rong Zhang
2026-11-02 15:10 ` [RFC PATCH v2 0/2] " Qiling Zhu
2026-11-02 15:10 ` [RFC PATCH v2 1/2] platform/x86: thinkpad_acpi: Extract kbdlight_notify_change() helper Qiling Zhu
2026-11-02 15:16 ` sashiko-bot
2026-11-02 15:10 ` [RFC PATCH v2 2/2] platform/x86: thinkpad_acpi: Add support for auto keyboard backlight Qiling Zhu
2026-11-02 15:33 ` sashiko-bot
2026-11-02 18:32 ` Rong Zhang
2026-11-02 18:13 ` [RFC PATCH v2 0/2] " Rong Zhang
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=sashiko-outbox-192354@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=kmatzkaczor244@gmail.com \
--cc=lee@kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/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