All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Ryan Brue" <ryanbrue.dev@gmail.com>
Cc: linux-usb@vger.kernel.org
Subject: Re: [PATCH RESEND] usb: mtu3: don't run gadget completion callbacks with interrupts disabled
Date: Mon, 02 Nov 2026 17:10:45 +0000	[thread overview]
Message-ID: <sashiko-outbox-193807@kernel.org> (raw)
In-Reply-To: <20261102-usb-mtu3-gadget-completion-callback-fix-v1-1-93c3a6dda31f@gmail.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] usb: mtu3: spin_lock_bh() in mtu3_irq() leaves hardirqs enabled in callbacks
--

commit 153a59750e6c0f99e7e0b96dbcf13012e52f3fc8
Author: Ryan Brue <ryanbrue.dev@gmail.com>

usb: mtu3: don't run gadget completion callbacks with interrupts disabled

This commit changes mtu3_irq() in drivers/usb/mtu3/mtu3_core.c to acquire
and release mtu->lock using spin_lock_bh() and spin_unlock_bh() instead of
spin_lock_irqsave() and spin_unlock_irqrestore() so that softirqs are
disabled when gadget completion callbacks invoke netif_rx().

> diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c
> index a40bf5bad2d5d..ed0e426645fd6 100644
> --- a/drivers/usb/mtu3/mtu3_core.c
> +++ b/drivers/usb/mtu3/mtu3_core.c
> @@ -790,10 +790,9 @@ static irqreturn_t mtu3_u2_common_isr(struct mtu3 *mtu)
>  static irqreturn_t mtu3_irq(int irq, void *data)
>  {
>  	struct mtu3 *mtu = (struct mtu3 *)data;
> -	unsigned long flags;
>  	u32 level1;
>  
> -	spin_lock_irqsave(&mtu->lock, flags);
> +	spin_lock_bh(&mtu->lock);
>  
>  	/* U3D_LV1ISR is RU */
>  	level1 = mtu3_readl(mtu->mac_base, U3D_LV1ISR);
> @@ -814,7 +813,7 @@ static irqreturn_t mtu3_irq(int irq, void *data)
>  	if (level1 & QMU_INTR)
>  		mtu3_qmu_isr(mtu);
>  
> -	spin_unlock_irqrestore(&mtu->lock, flags);
> +	spin_unlock_bh(&mtu->lock);

[Severity: High]
Does replacing spin_lock_irqsave() with spin_lock_bh() in mtu3_irq() break
the USB gadget completion contract and risk a hardirq deadlock?

When mtu3_irq() invokes mtu3_qmu_isr() or mtu3_ep0_isr() ->
mtu3_req_complete() (as well as forward_to_driver(),
mtu3_gadget_disconnect(), mtu3_gadget_suspend(), or mtu3_gadget_resume() in
drivers/usb/mtu3/mtu3_gadget.c), those helpers release mtu->lock with a
plain spin_unlock():

drivers/usb/mtu3/mtu3_gadget.c:mtu3_req_complete() {
	...
	spin_unlock(&mtu->lock);
	usb_gadget_giveback_request(&mep->ep, req);
	spin_lock(&mtu->lock);
	...
}

Because mtu3_irq() now only holds spin_lock_bh(&mtu->lock), dropping
mtu->lock with spin_unlock() leaves hardware interrupts enabled across
usb_gadget_giveback_request() and the gadget driver callbacks.

However, the struct usb_request kerneldoc in include/linux/usb/gadget.h
requires completion callbacks to be called with hardware interrupts
disabled:

include/linux/usb/gadget.h:
 * @complete: Function called when request completes, so this request and
 *	its buffer may be re-used.  The function will always be called with
 *	interrupts disabled, and it must not sleep.

Gadget function drivers rely on this guarantee and take hardirq-unsafe
locks with plain spin_lock() inside their callbacks. For example, in
drivers/usb/gadget/function/u_serial.c, gs_console_complete_out(),
gs_console_connect(), and gs_console_disconnect() acquire cons->lock with a
plain spin_lock():

drivers/usb/gadget/function/u_serial.c:gs_console_complete_out() {
	...
	default:
		spin_lock(&cons->lock);
		req->length = 0;
		schedule_work(&cons->work);
		spin_unlock(&cons->lock);
		break;
	}
}

Meanwhile, gs_console_write() is a console write callback that can be
invoked from any hardirq via printk() and acquires cons->lock with
spin_lock_irqsave(). Running completion or setup callbacks with hardware
interrupts enabled causes a lockdep inconsistent lock state splat and
deadlocks if a hardware interrupt on the same CPU calls printk() while
cons->lock is held. Similarly, u_audio_iso_complete() in
drivers/usb/gadget/function/u_audio.c calls snd_pcm_stream_lock() assuming
hardware interrupts are disabled.

In addition, mtu->lock itself can be acquired from hardirq context via
usb_ep_queue() -> mtu3_gadget_queue() when f_midi2 is driven by the ALSA
sequencer hrtimer backend (snd_hrtimer_callback() -> snd_timer_interrupt()
-> snd_seq_timer_interrupt() -> snd_seq_check_queue() ->
seq_ump_process_event() -> snd_rawmidi_kernel_write() ->
f_midi2_ump_trigger() -> process_ump_transmit() -> usb_ep_queue()), and
other callers of mtu3_req_complete() such as mtu3_gadget_ep_disable(),
mtu3_gadget_dequeue(), and mtu3_gadget_stop() still hold
spin_lock_irqsave(&mtu->lock, flags).

The commit message notes that dwc3 fixed the same issue in commit
84918a89d6ef ("usb: dwc3: gadget: Let the interrupt handler disable bottom
halves."), but dwc3 kept spin_lock_irqsave() and wrapped it with
local_bh_disable() and local_bh_enable():

drivers/usb/dwc3/gadget.c:dwc3_thread_interrupt() {
	unsigned long flags;
	irqreturn_t ret = IRQ_NONE;
	int i;

	local_bh_disable();
	spin_lock_irqsave(&dwc->lock, flags);
	ret = dwc3_process_event_buf(evt);
	spin_unlock_irqrestore(&dwc->lock, flags);
	local_bh_enable();

	return ret;
}

In net/core/dev.c, netif_rx() checks:

	need_bh_off = !(hardirq_count() | softirq_count());

When local_bh_disable() is held around spin_lock_irqsave() and
spin_unlock_irqrestore() (as done in dwc3, cdns3, and cdnsp),
softirq_count() is non-zero so netif_rx() does not call
local_bh_disable()/local_bh_enable() while hardware interrupts remain
disabled across usb_gadget_giveback_request(), and local_bh_enable() runs
pending softirqs after spin_unlock_irqrestore() re-enables hardware
interrupts.

Should mtu3_irq() wrap spin_lock_irqsave() and spin_unlock_irqrestore()
with local_bh_disable() and local_bh_enable() instead of using
spin_lock_bh()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-usb-mtu3-gadget-completion-callback-fix-v1-1-93c3a6dda31f@gmail.com?part=1

  reply	other threads:[~2026-11-02 17:10 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-02 16:29 [PATCH RESEND] usb: mtu3: don't run gadget completion callbacks with interrupts disabled Ryan Brue
2026-11-02 17:10 ` sashiko-bot [this message]
2026-11-03 23:29   ` Ryan Brue
  -- strict thread matches above, loose matches on Subject: below --
2026-09-29 15:04 Ryan Brue
2026-11-02  3:45 ` Ryan Brue

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-193807@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=ryanbrue.dev@gmail.com \
    --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.