[PATCH] usb: common: usb-conn-gpio: join IRQs before canceling work

From: Myeonghun Pak

Date: Fri Oct 02 2026 - 17:42:33 EST


The GPIO IRQ handlers can enqueue delayed work after remove has canceled
it. A similar lifetime issue exists during probe: once the ID IRQ has
been requested, a later VBUS IRQ setup failure can unwind probe while
the ID IRQ is still able to queue work.

Managed IRQ cleanup happens only after remove or failed probe returns,
so queued work can outlive the state it accesses.

Request both IRQs disabled and enable them only after setup succeeds.
During cleanup, explicitly free and synchronize the installed IRQs
before canceling delayed work. This closes the producer before joining
the work it can queue.

This issue was identified during our ongoing static-analysis research
while reviewing kernel code.

Fixes: 4602f3bff266 ("usb: common: add USB GPIO based connection detection driver")
Assisted-by: LLM
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Myeonghun Pak <mhun512@xxxxxxxxx>
---
drivers/usb/common/usb-conn-gpio.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c
index 421c3af38e06975259f4a1792aa3b3708a192d59..60badc9a2db8b8e7e621141307de3c1729e8f101 100644
--- a/drivers/usb/common/usb-conn-gpio.c
+++ b/drivers/usb/common/usb-conn-gpio.c
@@ -29,7 +29,7 @@
#define USB_GPIO_DEB_US ((USB_GPIO_DEB_MS) * 1000) /* us */

#define USB_CONN_IRQF \
- (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT)
+ (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_NO_AUTOEN)

struct usb_conn_info {
struct device *dev;
@@ -262,7 +262,7 @@
if (info->vbus_irq < 0) {
dev_err(dev, "failed to get VBUS IRQ\n");
ret = info->vbus_irq;
- goto put_role_sw;
+ goto free_id_irq;
}

ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
@@ -270,19 +270,30 @@
pdev->name, info);
if (ret < 0) {
dev_err(dev, "failed to request VBUS IRQ\n");
- goto put_role_sw;
+ goto free_id_irq;
}
}

platform_set_drvdata(pdev, info);
device_set_wakeup_capable(&pdev->dev, true);

+ info->initial_detection = true;
+
+ /* Enable the IRQs only after all the setup has succeeded. */
+ if (info->id_gpiod)
+ enable_irq(info->id_irq);
+ if (info->vbus_gpiod)
+ enable_irq(info->vbus_irq);
+
/* Perform initial detection */
- info->initial_detection = true;
usb_conn_queue_dwork(info, 0);

return 0;

+free_id_irq:
+ if (info->id_gpiod)
+ devm_free_irq(dev, info->id_irq, info);
+ cancel_delayed_work_sync(&info->dw_det);
put_role_sw:
usb_role_switch_put(info->role_sw);
return ret;
@@ -291,6 +302,11 @@
static void usb_conn_remove(struct platform_device *pdev)
{
struct usb_conn_info *info = platform_get_drvdata(pdev);
+
+ if (info->id_gpiod)
+ devm_free_irq(&pdev->dev, info->id_irq, info);
+ if (info->vbus_gpiod)
+ devm_free_irq(&pdev->dev, info->vbus_irq, info);

cancel_delayed_work_sync(&info->dw_det);