Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Jingyuan Liang" <jingyliang@chromium.org>
Cc: linux-input@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org,
	dmitry.torokhov@gmail.com
Subject: Re: [PATCH v6 10/11] HID: spi-hid: add power management implementation
Date: Mon, 19 Oct 2026 19:36:19 +0000	[thread overview]
Message-ID: <sashiko-outbox-178144@kernel.org> (raw)
In-Reply-To: <20261019-send-upstream-v6-10-cb9ba4b3d6fa@chromium.org>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] hid: spi-hid: disable_irq() before cancel_work_sync() in spi_hid_suspend()
- [High] hid: spi-hid: unchecked deassert_reset() and reset timeout in resume/suspend
- [High] hid: spi-hid: holding power_lock in spi_hid_wait_for_reset() blocks recovery
- [Medium] hid: spi-hid: spi_hid_resume() runs reset_resume before pending reset_work
- [Medium] HID: spi-hid: skipped power-up in spi_hid_resume() when wakeup is enabled
--

commit 943f2d7ec87adee993c65a27b7655a39b4d0ef5c
Author: Jingyuan Liang <jingyliang@chromium.org>

HID: spi-hid: add power management implementation

This commit implements system sleep power management callbacks for the
HID over SPI transport driver, handling device suspend, resume, reset
synchronization, and wake IRQ setup.

> diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
> index 96805c10bfe36..c2232d5959b88 100644
> --- a/drivers/hid/spi-hid/spi-hid-core.c
> +++ b/drivers/hid/spi-hid/spi-hid-core.c

[ ... ]

> @@ -242,6 +250,148 @@ static const char *spi_hid_power_mode_string(enum hidspi_power_state power_state
>  	}
>  }
>  
> +/*
> + * Wait for the device descriptor response after reset; the device then takes
> + * requests. Set READY here: the report descriptor check that normally sets it
> + * needs power_lock, which callers hold.
> + */
> +static void spi_hid_wait_for_reset(struct spi_hid *shid)
> +{
> +	unsigned long timeout = msecs_to_jiffies(SPI_HID_RESET_TIMEOUT +
> +						 SPI_HID_RESP_TIMEOUT);
> +
> +	if (!wait_for_completion_timeout(&shid->reset_done, timeout)) {
> +		dev_warn(&shid->spi->dev,
> +			 "Device descriptor not received after reset\n");
> +		return;
> +	}
> +
> +	set_bit(SPI_HID_READY, &shid->flags);
> +}

[Severity: High]
What happens if an error occurs during the post-reset handshake while a
caller is waiting in spi_hid_wait_for_reset()?

Both spi_hid_resume() and the power_down() rollback path in
spi_hid_suspend() hold shid->power_lock across spi_hid_wait_for_reset()
and hid_driver_reset_resume().

If a transient bus error, header validation error, or descriptor error
occurs in spi_hid_dev_irq(), spi_hid_reset_response(), or
spi_hid_dev_desc_response(), the driver sets SPI_HID_ERROR and schedules
shid->reset_work to run spi_hid_error_handler():

  spi_hid_reset_work()
    spi_hid_error_handler()
      guard(mutex)(&shid->power_lock);

Because spi_hid_wait_for_reset() is sleeping with shid->power_lock held,
spi_hid_error_handler() blocks on shid->power_lock and cannot perform any
of its SPI_HID_MAX_RESET_ATTEMPTS recovery resets until
spi_hid_wait_for_reset() times out after 2000 ms and spi_hid_resume() or
spi_hid_suspend() returns. That leaves SPI_HID_READY cleared when
hid_driver_reset_resume() runs, and delays the error reset until after
resume finishes.

> +
> +static int spi_hid_suspend(struct spi_hid *shid)
> +{
> +	int error;
> +	struct device *dev = &shid->spi->dev;
> +
> +	/* HID driver suspend may talk to the device; responses need the IRQ. */
> +	scoped_guard(mutex, &shid->power_lock) {
> +		if (shid->power_state != HIDSPI_OFF && shid->hid) {
> +			error = hid_driver_suspend(shid->hid, PMSG_SUSPEND);
> +			if (error) {
> +				dev_err(dev, "%s failed to suspend hid driver: %d\n",
> +					__func__, error);
> +				return error;
> +			}
> +		}
> +	}
> +
> +	/*
> +	 * disable_irq() waits for a running IRQ thread, so no new reset_work
> +	 * can be queued after it returns. Then wait for any queued or running
> +	 * instance. Must not hold power_lock here: reset_work takes it.
> +	 */
> +	disable_irq(shid->spi->irq);
> +	cancel_work_sync(&shid->reset_work);

[Severity: High]
Can calling disable_irq(shid->spi->irq) before
cancel_work_sync(&shid->reset_work) starve an in-flight synchronous
request in spi_hid_reset_work()?

If shid->reset_work is already running when spi_hid_suspend() disables the
IRQ, it may be executing a synchronous request in:

  spi_hid_reset_work()
    spi_hid_reset_response() / spi_hid_create_device() / spi_hid_refresh_device()
      spi_hid_sync_request()
        __spi_hid_sync_request()
          wait_for_completion_timeout(&shid->output_done, ...)

Since shid->output_done is completed by spi_hid_response_handler() from
the threaded IRQ handler spi_hid_dev_irq(), disabling the IRQ first
prevents the response interrupt from being serviced while
cancel_work_sync() waits for reset_work.

This stalls cancel_work_sync() for the full SPI_HID_RESP_TIMEOUT, fails
the request with -ETIMEDOUT, clears SPI_HID_READY, sets SPI_HID_ERROR,
and triggers dev_WARN_ONCE() in spi_hid_reset_response():

	error = spi_hid_sync_request(shid, &report);
	if (error) {
		dev_WARN_ONCE(dev, true,
			      "Failed to send device descriptor request: %d\n", error);
		set_bit(SPI_HID_ERROR, &shid->flags);
		schedule_work(&shid->reset_work);
	}

For wakeup-enabled devices, this also leaves the device suspended with
SPI_HID_READY cleared (causing spi_hid_input_report_handler() to drop
wakeup input reports) and SPI_HID_ERROR pending. In addition, because
shid->power_lock is released after hid_driver_suspend() and before
disable_irq(), spi_hid_create_device() or spi_hid_refresh_device() in
spi_hid_reset_work() can register a new shid->hid in that window that
never has hid_driver_suspend() called on it.

> +
> +	guard(mutex)(&shid->power_lock);
> +	if (!device_may_wakeup(dev)) {
> +		if (shid->power_state != HIDSPI_OFF) {
> +			/* Device is fully reset on resume; drop stale state. */
> +			clear_bit(SPI_HID_READY, &shid->flags);
> +			clear_bit(SPI_HID_RESET_RESPONSE, &shid->flags);
> +			clear_bit(SPI_HID_CREATE_DEVICE, &shid->flags);
> +			clear_bit(SPI_HID_ERROR, &shid->flags);
> +			set_bit(SPI_HID_RESET_PENDING, &shid->flags);
> +
> +			shid->ops->assert_reset(shid->ops);
> +
> +			error = shid->ops->power_down(shid->ops);
> +			if (error) {
> +				dev_err(dev, "%s: could not power down\n", __func__);
> +				shid->regulator_error_count++;
> +				shid->regulator_last_error = error;
> +				/*
> +				 * Undo partial suspend before returning error.
> +				 * Complete the reset; RESET_PENDING stays set
> +				 * until the device sends its reset response.
> +				 */
> +				shid->ops->sleep_minimal_reset_delay(shid->ops);
> +				reinit_completion(&shid->reset_done);
> +				shid->ops->deassert_reset(shid->ops);
> +				enable_irq(shid->spi->irq);
> +				if (shid->hid) {
> +					spi_hid_wait_for_reset(shid);
> +					hid_driver_reset_resume(shid->hid);
> +				}
> +				return error;
> +			}
> +
> +			shid->power_state = HIDSPI_OFF;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int spi_hid_resume(struct spi_hid *shid)
> +{
> +	int error;
> +	struct device *dev = &shid->spi->dev;
> +	bool was_reset = false;
> +
> +	guard(mutex)(&shid->power_lock);
> +	if (test_bit(SPI_HID_DEAD, &shid->flags)) {
> +		/* Stay off; only balance disable_irq() in suspend. */
> +		enable_irq(shid->spi->irq);
> +		return 0;
> +	}
> +
> +	if (!device_may_wakeup(dev)) {
> +		if (shid->power_state == HIDSPI_OFF) {

[Severity: Medium]
Can gating the power-up path on !device_may_wakeup(dev) and
shid->power_state == HIDSPI_OFF leave the device unpowered across resume
or hibernation restore?

In this commit, if shid->power_state is HIDSPI_OFF while
device_may_wakeup(dev) is true (for example, if power/wakeup is enabled
while suspended or after a failed power_up()), spi_hid_resume() skips
powering up and resetting the device. A later commit in the series ("HID:
spi-hid: add panel follower support") removes the outer
!device_may_wakeup(dev) check in spi_hid_resume(), but hibernation still
has a related problem when wakeup is enabled.

Because spi_hid_core_pm uses SYSTEM_SLEEP_PM_OPS(spi_hid_core_pm_suspend,
spi_hid_core_pm_resume), .freeze, .thaw, .poweroff, and .restore all call
spi_hid_suspend() and spi_hid_resume(). When device_may_wakeup(dev) is
true during hibernation:

1. During .freeze, spi_hid_suspend() sees device_may_wakeup(dev) and skips
   clearing SPI_HID_READY, asserting reset, calling ops->power_down(), and
   setting shid->power_state = HIDSPI_OFF. The hibernation image therefore
   saves shid->power_state as HIDSPI_ON (and conf->supply_enabled as true
   in spi-hid-of).

2. During .poweroff, spi_hid_suspend() again skips powering down the
   device, and platform power is then removed by S4 or by the boot
   kernel's .freeze before image restoration.

3. During .restore, spi_hid_resume() sees shid->power_state == HIDSPI_ON
   from the restored image, skips assert_reset(), power_up(),
   deassert_reset(), and spi_hid_wait_for_reset(), and calls
   hid_driver_reset_resume(shid->hid) while the hardware is unpowered or
   held in reset.

> +			shid->ops->assert_reset(shid->ops);
> +
> +			shid->ops->sleep_minimal_reset_delay(shid->ops);
> +
> +			error = shid->ops->power_up(shid->ops);
> +			if (error) {
> +				dev_err(dev, "%s: could not power up\n", __func__);
> +				shid->regulator_error_count++;
> +				shid->regulator_last_error = error;
> +				/* Suspend runs next even if resume fails. */
> +				enable_irq(shid->spi->irq);
> +				return error;
> +			}
> +			shid->power_state = HIDSPI_ON;
> +			reinit_completion(&shid->reset_done);
> +			shid->ops->deassert_reset(shid->ops);
> +			was_reset = true;
> +		}
> +	}
> +
> +	enable_irq(shid->spi->irq);
> +
> +	/*
> +	 * For devices that may wake up, process work that was pending when
> +	 * suspend cancelled reset_work. Otherwise suspend already dropped it.
> +	 */
> +	if (test_bit(SPI_HID_RESET_RESPONSE, &shid->flags) ||
> +	    test_bit(SPI_HID_CREATE_DEVICE, &shid->flags) ||
> +	    test_bit(SPI_HID_ERROR, &shid->flags))
> +		schedule_work(&shid->reset_work);
> +
> +	if (shid->hid) {
> +		if (was_reset)
> +			spi_hid_wait_for_reset(shid);
> +		error = hid_driver_reset_resume(shid->hid);

[Severity: High]
Should spi_hid_resume() and the power_down() error rollback in
spi_hid_suspend() check the return value of shid->ops->deassert_reset()
and handle timeouts from spi_hid_wait_for_reset()?

Unlike spi_hid_core_probe() and spi_hid_error_handler(), both sites ignore
the return value of shid->ops->deassert_reset(shid->ops) (which evaluates
the ACPI _RST method in spi_hid_acpi_deassert_reset() or drives the reset
GPIO in spi_hid_of_deassert_reset()).

If deassert_reset() fails, or if the device fails to respond before
spi_hid_wait_for_reset() times out, spi_hid_wait_for_reset() only logs a
warning and returns without setting SPI_HID_READY, setting SPI_HID_ERROR,
or scheduling shid->reset_work (and if shid->hid is NULL,
spi_hid_wait_for_reset() is not called at all).

Both functions then unconditionally call hid_driver_reset_resume(shid->hid)
while SPI_HID_READY is still 0, causing any synchronous HID requests
during reset_resume to fail with -ENODEV in __spi_hid_send_output_report(),
and leaving the device stuck in SPI_HID_RESET_PENDING with no recovery
scheduled.

[Severity: Medium]
Also in spi_hid_resume(), what happens when a wakeup-enabled device
(was_reset == false) resumes while reset or recovery work is pending?

When SPI_HID_RESET_RESPONSE, SPI_HID_CREATE_DEVICE, or SPI_HID_ERROR was
pending when spi_hid_suspend() cancelled shid->reset_work, SPI_HID_READY
is 0.

In spi_hid_resume(), schedule_work(&shid->reset_work) queues the worker
asynchronously while shid->power_lock is still held, spi_hid_wait_for_reset()
is skipped because was_reset is false, and hid_driver_reset_resume(shid->hid)
is called immediately before shid->reset_work can complete the reset and
set SPI_HID_READY.

As a result, __spi_hid_send_output_report() rejects any feature or output
reports sent by hid_driver_reset_resume() with -ENODEV:

	/* While not READY, only (re)init descriptor requests may be sent. */
	if (report->report_type != DEVICE_DESCRIPTOR &&
	    report->report_type != REPORT_DESCRIPTOR &&
	    !test_bit(SPI_HID_READY, &shid->flags))
		return -ENODEV;

> +		if (error) {
> +			dev_err(dev, "%s: failed to reset resume hid driver: %d\n",
> +				__func__, error);
> +			return error;
> +		}
> +	}
> +	return 0;
> +}

[ ... ]

> @@ -1454,6 +1620,25 @@ void spi_hid_core_remove(struct spi_device *spi)
>  }
>  EXPORT_SYMBOL_GPL(spi_hid_core_remove);
>  
> +static int spi_hid_core_pm_suspend(struct device *dev)
> +{
> +	struct spi_hid *shid = dev_get_drvdata(dev);
> +
> +	return spi_hid_suspend(shid);
> +}
> +
> +static int spi_hid_core_pm_resume(struct device *dev)
> +{
> +	struct spi_hid *shid = dev_get_drvdata(dev);
> +
> +	return spi_hid_resume(shid);
> +}
> +
> +const struct dev_pm_ops spi_hid_core_pm = {
> +	SYSTEM_SLEEP_PM_OPS(spi_hid_core_pm_suspend, spi_hid_core_pm_resume)
> +};
> +EXPORT_SYMBOL_GPL(spi_hid_core_pm);

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019-send-upstream-v6-0-cb9ba4b3d6fa@chromium.org?part=10

  reply	other threads:[~2026-10-19 19:36 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19 19:01 [PATCH v6 00/11] Add spi-hid transport driver Jingyuan Liang
2026-10-19 19:01 ` [PATCH v6 01/11] Documentation: Correction in HID output_report callback description Jingyuan Liang
2026-10-19 19:01 ` [PATCH v6 02/11] HID: Add BUS_SPI support and define HID_SPI_DEVICE macro Jingyuan Liang
2026-10-19 19:01 ` [PATCH v6 03/11] HID: spi-hid: add transport driver skeleton for HID over SPI bus Jingyuan Liang
2026-10-19 19:11   ` sashiko-bot
2026-10-19 19:01 ` [PATCH v6 04/11] HID: spi-hid: add spi-hid driver HID layer Jingyuan Liang
2026-10-19 19:26   ` sashiko-bot
2026-10-19 19:01 ` [PATCH v6 05/11] HID: spi-hid: add HID SPI protocol implementation Jingyuan Liang
2026-10-19 19:22   ` sashiko-bot
2026-10-19 19:01 ` [PATCH v6 06/11] HID: spi-hid: add spi_hid traces Jingyuan Liang
2026-10-19 19:01 ` [PATCH v6 07/11] HID: spi-hid: add ACPI support for HID over SPI Jingyuan Liang
2026-10-19 19:01 ` [PATCH v6 08/11] dt-bindings: input: Document hid-over-spi DT schema Jingyuan Liang
2026-10-20 16:34   ` Rob Herring (Arm)
2026-10-20 17:57   ` Conor Dooley
2026-10-19 19:01 ` [PATCH v6 09/11] HID: spi-hid: add device tree support for HID over SPI Jingyuan Liang
2026-10-19 19:01 ` [PATCH v6 10/11] HID: spi-hid: add power management implementation Jingyuan Liang
2026-10-19 19:36   ` sashiko-bot [this message]
2026-10-19 19:01 ` [PATCH v6 11/11] HID: spi-hid: add panel follower support Jingyuan Liang

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-178144@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=jingyliang@chromium.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=robh@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