From: "Elvira Khabirova" <lineprinter0@gmail.com>
To: <sashiko-reviews@lists.linux.dev>,
"Elvira Khabirova" <lineprinter0@gmail.com>
Cc: <linux-usb@vger.kernel.org>
Subject: Re: [PATCH v2] usb: musb: gadget: don't flush a waiting RX packet when queueing a request
Date: Sat, 07 Nov 2026 13:12:45 +0100 [thread overview]
Message-ID: <DMOYWU5HB1UM.BQG0BZT7RFCP@gmail.com> (raw)
In-Reply-To: <sashiko-outbox-202856@kernel.org>
On Sat Nov 7, 2026 at 12:30 PM CET, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential
> issues to consider:
> - [High] usb: musb: gadget: overlapping musb_g_giveback() calls wedge
> ep->busy
> - [High] usb: musb: gadget: race and ordering bug in
> musb_gadget_disable() for rx_kick
> - [Medium] usb: musb: gadget: lost rx_kick across system suspend and
> resume
> --
>
> commit cef9b9e2278bf856fd53af421c899c0ae67866b7
> Author: Elvira Khabirova <lineprinter0@gmail.com>
>
> usb: musb: gadget: don't flush a waiting RX packet when queueing a request
>
> This commit replaces the RX FIFO flush in musb_ep_restart() with a
> system_bh_wq work item that invokes musb_g_rx(), and defers RX processing
> via ep->rx_kick_deferred while ep->busy is set.
>
>> diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c
>> index 016d3f3fc1e05..c71c60efe5106 100644
>> --- a/drivers/usb/musb/musb_gadget.c
>> +++ b/drivers/usb/musb/musb_gadget.c
>> @@ -148,6 +148,21 @@ __acquires(ep->musb->lock)
>> usb_gadget_giveback_request(&req->ep->end_point, &req->request);
>> spin_lock(&musb->lock);
>> ep->busy = busy;
>> +
>> + if (!ep->busy && ep->rx_kick_deferred) {
>> + ep->rx_kick_deferred = false;
>> + if (!queue_work(system_bh_wq, &ep->rx_kick))
>> + pm_runtime_put_noidle(musb->controller);
>> + }
>> +}
>
> [Severity: High]
> Can overlapping musb_g_giveback() calls finish out of LIFO order and leave
> ep->busy permanently stuck at 1?
>
> musb_g_giveback() saves ep->busy in a stack variable, sets ep->busy = 1,
> and drops musb->lock around usb_gadget_giveback_request(). With RX
> givebacks running in system_bh_wq, a second giveback on the same endpoint
> (for example via musb_gadget_dequeue(), which enters musb_g_giveback()
> when musb_ep->busy is set) can overlap on another CPU:
>
> CPU0 CPU1
> musb_g_rx_kick()
> musb_g_rx()
> musb_g_giveback()
> busy = ep->busy; // 0
> ep->busy = 1;
> spin_unlock(&musb->lock)
> usb_gadget_giveback_request()
> musb_gadget_dequeue()
> spin_lock_irqsave(&musb->lock, ...)
> musb_g_giveback()
> busy = ep->busy; // 1
> ep->busy = 1;
> spin_unlock(&musb->lock)
> usb_gadget_giveback_request()
> spin_lock(&musb->lock)
> ep->busy = 0;
> queue_work(..., &ep->rx_kick)
> spin_unlock(&musb->lock)
> spin_lock(&musb->lock)
> ep->busy = 1; <-- stuck at 1
>
> First, CPU0 clears ep->busy to 0 and schedules ep->rx_kick while CPU1 is
> still in usb_gadget_giveback_request(). Second, when CPU1 finishes, it
> writes its saved busy = 1 back to ep->busy. After that, every subsequent
> musb_g_rx(), musb_g_rx_kick(), and musb_gadget_queue() call sees
> ep->busy == 1 and defers work forever, wedging the endpoint and leaking
> the runtime PM reference taken by musb_g_defer_rx_kick().
>
> Making ep->busy an active giveback counter (incremented before dropping
> musb->lock and decremented after re-acquiring it) would ensure ep->busy
> only reaches 0 when the last giveback completes.
Yes, that is possible, but this is a pre-existing bug. I even have
a separate patch prepared to make `busy` a counter instead of a flag.
This patch does not aggravate this problem.
The same race is possible between a dequeue and a HW IRQ.
>> @@ -1111,6 +1137,12 @@ static int musb_gadget_disable(struct usb_ep *ep)
>>
>> /* abort all pending DMA and requests */
>> nuke(musb_ep, -ESHUTDOWN);
>> + if (cancel_work(&musb_ep->rx_kick))
>> + pm_runtime_put_autosuspend(musb->controller);
>> + if (musb_ep->rx_kick_deferred) {
>> + musb_ep->rx_kick_deferred = false;
>> + pm_runtime_put_autosuspend(musb->controller);
>> + }
>>
>> musb_ep->desc = NULL;
>> musb_ep->end_point.desc = NULL;
>
> [Severity: High]
> Can musb_g_rx_kick() run while nuke() drops musb->lock or after the
> endpoint is re-enabled?
>
> Because musb_ep->desc is cleared after nuke(), musb->lock is dropped
> inside nuke() -> musb_g_giveback() while musb_ep->desc is still non-NULL.
> If an earlier giveback on another CPU finishes during that window, it
> restores ep->busy = 0 and queues ep->rx_kick, allowing musb_g_rx_kick() ->
> musb_g_rx() -> rxstate() to run after nuke() has already freed
> musb_ep->dma.
Good catch, and actually I think this can be resolved with making
ep->busy a counter instead.
I will send them together as a series for v3.
> In addition, cancel_work() is non-synchronous:
>
> CPU0 CPU1
> <softirq>
> musb_g_rx_kick()
> // waiting for musb->lock
> musb_gadget_disable()
> spin_lock_irqsave(&musb->lock, ...)
> cancel_work(&musb_ep->rx_kick) // false
> musb_ep->desc = NULL;
> spin_unlock_irqrestore(&musb->lock, ...)
> musb_gadget_enable()
> spin_lock_irqsave(&musb->lock, ...)
> musb_ep->desc = desc;
> musb_ep->busy = 0;
> spin_unlock_irqrestore(&musb->lock, ...)
> spin_lock_irqsave(&musb->lock, ...)
> if (!musb_ep->desc || ...) // false
> musb_g_rx() <-- stale kick runs
>
> Clearing musb_ep->desc and musb_ep->end_point.desc before calling nuke()
> prevents musb_g_rx_kick() and musb_gadget_queue() from acting on the
> endpoint while nuke() drops musb->lock, and prevented stale kicks will see
> the updated state if musb_gadget_enable() also cancels any pending
> rx_kick.
Is this any different from before the patch, with a HW IRQ?
I think a stale kick handles everything well, if there is no work,
musb_g_rx() just returns.
>> @@ -1810,6 +1881,36 @@ void musb_gadget_cleanup(struct musb *musb)
>>
>> cancel_delayed_work_sync(&musb->gadget_work);
>> usb_del_gadget_udc(&musb->g);
>> +
>> + musb_gadget_disable_rx_kicks(musb);
>> +}
>> +
>> +static void musb_g_disable_rx_kick(struct musb *musb, struct musb_ep *musb_ep)
>> +{
>> + if (musb_ep->rx_kick.func && disable_work_sync(&musb_ep->rx_kick))
>> + pm_runtime_put_noidle(musb->controller);
>> +}
>> +
>> +void musb_gadget_disable_rx_kicks(struct musb *musb)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < musb->nr_endpoints; i++) {
>> + musb_g_disable_rx_kick(musb, &musb->endpoints[i].ep_in);
>> + musb_g_disable_rx_kick(musb, &musb->endpoints[i].ep_out);
>> + }
>> +}
>> +
>> +void musb_gadget_enable_rx_kicks(struct musb *musb)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < musb->nr_endpoints; i++) {
>> + if (musb->endpoints[i].ep_in.rx_kick.func)
>> + enable_work(&musb->endpoints[i].ep_in.rx_kick);
>> + if (musb->endpoints[i].ep_out.rx_kick.func)
>> + enable_work(&musb->endpoints[i].ep_out.rx_kick);
>> + }
>> }
>
> [Severity: Medium]
> Does musb_gadget_disable_rx_kicks() lose a pending or deferred rx_kick
> across system suspend and resume?
>
> If ep->rx_kick is pending when musb_suspend() calls disable_work_sync(),
> the work item is cancelled and its runtime PM reference is dropped, or if
> musb_g_rx_kick() is currently in musb_g_giveback() with
> ep->rx_kick_deferred set, disable_work_sync() marks ep->rx_kick disabled
> before waiting for completion so queue_work() in musb_g_giveback() fails.
> On resume, musb_gadget_enable_rx_kicks() only calls enable_work(), which
> does not re-queue the cancelled work. When the USB session is preserved
> across suspend/resume (such as with MUSB_PRESERVE_SESSION or standby),
> MUSB_RXCSR_RXPKTRDY remains set in hardware with no interrupt to
> re-trigger it, stalling RX on the endpoint.
I see, I wish I had more hardware to test on.
This seems to be possible on da8xx.
I will find a way to re-queue the cancelled work items for v3.
prev parent reply other threads:[~2026-11-07 12:12 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 10:55 [PATCH v2] usb: musb: gadget: don't flush a waiting RX packet when queueing a request Elvira Khabirova
2026-11-07 11:30 ` sashiko-bot
2026-11-07 12:12 ` Elvira Khabirova [this message]
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=DMOYWU5HB1UM.BQG0BZT7RFCP@gmail.com \
--to=lineprinter0@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.