All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Ciro Pedrini" <ciro.pedrini@gmail.com>
Cc: linux-input@vger.kernel.org, dmitry.torokhov@gmail.com
Subject: Re: [PATCH RFC 2/2] HID: lenovo-legion: Add keyboard lighting driver for Legion Gen 10
Date: Mon, 09 Nov 2026 23:04:34 +0000	[thread overview]
Message-ID: <sashiko-outbox-205784@kernel.org> (raw)
In-Reply-To: <20261109-legion-lighting-rfc-v1-2-a87e1db8c5e3@gmail.com>

Thank you for your contribution! Sashiko AI review found 5 potential
issues to consider:
- [Medium] HID: lenovo-legion: stale led_cdev.brightness after hotkey
  events
- [Medium] HID: lenovo-legion: missing color application when pairing in
  legion_kbd_probe()
- [Medium] HID: lenovo-legion: broken color_set and rgb state tracking
  on write and error
- [Medium] HID: lenovo-legion: missing controller owner hand-back in
  legion_lamps_remove()
- [Medium] HID: lenovo-legion: race between legion_brightness_set() and
  lighting_mode_store()
--

commit f6469bc79f96d1ad122443bdd5d1613264a3381f
Author: Ciro Pedrini <ciro.pedrini@gmail.com>

HID: lenovo-legion: Add keyboard lighting driver for Legion Gen 10

This commit adds a HID driver for the keyboard and chassis lighting of
2025 Lenovo Legion laptops, pairing the ITE 8258 keyboard controller and
its companion HID LampArray into a single multicolor LED device.

> diff --git a/drivers/hid/hid-lenovo-legion.c b/drivers/hid/hid-lenovo-legion.c
> new file mode 100644
> index 0000000000000..923f03d4ed342
> --- /dev/null
> +++ b/drivers/hid/hid-lenovo-legion.c

[ ... ]

> +static int legion_brightness_set(struct led_classdev *led,
> +				 enum led_brightness value)
> +{
> +	struct led_classdev_mc *mc = lcdev_to_mccdev(led);
> +	struct legion_kbd *kbd = container_of(mc, struct legion_kbd, mc);
> +	u8 level = min_t(unsigned int, value, LEGION_MAX_BRIGHTNESS);
> +	u8 rgb[3];
> +	int i, ret;
> +
> +	mutex_lock(&kbd->lock);
> +	ret = legion_exchange(kbd, LEGION_OP_SET_BRIGHTNESS, &level, 1, NULL);
> +	mutex_unlock(&kbd->lock);
> +	if (ret)
> +		return ret;
> +
> +	/* A multi_intensity write ends up here too: apply a changed colour. */
> +	for (i = 0; i < 3; i++)
> +		rgb[i] = READ_ONCE(mc->subled_info[i].intensity);
> +
> +	mutex_lock(&legion_pair_lock);
> +	if (memcmp(rgb, kbd->rgb, sizeof(rgb))) {
> +		memcpy(kbd->rgb, rgb, sizeof(rgb));
> +		kbd->color_set = true;
> +		ret = legion_apply_color(kbd);
> +	}
> +	mutex_unlock(&legion_pair_lock);
> +
> +	return ret;
> +}

[Severity: Medium]
Does checking only memcmp(rgb, kbd->rgb, sizeof(rgb)) prevent switching to
"color" mode when the written multi_intensity matches kbd->rgb?

legion_kbd_probe() initializes both subleds[i].intensity and kbd->rgb[i]
to 255 while starting in "firmware" mode (color_set = false), and
switching back to "firmware" mode in lighting_mode_store() leaves kbd->rgb
untouched.  Writing 255 255 255 after probe, or re-writing the previous
colour after switching to "firmware" mode, finds memcmp() == 0 and leaves
the controller in firmware mode.

In addition, both legion_brightness_set() and lighting_mode_store() update
kbd->rgb and set kbd->color_set = true before legion_apply_color(kbd)
succeeds.  If the USB transfer in legion_apply_color() fails,
lighting_mode falsely reports "color" and retries of the same colour via
multi_intensity are skipped.

[ ... ]

> +static void legion_hw_changed_work(struct work_struct *work)
> +{
> +	struct legion_kbd *kbd = container_of(work, struct legion_kbd,
> +					      hw_changed_work);
> +
> +	led_classdev_notify_brightness_hw_changed(&kbd->mc.led_cdev,
> +						  READ_ONCE(kbd->hw_brightness));
> +}

[Severity: Medium]
When Fn+Up or Fn+Down changes the controller's brightness, does
kbd->mc.led_cdev.brightness stay at its old value?

led_classdev_notify_brightness_hw_changed() records the value in
led_cdev->brightness_hw_changed and notifies sysfs pollers, but does not
update led_cdev->brightness:

drivers/leds/led-class-multicolor.c:multi_intensity_store() {
    ...
	if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags))
		led_set_brightness(led_cdev, led_cdev->brightness);
    ...
}

When userspace later writes a colour to multi_intensity,
multi_intensity_store() calls legion_brightness_set() with the stale
led_cdev->brightness and sends LEGION_OP_SET_BRIGHTNESS to the controller,
overwriting the hotkey-adjusted brightness (for example, turning the LEDs
back off if brightness started at 0 and was raised with Fn+Up).

[ ... ]

> +static ssize_t lighting_mode_store(struct device *dev,
> +				   struct device_attribute *attr,
> +				   const char *buf, size_t count)
> +{
> +	struct legion_kbd *kbd = led_dev_to_kbd(dev);
> +	struct led_classdev_mc *mc = &kbd->mc;
> +	int i, ret = 0;
> +
> +	mutex_lock(&legion_pair_lock);
> +	if (sysfs_streq(buf, "color")) {
> +		for (i = 0; i < 3; i++)
> +			kbd->rgb[i] = READ_ONCE(mc->subled_info[i].intensity);
> +		kbd->color_set = true;
> +		ret = legion_apply_color(kbd);
> +	} else if (sysfs_streq(buf, "firmware")) {
> +		kbd->color_set = false;
> +		legion_hand_back(kbd);
> +	} else {
> +		ret = -EINVAL;
> +	}
> +	mutex_unlock(&legion_pair_lock);
> +
> +	return ret ? ret : count;
> +}

[Severity: Medium]
Can an in-flight set_brightness_work race with lighting_mode_store() and
revert a switch to "firmware" mode?

Because the LED uses brightness_set_blocking, multi_intensity_store()
updates mc->subled_info[i].intensity, schedules set_brightness_work on
leds_wq, and returns immediately:

CPU0 (sysfs)                      CPU1 (leds_wq)
multi_intensity_store()
  subled_info[i].intensity = new
  schedule_work(set_brightness_work)
                                  legion_brightness_set()
                                    legion_exchange(SET_BRIGHTNESS)
                                    rgb[i] = subled_info[i].intensity
lighting_mode_store("firmware")
  mutex_lock(&legion_pair_lock)
  kbd->color_set = false
  legion_hand_back(kbd)
  mutex_unlock(&legion_pair_lock)
                                    mutex_lock(&legion_pair_lock)
                                    memcmp(rgb, kbd->rgb) != 0
                                    kbd->color_set = true
                                    legion_apply_color(kbd)  <-- reverts mode

Similarly, if LEGION_OP_SET_BRIGHTNESS fails during a multi_intensity
write, legion_brightness_set() returns before updating kbd->rgb, so a
subsequent brightness change while in "firmware" mode sees memcmp(rgb,
kbd->rgb) != 0 and switches lighting_mode to "color".

[ ... ]

> +	ret = led_classdev_multicolor_register(&hdev->dev, &kbd->mc);
> +	if (ret)
> +		goto err_close;
> +
> +	/* Added afterwards: the multicolor class replaces led_cdev.groups. */
> +	ret = device_add_groups(kbd->mc.led_cdev.dev, legion_led_groups);
> +	if (ret)
> +		goto err_unregister;
> +
> +	spin_lock_irq(&kbd->event_lock);
> +	kbd->ready = true;
> +	spin_unlock_irq(&kbd->event_lock);
> +
> +	mutex_lock(&legion_pair_lock);
> +	list_add(&kbd->node, &legion_kbds);
> +	list_for_each_entry(lamps, &legion_lamps_list, node) {
> +		if (lamps->bus == kbd->bus && !lamps->kbd) {
> +			lamps->kbd = kbd;
> +			kbd->lamps = lamps;
> +			hid_info(hdev, "paired with %s\n", dev_name(&lamps->hdev->dev));
> +			break;
> +		}
> +	}
> +	mutex_unlock(&legion_pair_lock);

[Severity: Medium]
What happens if userspace writes to multi_intensity or lighting_mode right
after the LED is registered, before legion_kbd_probe() pairs kbd with an
already-probed LampArray?

CPU0 (legion_kbd_probe)           CPU1 (udev / sysfs)
led_classdev_multicolor_register()
device_add_groups()
                                  lighting_mode_store("color")
                                    mutex_lock(&legion_pair_lock)
                                    kbd->color_set = true
                                    legion_apply_color(kbd)
                                      if (!kbd->lamps)
                                        return 0;
                                    mutex_unlock(&legion_pair_lock)
mutex_lock(&legion_pair_lock)
kbd->lamps = lamps
/* missing legion_apply_color() */
mutex_unlock(&legion_pair_lock)

Unlike legion_lamps_probe(), the pairing loop in legion_kbd_probe() does
not call legion_apply_color(kbd) when kbd->color_set is already true, so
the hardware stays in firmware mode while lighting_mode reports "color".
Calling legion_apply_color(kbd) when kbd->color_set is set upon pairing
(or pairing before registering the LED) would avoid this.

[ ... ]

> +static void legion_lamps_remove(struct hid_device *hdev)
> +{
> +	struct legion_lamps *lamps = hid_get_drvdata(hdev);
> +
> +	mutex_lock(&legion_pair_lock);
> +	if (lamps->kbd) {
> +		if (lamps->kbd->lamparray_owns)
> +			lamps_set_autonomous(lamps, true);
> +		lamps->kbd->lamps = NULL;
> +	}
> +	list_del(&lamps->node);
> +	mutex_unlock(&legion_pair_lock);
> +
> +	hid_hw_stop(hdev);
> +}

[Severity: Medium]
When the LampArray device is removed or unbound while lamparray_owns is
true, shouldn't legion_lamps_remove() also hand LED ownership back to the
keyboard controller?

legion_lamps_remove() puts the LampArray into autonomous mode, but leaves
the keyboard controller in LEGION_OWNER_LAMPARRAY (0xAA 00) and leaves
lamps->kbd->lamparray_owns set to true, which leaves the LEDs dark instead
of restoring the controller's effects as legion_hand_back() does.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261109-legion-lighting-rfc-v1-0-a87e1db8c5e3@gmail.com?part=2

  reply	other threads:[~2026-11-09 23:04 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-09 22:50 [PATCH RFC 0/2] Keyboard lighting for the Lenovo Legion Pro 7 Gen 10 Ciro Pedrini via B4 Relay
2026-11-09 22:50 ` Ciro Pedrini
2026-11-09 22:50 ` [PATCH RFC 1/2] platform/x86: ideapad-laptop: Ignore keyboard backlight hotkeys Ciro Pedrini via B4 Relay
2026-11-09 22:50   ` Ciro Pedrini
2026-11-10 18:44   ` Rong Zhang
2026-11-09 22:50 ` [PATCH RFC 2/2] HID: lenovo-legion: Add keyboard lighting driver for Legion Gen 10 Ciro Pedrini via B4 Relay
2026-11-09 22:50   ` Ciro Pedrini
2026-11-09 23:04   ` sashiko-bot [this message]
2026-11-10 10:28   ` Ilpo Järvinen
2026-11-10 20:34     ` Ciro Pedrini
2026-11-10 14:56 ` [PATCH RFC 0/2] Keyboard lighting for the Lenovo Legion Pro 7 " Rong Zhang
2026-11-10 20:34   ` Ciro Pedrini
2026-11-11 12:10     ` Rong Zhang
2026-11-11 17:27       ` Ciro Pedrini
2026-11-11 17:39         ` 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-205784@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=ciro.pedrini@gmail.com \
    --cc=dmitry.torokhov@gmail.com \
    --cc=linux-input@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 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.