[PATCH] usb: gadget: f_ncm: restart the TX timer if the TX freelist is empty

From: Cosmin Tanislav

Date: Mon Aug 17 2026 - 15:28:13 EST


ncm_wrap_ntb() aggregates datagrams into a pending NTB, which is sent
either when it becomes full, or after 300us since the NTB was started,
using ndo_start_xmit() (eth_start_xmit()).

ndo_start_xmit() returns NETDEV_TX_BUSY when the TX freelist is empty.

For regular packets this is fine since the networking core will requeue
them, but for timer-flushed packets there is no logic to handle this
situation, and HRTIMER_NORESTART is returned from ncm_tx_timeout()
without checking eth_start_xmit()'s return value.

Under sustained TX saturation the request freelist is empty most of the
time. Since the timer is never restarted, small packets keep getting
accumulated into the NTB, until either TX_MAX_NUM_DPE packets are stored
or NTB_DEFAULT_IN_SIZE is reached, which is an unknown amount of time.

Check the return value of ndo_start_xmit() and restart the timer after
another 300us.

Cc: stable@xxxxxxxxxxxxxxx
Fixes: 6d3865f9d41f ("usb: gadget: NCM: Add transmit multi-frame.")
Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@xxxxxxxxxxx>
---
drivers/usb/gadget/function/f_ncm.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c
index bf02545b37a2..1f521c1898d0 100644
--- a/drivers/usb/gadget/function/f_ncm.c
+++ b/drivers/usb/gadget/function/f_ncm.c
@@ -1148,6 +1148,7 @@ static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data)
{
struct f_ncm *ncm = container_of(data, struct f_ncm, task_timer);
struct net_device *netdev = READ_ONCE(ncm->netdev);
+ netdev_tx_t ret;

if (netdev) {
/* XXX This allowance of a NULL skb argument to ndo_start_xmit
@@ -1158,7 +1159,11 @@ static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data)
*
* This will call directly into u_ether's eth_start_xmit()
*/
- netdev->netdev_ops->ndo_start_xmit(NULL, netdev);
+ ret = netdev->netdev_ops->ndo_start_xmit(NULL, netdev);
+ if (ret == NETDEV_TX_BUSY) {
+ hrtimer_forward_now(data, TX_TIMEOUT_NSECS);
+ return HRTIMER_RESTART;
+ }
}
return HRTIMER_NORESTART;
}
--
2.55.0