Re: [PATCH 1/1] usb: dwc3: gadget: fix IRQ storm on invalid event buffer count

Thinh Nguyen <[email protected]>
Newsgroups org.kernel.vger.linux-kernel,org.kernel.vger.linux-usb,org.kernel.vger.stable
Message-ID <anJqxCgXNF4WN1Qe@vbox>
On Mon, Jul 27, 2026, Jiazi Liu wrote:
> When dwc3_check_event_buf() reads a GEVNTCOUNT value exceeding the
> event buffer length, commit 63ccd26cd1f6 ("usb: dwc3: gadget: check
> that event count does not exceed event buffer length") returns IRQ_NONE
> without writing back GEVNTCOUNT. Since the DWC3 interrupt is
> level-triggered, the uncleared IRQ source keeps the line asserted,
> causing a tight IRQ storm that accumulates 99,900 unhandled interrupts
> and triggers spurious.c:184 BUG -> kernel panic.
> 
> The resulting call stack:
>   __report_bad_irq+0xac/0xc8
>   note_interrupt+0x340/0x468
>   handle_irq_event+0xac/0xc0
>   handle_fasteoi_irq+0x120/0x228
>   gic_handle_irq+0x68/0x108
>   ...
>   kernel BUG at kernel/irq/spurious.c:184
> 
> To reproduce, write a bogus value exceeding the event buffer length
> directly to the GEVNTCOUNT register:
> 
>   devmem <DWC3_BASE + 0xc40c> 4 0x1004
> 
> Write the bogus count back to GEVNTCOUNT to clear the IRQ source,
> consistent with the stale event clearing pattern in
> dwc3_event_buffers_setup(), and schedule a soft disconnect via
> softcon_work to recover the controller state.
> 
> Fixes: 63ccd26cd1f6 ("usb: dwc3: gadget: check that event count does not exceed event buffer length")
> Cc: [email protected]
> Signed-off-by: Jiazi Liu <[email protected]>
> ---
>  drivers/usb/dwc3/core.h   |  2 ++
>  drivers/usb/dwc3/gadget.c | 23 ++++++++++++++++++++++-
>  2 files changed, 24 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
> index 608daeb7ef10..0881ed7e956e 100644
> --- a/drivers/usb/dwc3/core.h
> +++ b/drivers/usb/dwc3/core.h
> @@ -1004,6 +1004,7 @@ struct dwc3_glue_ops {
>  /**
>   * struct dwc3 - representation of our controller
>   * @drd_work: workqueue used for role swapping
> + * @softcon_work: workqueue used for soft connect/disconnect recovery

Rename this to err_recovery_work and update the description.

>   * @ep0_trb: trb which is used for the ctrl_req
>   * @bounce: address of bounce buffer
>   * @setup_buf: used while precessing STD USB requests
> @@ -1189,6 +1190,7 @@ struct dwc3_glue_ops {
>   */
>  struct dwc3 {
>  	struct work_struct	drd_work;
> +	struct work_struct	softcon_work;
>  	struct dwc3_trb		*ep0_trb;
>  	void			*bounce;
>  	u8			*setup_buf;
> diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
> index fa0f16ffafef..9b24259272a4 100644
> --- a/drivers/usb/dwc3/gadget.c
> +++ b/drivers/usb/dwc3/gadget.c
> @@ -4669,7 +4669,18 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt)
>  	if (count > evt->length) {
>  		dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n",
>  			count, evt->length);

We probably no longer need the dev_err to be ratelimited.

> -		return IRQ_NONE;
> +		/*
> +		 * The DWC3 interrupt is level-triggered. Returning IRQ_NONE

This comment is out of context now that it's no longer IRQ_NONE. Most of
the details here are only relevant if we return IRQ_NONE. What we need
to document here why this is a fatal error and require reinitializing
the controller. Document that the driver and the controller are out of
sync to which event is consumed.

> +		 * without clearing the IRQ source leaves the line asserted,
> +		 * causing a tight IRQ storm that triggers spurious.c:184 BUG.
> +		 * Write the bogus count back to GEVNTCOUNT to clear the source,
> +		 * consistent with the stale event clearing in
> +		 * dwc3_event_buffers_setup(), then schedule a soft disconnect
> +		 * to recover the controller state.
> +		 */
> +		dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), count);
> +		schedule_work(&dwc->softcon_work);
> +		return IRQ_HANDLED;
>  	}
>  
>  	evt->count = count;
> @@ -4729,6 +4740,14 @@ static void dwc_gadget_release(struct device *dev)
>  	kfree(gadget);
>  }
>  
> +static void dwc3_softcon_work(struct work_struct *work)
> +{
> +	struct dwc3 *dwc = container_of(work, struct dwc3, softcon_work);
> +
> +	dev_err(dwc->dev, "event buffer error: performing soft disconnect\n");

Remove this dev_err(). The error message should be at the caller where
the specific failure condition is known, not the generic work handler.

> +	dwc3_gadget_soft_disconnect(dwc);

Check and follow the dwc3_gadget_suspend() and dwc3_gadget_resume()
logic for soft-disconnect and soft-connect to make sure the internal
states are updated and notify the gadget drivers correctly.

Perhaps you can also recover with soft-connect after soft-disconnect.

> +}
> +
>  /**
>   * dwc3_gadget_init - initializes gadget related registers
>   * @dwc: pointer to our controller context structure
> @@ -4772,6 +4791,7 @@ int dwc3_gadget_init(struct dwc3 *dwc)
>  	}
>  
>  	init_completion(&dwc->ep0_in_setup);
> +	INIT_WORK(&dwc->softcon_work, dwc3_softcon_work);
>  	dwc->gadget = kzalloc_obj(struct usb_gadget);
>  	if (!dwc->gadget) {
>  		ret = -ENOMEM;
> @@ -4868,6 +4888,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc)
>  	if (!dwc->gadget)
>  		return;
>  
> +	cancel_work_sync(&dwc->softcon_work);
>  	dwc3_enable_susphy(dwc, true);
>  	usb_del_gadget(dwc->gadget);
>  	dwc3_gadget_free_endpoints(dwc);
> -- 
> 2.50.1 (Apple Git-155)
> 

Thanks,
Thinh
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.