All of lore.kernel.org
 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 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.