All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Muhammad Mahdi Amirpour" <ampr.mhd@gmail.com>
Cc: linux-usb@vger.kernel.org
Subject: Re: [PATCH v4] usb: ohci-omap: implement overcurrent handling for GPIO-based power control
Date: Sun, 18 Oct 2026 22:50:50 +0000	[thread overview]
Message-ID: <sashiko-outbox-176434@kernel.org> (raw)
In-Reply-To: <20261018223312.120913-1-ampr.mhd@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] usb: ohci-omap: race and inverted ordering in ohci_omap_overcurrent_irq() leaves VBUS on
- [High] usb: ohci-omap: overcurrent IRQ fails to resume suspended root hub
- [Medium] usb: ohci-omap: race and missed falling edge in probe overcurrent init
--

commit 2a3f441c427f55a1886bf324043fa831b260b3d4
Author: Muhammad Mahdi Amirpour <ampr.mhd@gmail.com>

usb: ohci-omap: implement overcurrent handling for GPIO-based power control

This commit implements overcurrent detection and VBUS power control via
GPIOs in the OMAP OHCI driver using a threaded interrupt handler and custom
root hub status and control callbacks.

> diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c
> index f6e56c4b99148..c717212439053 100644
> --- a/drivers/usb/host/ohci-omap.c
> +++ b/drivers/usb/host/ohci-omap.c

[ ... ]

> @@ -181,6 +195,128 @@ static int ohci_omap_reset(struct usb_hcd *hcd)
>  	return 0;
>  }
>  
> +static irqreturn_t ohci_omap_overcurrent_irq(int irq, void *data)
> +{
> +	struct usb_hcd *hcd = data;
> +	struct ohci_omap_priv *priv = hcd_to_ohci_omap_priv(hcd);
> +	int status;
> +	bool was_active;
> +
> +	status = gpiod_get_value_cansleep(priv->overcurrent);
> +	if (status < 0)
> +		return IRQ_HANDLED;
> +
> +	if (status && priv->power)
> +		gpiod_set_value_cansleep(priv->power, 1);
> +
> +	was_active = READ_ONCE(priv->overcurrent_active);
> +	WRITE_ONCE(priv->overcurrent_active, !!status);
> +	if (was_active != !!status)
> +		WRITE_ONCE(priv->overcurrent_change_pending, true);
> +
> +	usb_hcd_poll_rh_status(hcd);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ohci_omap_hub_status_data(struct usb_hcd *hcd, char *buf)
> +{
> +	struct ohci_omap_priv *priv = hcd_to_ohci_omap_priv(hcd);
> +	int length;
> +
> +	length = ohci_hub_status_data(hcd, buf);
> +
> +	if (!priv->overcurrent)
> +		return length;
> +
> +	if (READ_ONCE(priv->overcurrent_change_pending)) {
> +		if (!length) {
> +			buf[0] = 0;
> +			length = 1;
> +		}
> +		buf[0] |= BIT(1); /* port 1 */
> +	}
> +
> +	return length;
> +}

[Severity: High]
What happens if an overcurrent interrupt fires while the root hub is
runtime-suspended or autostopped?

When the root hub is suspended, hub_suspend() -> hub_quiesce() kills
hub->urb (leaving hcd->status_urb as NULL) and ohci_bus_suspend() places the
controller into OHCI_USB_SUSPEND.

When ohci_omap_overcurrent_irq() cuts VBUS and calls
usb_hcd_poll_rh_status(), that calls ohci_omap_hub_status_data() ->
ohci_hub_status_data(). Because the overcurrent signal is on an external
GPIO rather than the OHCI hardware pin, ohci_hub_status_data() sees no
hardware port change bits (changed == 0), so ohci_root_hub_state_changes()
does not call ohci_rh_resume() or usb_hcd_resume_root_hub():

drivers/usb/host/ohci-hub.c:ohci_root_hub_state_changes() {
	...
	case OHCI_USB_SUSPEND:
	case OHCI_USB_RESUME:
		/* if there is a port change, autostart or ask to be resumed */
		if (changed) {
			if (ohci->autostop)
				ohci_rh_resume(ohci);
			else
				usb_hcd_resume_root_hub(ohci_to_hcd(ohci));
	...
}

Then, even though ohci_omap_hub_status_data() returns length = 1,
usb_hcd_poll_rh_status() sees that hcd->status_urb is NULL, so it only sets
HCD_FLAG_POLL_PENDING and returns without waking hub_wq:

drivers/usb/core/hcd.c:usb_hcd_poll_rh_status() {
	...
		urb = hcd->status_urb;
		if (urb) {
			...
		} else {
			length = 0;
			set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
		}
	...
}

With VBUS turned off and the root hub still suspended, won't the port remain
unpowered indefinitely because hub_wq never runs port_event() to clear the
overcurrent change and restore power?

> +
> +static int ohci_omap_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
> +				 u16 wIndex, char *buf, u16 wLength)
> +{
> +	struct ohci_omap_priv *priv = hcd_to_ohci_omap_priv(hcd);
> +	int ret;
> +
> +	/*
> +	 * Drive the VBUS power GPIO alongside the OHCI hardware register.
> +	 * On boards like the OSK, the OHCI PPS bit does not control the
> +	 * real power switch — the GPIO does.
> +	 */
> +	if (priv->power && wIndex == 1) {
> +		if (typeReq == SetPortFeature &&
> +		    wValue == USB_PORT_FEAT_POWER) {
> +			ret = ohci_hub_control(hcd, typeReq, wValue, wIndex,
> +					       buf, wLength);
> +			if (!ret && !READ_ONCE(priv->overcurrent_active))
> +				gpiod_set_value_cansleep(priv->power, 0);
> +			/*
> +			 * Re-check: the overcurrent IRQ may have fired
> +			 * between the check above and the GPIO call.
> +			 * A mutex would close this fully, but the window
> +			 * is microseconds and the hardware current limiter
> +			 * provides the final safety net.
> +			 */
> +			if (!ret && READ_ONCE(priv->overcurrent_active))
> +				gpiod_set_value_cansleep(priv->power, 1);
> +			return ret;
> +		}

[Severity: High]
Can a concurrent SetPortFeature(USB_PORT_FEAT_POWER) in
ohci_omap_hub_control() race with ohci_omap_overcurrent_irq() and leave VBUS
switched on during an active overcurrent condition?

In ohci_omap_overcurrent_irq(), gpiod_set_value_cansleep(priv->power, 1) is
called before WRITE_ONCE(priv->overcurrent_active, !!status). Because
priv->power is on an I2C-attached TPS65010 expander on OMAP1 OSK, that GPIO
write sleeps across an I2C transfer.

If ohci_omap_hub_control() runs while ohci_omap_overcurrent_irq() is
sleeping in gpiod_set_value_cansleep(priv->power, 1) (or before the IRQ
thread is scheduled to run WRITE_ONCE(priv->overcurrent_active, true)),
ohci_omap_hub_control() can observe priv->overcurrent_active as false on
both checks and drive priv->power back to 0. When
ohci_omap_overcurrent_irq() finishes and sets priv->overcurrent_active to
true, it has already completed its GPIO write, leaving VBUS enabled while
priv->overcurrent_active is true.

Even if WRITE_ONCE(priv->overcurrent_active, !!status) were moved before the
GPIO write in ohci_omap_overcurrent_irq(), if ohci_omap_hub_control() passes
the initial !READ_ONCE(priv->overcurrent_active) check and is preempted
while an overcurrent event asserts, cuts VBUS, and then deasserts once
current stops flowing, priv->overcurrent_active will return to false and
ohci_omap_hub_control() will turn VBUS back on and pass the re-check without
the hub core authorizing recovery. Would a mutex serializing the GPIO writes
and overcurrent state updates avoid this?

[ ... ]

> @@ -277,23 +412,73 @@ static int ohci_hcd_omap_probe(struct platform_device *pdev)
>  	if (!hcd->regs) {
>  		dev_err(&pdev->dev, "can't ioremap OHCI HCD\n");
>  		retval = -ENOMEM;
> -		goto err2;
> +		goto err_release_mem;
>  	}
>  
>  	irq = platform_get_irq(pdev, 0);
>  	if (irq < 0) {
>  		retval = irq;
> -		goto err3;
> +		goto err_iounmap;
>  	}
>  	retval = usb_add_hcd(hcd, irq, 0);
>  	if (retval)
> -		goto err3;
> +		goto err_iounmap;
> +
> +	if (priv->overcurrent) {
> +		int status;
> +
> +		priv->overcurrent_irq = gpiod_to_irq(priv->overcurrent);
> +		if (priv->overcurrent_irq < 0) {
> +			retval = priv->overcurrent_irq;
> +			goto err_remove_hcd;
> +		}
> +
> +		retval = request_threaded_irq(
> +			priv->overcurrent_irq, NULL, ohci_omap_overcurrent_irq,
> +			IRQF_ONESHOT | IRQF_NO_AUTOEN | IRQF_TRIGGER_RISING |
> +				IRQF_TRIGGER_FALLING,
> +			"ohci-omap-overcurrent", hcd);
> +		if (retval) {
> +			priv->overcurrent_irq = -1;
> +			goto err_remove_hcd;
> +		}
> +
> +		status = gpiod_get_value_cansleep(priv->overcurrent);
> +		if (status < 0) {
> +			retval = status;
> +			goto err_remove_hcd;
> +		}
> +
> +		WRITE_ONCE(priv->overcurrent_active, !!status);
> +		WRITE_ONCE(priv->overcurrent_change_pending, !!status);
> +
> +		if (status && priv->power)
> +			gpiod_set_value_cansleep(priv->power, 1);
> +
> +		enable_irq(priv->overcurrent_irq);
> +		if (status)
> +			usb_hcd_poll_rh_status(hcd);
> +	}

[Severity: Medium]
In ohci_hcd_omap_probe(), if status is non-zero at probe time, could cutting
VBUS power before calling enable_irq(priv->overcurrent_irq) cause the
falling edge on priv->overcurrent to be missed?

When gpiod_set_value_cansleep(priv->power, 1) turns off VBUS (which sleeps
on an I2C GPIO expander), the power switch can deassert its overcurrent line
(1 -> 0) before enable_irq(priv->overcurrent_irq) is called. Because
priv->overcurrent_irq was requested with IRQF_NO_AUTOEN, that falling edge
occurs while the interrupt is still disabled, leaving
priv->overcurrent_active stuck at true and preventing subsequent
SetPortFeature(USB_PORT_FEAT_POWER) requests from ever re-enabling VBUS.

Also, since usb_add_hcd() calls ohci_omap_reset() (which enables VBUS) and
registers the root hub before priv->overcurrent_irq is requested, hub_wq can
already be running ohci_omap_hub_control() concurrently with this
initialization block.

[ ... ]

>
> -- 
> 2.51.2
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261018223312.120913-1-ampr.mhd@gmail.com?part=1

  reply	other threads:[~2026-10-18 22:50 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-18 22:33 [PATCH v4] usb: ohci-omap: implement overcurrent handling for GPIO-based power control Muhammad Mahdi Amirpour
2026-10-18 22:50 ` sashiko-bot [this message]
2026-10-19  6:46 ` Greg KH
  -- strict thread matches above, loose matches on Subject: below --
2026-10-19 16:15 Muhammad Mahdi Amirpour
2026-10-20 20:23 ` Andreas Kemnade

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-176434@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=ampr.mhd@gmail.com \
    --cc=linux-usb@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.