Linux LED subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Aaron Erhardt" <aer@tuxedocomputers.com>
Cc: linux-input@vger.kernel.org, linux-leds@vger.kernel.org,
	dmitry.torokhov@gmail.com, lee@kernel.org
Subject: Re: [PATCH v7 3/3] HID: lamparray: blank lamps across suspend and restore on resume
Date: Tue, 20 Oct 2026 10:11:59 +0000	[thread overview]
Message-ID: <sashiko-outbox-178800@kernel.org> (raw)
In-Reply-To: <20261020-hid-lamparray-v7-3-47418b667b47@tuxedocomputers.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] HID: hid-generic: missing PMSG_IS_AUTO() check in hid_generic_suspend()
- [Medium] HID: lamparray: missing LED_SUSPENDED and work flush in lamparray_suspend()
- [Medium] HID: lamparray: race on use_leds_uapi in suspend and resume paths
--

commit eae25839ebd677bf288de15dd8a2d0952e978e6d
Author: Cristian Mazzotta <cmmazzo@icloud.com>

HID: lamparray: blank lamps across suspend and restore on resume

This commit adds lamparray_suspend() and lamparray_resume() helpers to blank
LampArray lighting before system sleep and restore the cached RGB and
brightness state on resume, wiring them up in hid-generic.

> diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
> index fde772c5014bb..f78c62f796fbb 100644
> --- a/drivers/hid/hid-generic.c
> +++ b/drivers/hid/hid-generic.c
> @@ -102,9 +102,34 @@ static int hid_generic_probe(struct hid_device *hdev,
>  
>  static int hid_generic_reset_resume(struct hid_device *hdev)
>  {
> +	struct lamparray *la = hid_get_drvdata(hdev);
> +
>  	if (hdev->claimed & HID_CLAIMED_INPUT)
>  		hidinput_reset_resume(hdev);
>  
> +	if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && la)
> +		lamparray_resume(la);
> +
> +	return 0;
> +}
> +
> +static int hid_generic_suspend(struct hid_device *hdev, pm_message_t message)
> +{
> +	struct lamparray *la = hid_get_drvdata(hdev);
> +
> +	if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && la)
> +		lamparray_suspend(la);
> +
> +	return 0;
> +}
> +
> +static int hid_generic_resume(struct hid_device *hdev)
> +{
> +	struct lamparray *la = hid_get_drvdata(hdev);
> +
> +	if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && la)
> +		lamparray_resume(la);
> +
>  	return 0;
>  }

[Severity: High]
How does this behave with USB runtime autosuspend when PMSG_IS_AUTO(message)
is true?

In drivers/hid/usbhid/hid-core.c, hid_suspend() counts only EV_LED usages in
HID_OUTPUT_REPORT via hidinput_count_leds(), not LampArray HID_FEATURE_REPORT
LEDs. When a USB LampArray interface enters runtime autosuspend in S0,
hid_suspend() sets HID_SUSPENDED on usbhid->iofl before calling
hid_driver_suspend():

drivers/hid/usbhid/hid-core.c:hid_suspend() {
	if (PMSG_IS_AUTO(message)) {
		ledcount = hidinput_count_leds(hid);
		spin_lock_irq(&usbhid->lock);	/* Sync with error handler */
		if (!test_bit(HID_RESET_PENDING, &usbhid->iofl)
		    && !test_bit(HID_CLEAR_HALT, &usbhid->iofl)
		    && !test_bit(HID_OUT_RUNNING, &usbhid->iofl)
		    && !test_bit(HID_CTRL_RUNNING, &usbhid->iofl)
		    && !test_bit(HID_KEYS_PRESSED, &usbhid->iofl)
		    && (!ledcount || ignoreled))
		{
			set_bit(HID_SUSPENDED, &usbhid->iofl);
			spin_unlock_irq(&usbhid->lock);
			status = hid_driver_suspend(hid, message);
}

Because hid_generic_suspend() ignores message and calls lamparray_suspend(),
lamparray_hw_set_state() -> hid_hw_request() -> __usbhid_submit_report()
queues the (0, 0, 0, 0) report in usbhid->ctrl[] and calls
usbhid_restart_ctrl_queue(), which returns early because HID_SUSPENDED is
already set:

drivers/hid/usbhid/hid-core.c:usbhid_restart_ctrl_queue() {
	if (!hid || test_bit(HID_RESET_PENDING, &usbhid->iofl) ||
			test_bit(HID_SUSPENDED, &usbhid->iofl))
		return 0;
}

hid_hw_wait() then sees HID_CTRL_RUNNING == 0 and returns immediately while
the blanking report remains unsent in usbhid->ctrl[].

If the system subsequently enters s2idle or S3 while the USB device is already
runtime-suspended, usb_suspend_both() skips usb_suspend_interface() and the
blanking report is never transmitted to the hardware before system sleep. In
S0, when the device runtime-resumes via hid_resume(), hid_restart_io()
transmits the stale (0, 0, 0, 0) report and hid_generic_resume() calls
lamparray_resume(), cycling AutonomousMode (1 -> 0) and restoring state on
every runtime wakeup.

Should hid_generic_suspend() and hid_generic_resume() skip LampArray
blanking/restoration during runtime PM (or should runtime autosuspend be
prevented while LampArray LEDs are active)?

[ ... ]

> diff --git a/drivers/hid/hid-lamparray.c b/drivers/hid/hid-lamparray.c
> index fb3082f6d19a0..5c97cafbd25d2 100644
> --- a/drivers/hid/hid-lamparray.c
> +++ b/drivers/hid/hid-lamparray.c
> @@ -852,6 +852,60 @@ void lamparray_unregister(struct lamparray *la)
>  }
>  EXPORT_SYMBOL_GPL(lamparray_unregister);
>  
> +/*
> + * Blank all lamps on suspend rather than handing control back to the firmware,
> + * which may not turn them off in low power states. On an Acer Predator PT14-52T,
> + * system power draw during s2idle was ~12.35W with lamps lit, and ~3.14W with
> + * them blanked; the lighting accounted for ~75% of the power draw during suspend.
> + * Since writing zeroes is well defined on all lamparray devices, always do it.
> + * This is ignored if use_leds_uapi is 0; let userspace keep full control.
> + */
> +void lamparray_suspend(struct lamparray *la)
> +{
> +	struct lamparray_device *ldev;
> +
> +	if (!la)
> +		return;
> +
> +	ldev = &la->ldev;
> +
> +	if (!ldev->use_leds_uapi)
> +		return;
> +
> +	mutex_lock(&ldev->dev_lock);
> +	lamparray_hw_set_state(ldev, 0, 0, 0, 0);
> +	mutex_unlock(&ldev->dev_lock);
> +
> +	hid_hw_wait(ldev->hdev);
> +}
> +EXPORT_SYMBOL_GPL(lamparray_suspend);

[Severity: Medium]
Could pending LED work or software blink timers run after lamparray_suspend()
blanks the hardware, un-blanking the lamps and overwriting ldev->last_*?

lamparray_register_led() registers ldev->mc_cdev.led_cdev with
brightness_set_blocking = lamparray_led_brightness_set and without
LED_CORE_SUSPENDRESUME. All non-sleeping brightness updates (sysfs writes,
led_timer_function() software blink, and in-kernel LED triggers) go through
led_set_brightness_nosleep() -> led_set_brightness_nopm(), which queues
cdev->set_brightness_work onto leds_wq (a non-freezable workqueue) unless
LED_SUSPENDED is set on cdev->flags.

Because lamparray_suspend() does not set LED_SUSPENDED on
ldev->mc_cdev.led_cdev.flags or call
flush_work(&ldev->mc_cdev.led_cdev.set_brightness_work), a queued
set_brightness_work or a blink/trigger timer firing during dpm_suspend() or
s2idle can run lamparray_led_brightness_set() after lamparray_suspend() drops
ldev->dev_lock:

drivers/hid/hid-lamparray.c:lamparray_led_brightness_set() {
	...
	guard(mutex)(&ldev->dev_lock);
	...
	if (brightness)
		ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
	else
		ret = lamparray_hw_set_state(ldev, 0, 0, 0, 0);
	...
	ldev->last_r = r;
	ldev->last_g = g;
	ldev->last_b = b;
	ldev->last_brightness = brightness;
}

This sends a new HID_REQ_SET_REPORT during suspend and clobbers the cached
ldev->last_r, ldev->last_g, ldev->last_b, and ldev->last_brightness values.
Similarly, during lamparray_resume(), set_brightness_work can interleave
between lamparray_hw_set_autonomous() and lamparray_restore_state() while
ldev->dev_lock is dropped.

> +
> +void lamparray_resume(struct lamparray *la)
> +{
> +	struct lamparray_device *ldev;
> +
> +	if (!la)
> +		return;
> +
> +	ldev = &la->ldev;
> +
> +	if (!ldev->use_leds_uapi)
> +		return;
> +
> +	/*
> +	 * After a S4 transition, some devices report
> +	 * AutonomousMode = 0 while still ignoring host lamp updates.
> +	 * Writing 0 again does nothing; forcing a 1 -> 0
> +	 * will guarantee the device will update.
> +	 */
> +	lamparray_hw_set_autonomous(ldev, true);
> +	hid_hw_wait(ldev->hdev);
> +	lamparray_hw_set_autonomous(ldev, false);
> +
> +	lamparray_restore_state(ldev);
> +}
> +EXPORT_SYMBOL_GPL(lamparray_resume);

[Severity: Medium]
Can lamparray_suspend() and lamparray_resume() race with concurrent writes to
use_leds_uapi_store()?

In use_leds_uapi_store(), transitions of ldev->use_leds_uapi, LED
registration/unregistration, and AutonomousMode updates are serialized under
ldev->sysfs_lock:

drivers/hid/hid-lamparray.c:use_leds_uapi_store() {
	guard(mutex)(&ldev->sysfs_lock);
	old_val = ldev->use_leds_uapi;

	if (val == old_val)
		return count;

	ldev->use_leds_uapi = val;
	...
}

Neither lamparray_suspend() nor lamparray_resume() acquires ldev->sysfs_lock,
and lamparray_resume() drops ldev->dev_lock between
lamparray_hw_set_autonomous(ldev, true), hid_hw_wait(),
lamparray_hw_set_autonomous(ldev, false), and lamparray_restore_state().

If use_leds_uapi_store() transitions from 1 to 0 while lamparray_resume() is
sleeping in hid_hw_wait() (for example, during USB runtime resume),
use_leds_uapi_store() unregisters the LED and sets AutonomousMode = 1, after
which lamparray_resume() calls lamparray_hw_set_autonomous(ldev, false)
without re-checking use_leds_uapi, leaving AutonomousMode disabled while
use_leds_uapi is 0.

Conversely, during a 0 to 1 transition in use_leds_uapi_store(),
ldev->use_leds_uapi = val is written before lamparray_register_led()
completes. If initial LED registration failed during lamparray_register() and
lamparray_resume() observes ldev->use_leds_uapi == true before
lamparray_register_led() finishes initializing ldev->mc_cdev.subled_info,
lamparray_restore_state() can dereference a NULL ldev->mc_cdev.subled_info
pointer.

Should lamparray_suspend() and lamparray_resume() serialize against
use_leds_uapi_store() with ldev->sysfs_lock?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261020-hid-lamparray-v7-0-47418b667b47@tuxedocomputers.com?part=3

      reply	other threads:[~2026-10-20 10:12 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-20  9:51 [PATCH v7 0/3] HID: generic: add LampArray support via hid-lamparray helper Aaron Erhardt
2026-10-20  9:51 ` [PATCH v7 1/3] HID: lamparray: add new LampArray helper module Aaron Erhardt
2026-10-20 10:21   ` sashiko-bot
2026-10-20  9:51 ` [PATCH v7 2/3] HID: generic: add LampArray support via hid-lamparray helper Aaron Erhardt
2026-10-20 10:06   ` sashiko-bot
2026-10-20  9:51 ` [PATCH v7 3/3] HID: lamparray: blank lamps across suspend and restore on resume Aaron Erhardt
2026-10-20 10:11   ` sashiko-bot [this message]

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-178800@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=aer@tuxedocomputers.com \
    --cc=dmitry.torokhov@gmail.com \
    --cc=lee@kernel.org \
    --cc=linux-input@vger.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