NetBSD Problem Report #59939
From www@netbsd.org Sat Jan 24 21:27:07 2026
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
client-signature RSA-PSS (2048 bits) client-digest SHA256)
(Client CN "mail.netbsd.org", Issuer "R13" (verified OK))
by mollari.NetBSD.org (Postfix) with ESMTPS id 1C7021A923D
for <gnats-bugs@gnats.NetBSD.org>; Sat, 24 Jan 2026 21:27:07 +0000 (UTC)
Message-Id: <20260124212705.CF6881A923E@mollari.NetBSD.org>
Date: Sat, 24 Jan 2026 21:27:05 +0000 (UTC)
From: campbell+netbsd@mumble.net
Reply-To: campbell+netbsd@mumble.net
To: gnats-bugs@NetBSD.org
Subject: usbnet(9): support multi-packet tx transfers
X-Send-Pr-Version: www-1.0
X-From4GNATS: "campbell+netbsd@mumble.net via gnats" <gnats-admin@NetBSD.org>
>Number: 59939
>Category: kern
>Synopsis: usbnet(9): support multi-packet tx transfers
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Sat Jan 24 21:30:00 +0000 2026
>Last-Modified: Mon Jan 26 04:40:01 +0000 2026
>Originator: Taylor R Campbell
>Release: current, 11, 10, 9, ...
>Organization:
The NetBSD Foundation The NetBSD Foundation The NetBSD Foundation
>Environment:
>Description:
Some USB ethernet devices such as ure(4) support transmitting
multiple packets in a single USB transfer.^[citation needed]
usbnet(9) currently supports _receiving_ multiple packets in a
single USB transfer -- it is the driver's responsibility to
iterate over the packets in uno_rx_loop and run usbnet_enqueue
on each of them:
(usbnet(9) transfer completion callback)
351 static void
352 usbnet_rxeof(struct usbd_xfer *xfer, void *priv, usbd_status status)
353 {
...
388 uno_rx_loop(un, c, total_len);
https://nxr.netbsd.org/xref/src/sys/dev/usb/usbnet.c?r=1.121#347
944 static void
945 ure_uno_rx_loop(struct usbnet *un, struct usbnet_chain *c, uint32_t total_len)
946 {
...
953 do {
...
976 usbnet_enqueue(un, buf, pkt_len - ETHER_CRC_LEN,
977 ure_rxcsum(ifp, &rxhdr), 0, 0);
978
979 pkt_count++;
980
981 } while (total_len > 0);
https://nxr.netbsd.org/xref/src/sys/dev/usb/if_ure.c?r=1.60#944
But usbnet(9) only _transmits_ a single packet at a time in
each USB transfer:
523 while (cd->uncd_tx_cnt < un->un_tx_list_cnt) {
524 IFQ_POLL(&ifp->if_snd, m);
...
533 length = uno_tx_prepare(un, m, c);
...
546 usbd_setup_xfer(c->unc_xfer, c, c->unc_buf, length,
547 un->un_tx_xfer_flags, 10000, usbnet_txeof);
548
549 /* Transmit */
550 usbd_status err = usbd_transfer(c->unc_xfer);
551 if (err != USBD_IN_PROGRESS) {
...
556 }
557 done_transmit = true;
558
559 IFQ_DEQUEUE(&ifp->if_snd, m);
https://nxr.netbsd.org/xref/src/sys/dev/usb/usbnet.c?r=1.121#523
For better performance,^[citation needed] it should offer the
driver a batch of packets and let the driver say when to stop
and send out a transfer (probably when the transfer buffer is
full).
As a side note, I notice that usbnet(9) doesn't make
ABI-compatible extensions easy -- there's no version numbers or
feature flags for a driver to use. We should consider having a
version number on struct usbnet_ops so we can safely add new
optional operations for drivers to implement without changing
the module ABI.
>How-To-Repeat:
observe the competition claiming to speed up
>Fix:
New argument `offset' to uno_tx_prepare callback. The packets
m[0], m[1], m[2], ... to be transmitted will be passed to the
driver roughly like:
length0 = uno_tx_prepare(un, c, m[0], 0);
length1 = uno_tx_prepare(un, c, m[1], length0);
length2 = uno_tx_prepare(un, c, m[2], length0 + length1);
...
=> If uno_tx_prepare returns 0 for the _first_ packet m[0],
we drop it because it can't be transmitted.
=> If uno_tx_prepare returns 0 for packet m[i] when i > 0, we
transfer what we have and put m[i] back in the queue for the
next transfer.
>Audit-Trail:
From: Taylor R Campbell <riastradh@NetBSD.org>
To: netbsd-bugs@NetBSD.org, gnats-bugs@NetBSD.org
Cc: Jared McNeill <jmcneill@invisible.ca>
Subject: Re: kern/59939: usbnet(9): support multi-packet tx transfers
Date: Sat, 24 Jan 2026 21:44:35 +0000
This is a multi-part message in MIME format.
--=_rFLsIlDMAmZS54PhaM3eE2ii5a8qA3Zz
Compile-tested only so far. Doesn't address the ABI compatibility
issue, just does this in a way to maintain API compatibility so I
don't have to patch all the existing usbnet(9) drivers in tree.
Handles up to 17 packets in a batch. Could maybe eliminate that
arbitrary constant by adapting it to use its own linked list of mbufs
for the unwind path if the transfer fails, since the mbufs are no
longer on the ifq. TBD; let's see if this is worth doing first.
--=_rFLsIlDMAmZS54PhaM3eE2ii5a8qA3Zz
Content-Type: text/plain; charset="ISO-8859-1"; name="pr59939-multipkttxxfer"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="pr59939-multipkttxxfer.patch"
# HG changeset patch
# User Taylor R Campbell <riastradh@NetBSD.org>
# Date 1769290484 0
# Sat Jan 24 21:34:44 2026 +0000
# Branch trunk
# Node ID 0c1b80d41c95e437ff64c85b0e72466bebf8dd60
# Parent 2abb22668d716977f0c5b6d599d23691f2845a30
# EXP-Topic riastradh-pr59939-usbnetmultipkttxxfer
WIP: usbnet(9): Support multi-packet tx transfers.
New usbnet_ops operation uno_tx_prepare1(un, c, m, offset) asks the
driver to encode packet m at the given byte offset in the tx chain
buffer c, and returns the number of bytes consumed by that encoding
with any device-specific header included, or returns zero if it
wouldn't fit.
uno_tx_prepare(un, c, m) should be equivalent to uno_tx_prepare1(un,
c, m, 0), but the original operation remains intact in this draft in
order to avoid having to update all the drivers that don't support
multi-packet tx transfers: usbnet(9) will only call uno_tx_prepare1
with a nonzero offset if it is provided by the driver.
XXX kernel revbump -- breaks usbnet(9) module ABI by expanding struct
usbnet_ops with no version or feature flag on which to gate access
kern/59939: usbnet(9): support multi-packet tx transfers
diff -r 2abb22668d71 -r 0c1b80d41c95 sys/dev/usb/usbnet.c
--- a/sys/dev/usb/usbnet.c Thu Jan 22 03:22:43 2026 +0000
+++ b/sys/dev/usb/usbnet.c Sat Jan 24 21:34:44 2026 +0000
@@ -229,6 +229,14 @@ uno_tx_prepare(struct usbnet *un, struct
return (*un->un_ops->uno_tx_prepare)(un, m, c);
}
=20
+static unsigned
+uno_tx_prepare1(struct usbnet *un, struct mbuf *m, struct usbnet_chain *c,
+ unsigned offset)
+{
+ usbnet_isowned_tx(un);
+ return (*un->un_ops->uno_tx_prepare1)(un, m, c, offset);
+}
+
static void
uno_rx_loop(struct usbnet *un, struct usbnet_chain *c, uint32_t total_len)
{
@@ -521,26 +529,72 @@ usbnet_start_locked(struct ifnet *ifp)
idx =3D cd->uncd_tx_prod;
count =3D 0;
while (cd->uncd_tx_cnt < un->un_tx_list_cnt) {
+ struct mbuf *batch[16]; /* XXX arbitrary limit */
+ unsigned nbatch =3D 0;
+
IFQ_POLL(&ifp->if_snd, m);
if (m =3D=3D NULL) {
DPRINTF("start called, queue empty", 0, 0, 0, 0);
break;
}
+ IFQ_DEQUEUE(&ifp->if_snd, m);
+
+ /*
+ * Obeying the MTU is the sender's responsibility.
+ */
KASSERT(m->m_pkthdr.len <=3D un->un_tx_bufsz);
=20
- struct usbnet_chain *c =3D &cd->uncd_tx_chain[idx];
+ struct usbnet_chain *const c =3D &cd->uncd_tx_chain[idx];
=20
length =3D uno_tx_prepare(un, m, c);
if (length =3D=3D 0) {
DPRINTF("uno_tx_prepare gave zero length", 0, 0, 0, 0);
if_statinc(ifp, if_oerrors);
+ m_freem(m);
+ break;
+ }
+ KASSERTMSG(length <=3D un->un_tx_bufsz,
+ "length=3D%u bufsz=3D%u", length, un->un_tx_bufsz);
+
+ /* XXX KASSERT */
+ if (__predict_false(c->unc_xfer =3D=3D NULL)) {
+ DPRINTF("unc_xfer is NULL", 0, 0, 0, 0);
+ if_statinc(ifp, if_oerrors);
+ m_freem(m);
break;
}
=20
- if (__predict_false(c->unc_xfer =3D=3D NULL)) {
- DPRINTF("unc_xfer is NULL", 0, 0, 0, 0);
- if_statinc(ifp, if_oerrors);
- break;
+ /*
+ * If the driver supports it, cram a batch of as many
+ * packets we can fit into a single transfer at once.
+ */
+ if (un->un_ops->uno_tx_prepare1) {
+ unsigned offset =3D length;
+
+ while (nbatch < __arraycount(batch)) {
+ batch[nbatch++] =3D m;
+ IFQ_POLL(&ifp->if_snd, m);
+ if (m =3D=3D NULL) {
+ m =3D batch[--nbatch];
+ break;
+ }
+ KASSERT(m->m_pkthdr.len <=3D un->un_tx_bufsz);
+ length =3D uno_tx_prepare1(un, m, c, offset);
+ KASSERTMSG(length <=3D un->un_tx_bufsz - offset,
+ "length=3D%u bufsz=3D%u offset=3D%u",
+ length, un->un_tx_bufsz, offset);
+ if (length =3D=3D 0) {
+ /*
+ * Wouldn't fit in the buffer;
+ * leave the last packet queued
+ * in ifp->if_snd.
+ */
+ m =3D batch[--nbatch];
+ break;
+ }
+ offset +=3D length;
+ IFQ_DEQUEUE(&ifp->if_snd, m);
+ }
}
=20
usbd_setup_xfer(c->unc_xfer, c, c->unc_buf, length,
@@ -551,19 +605,22 @@ usbnet_start_locked(struct ifnet *ifp)
if (err !=3D USBD_IN_PROGRESS) {
DPRINTF("usbd_transfer on %#jx for %ju bytes: %jd",
(uintptr_t)c->unc_buf, length, err, 0);
- if_statinc(ifp, if_oerrors);
+ if_statadd(ifp, if_oerrors, nbatch + 1);
+ m_freem(m);
+ while (nbatch --> 0)
+ m_freem(batch[nbatch]);
break;
}
done_transmit =3D true;
=20
- IFQ_DEQUEUE(&ifp->if_snd, m);
-
/*
* If there's a BPF listener, bounce a copy of this frame
* to him.
*/
bpf_mtap(ifp, m, BPF_D_OUT);
m_freem(m);
+ while (nbatch --> 0)
+ m_freem(batch[nbatch]);
=20
idx =3D (idx + 1) % un->un_tx_list_cnt;
cd->uncd_tx_cnt++;
diff -r 2abb22668d71 -r 0c1b80d41c95 sys/dev/usb/usbnet.h
--- a/sys/dev/usb/usbnet.h Thu Jan 22 03:22:43 2026 +0000
+++ b/sys/dev/usb/usbnet.h Sat Jan 24 21:34:44 2026 +0000
@@ -141,6 +141,9 @@ typedef void (*usbnet_mii_statchg_cb)(st
/* Prepare packet to send callback, returns length. */
typedef unsigned (*usbnet_tx_prepare_cb)(struct usbnet *, struct mbuf *,
struct usbnet_chain *);
+/* Prepare subsequent packet to send callback, returns length. */
+typedef unsigned (*usbnet_tx_prepare1_cb)(struct usbnet *, struct mbuf *,
+ struct usbnet_chain *, unsigned);
/* Receive some packets callback. */
typedef void (*usbnet_rx_loop_cb)(struct usbnet *, struct usbnet_chain *,
uint32_t);
@@ -181,6 +184,7 @@ struct usbnet_ops {
usbnet_rx_loop_cb uno_rx_loop; /* R */
usbnet_tick_cb uno_tick; /* n */
usbnet_intr_cb uno_intr; /* n */
+ usbnet_tx_prepare1_cb uno_tx_prepare1; /* T */
};
=20
/*
# HG changeset patch
# User Taylor R Campbell <riastradh@NetBSD.org>
# Date 1769290712 0
# Sat Jan 24 21:38:32 2026 +0000
# Branch trunk
# Node ID 0aa5dacc886a083d3e52de8738c4fdf5d258cbba
# Parent 0c1b80d41c95e437ff64c85b0e72466bebf8dd60
# EXP-Topic riastradh-pr59939-usbnetmultipkttxxfer
WIP: ure(4): Support multi-packet tx transfers.
PR kern/59939: usbnet(9): support multi-packet tx transfers
diff -r 0c1b80d41c95 -r 0aa5dacc886a sys/dev/usb/if_ure.c
--- a/sys/dev/usb/if_ure.c Sat Jan 24 21:34:44 2026 +0000
+++ b/sys/dev/usb/if_ure.c Sat Jan 24 21:38:32 2026 +0000
@@ -91,6 +91,8 @@ static void ure_uno_mcast(struct ifnet *
static int ure_uno_mii_read_reg(struct usbnet *, int, int, uint16_t *);
static int ure_uno_mii_write_reg(struct usbnet *, int, int, uint16_t);
static void ure_uno_miibus_statchg(struct ifnet *);
+static unsigned ure_uno_tx_prepare1(struct usbnet *, struct mbuf *,
+ struct usbnet_chain *, unsigned);
static unsigned ure_uno_tx_prepare(struct usbnet *, struct mbuf *,
struct usbnet_chain *);
static void ure_uno_rx_loop(struct usbnet *, struct usbnet_chain *,
@@ -110,6 +112,7 @@ static const struct usbnet_ops ure_ops =3D
.uno_write_reg =3D ure_uno_mii_write_reg,
.uno_statchg =3D ure_uno_miibus_statchg,
.uno_tx_prepare =3D ure_uno_tx_prepare,
+ .uno_tx_prepare1 =3D ure_uno_tx_prepare1,
.uno_rx_loop =3D ure_uno_rx_loop,
.uno_init =3D ure_uno_init,
};
@@ -1024,14 +1027,18 @@ ure_rxcsum(struct ifnet *ifp, struct ure
}
=20
static unsigned
-ure_uno_tx_prepare(struct usbnet *un, struct mbuf *m, struct usbnet_chain =
*c)
+ure_uno_tx_prepare1(struct usbnet *un, struct mbuf *m, struct usbnet_chain=
*c,
+ unsigned offset)
{
struct ure_txpkt txhdr;
uint32_t frm_len =3D 0;
uint8_t *buf =3D c->unc_buf;
=20
- if ((unsigned)m->m_pkthdr.len > un->un_tx_bufsz - sizeof(txhdr))
+ if (sizeof(txhdr) > un->un_tx_bufsz - offset ||
+ (unsigned)m->m_pkthdr.len > (un->un_tx_bufsz -
+ - offset - sizeof(txhdr)))
return 0;
+ buf +=3D offset;
=20
/* header */
txhdr.ure_pktlen =3D htole32(m->m_pkthdr.len | URE_TXPKT_TX_FS |
@@ -1050,6 +1057,13 @@ ure_uno_tx_prepare(struct usbnet *un, st
return frm_len;
}
=20
+static unsigned
+ure_uno_tx_prepare(struct usbnet *un, struct mbuf *m, struct usbnet_chain =
*c)
+{
+
+ return ure_uno_tx_prepare1(un, m, c, /*offset*/0);
+}
+
/*
* We need to calculate L4 checksum in software, if the offset of
* L4 header is larger than 0x7ff =3D 2047.
--=_rFLsIlDMAmZS54PhaM3eE2ii5a8qA3Zz--
From: Martin Husemann <martin@duskware.de>
To: gnats-bugs@netbsd.org
Cc:
Subject: Re: kern/59939: usbnet(9): support multi-packet tx transfers
Date: Sun, 25 Jan 2026 10:34:21 +0100
If this works out and helps performance, we need to do the same to usbwifi
on the wifi-renewal topic.
Martin
From: Nick Hudson <nick.hudson@gmx.co.uk>
To: gnats-bugs@netbsd.org, kern-bug-people@netbsd.org,
gnats-admin@netbsd.org, netbsd-bugs@netbsd.org, campbell+netbsd@mumble.net
Cc:
Subject: Re: kern/59939: usbnet(9): support multi-packet tx transfers
Date: Sun, 25 Jan 2026 12:00:48 +0000
On 25/01/2026 09:35, Martin Husemann via gnats wrote:
[...]
>=20
> If this works out and helps performance, we need to do the same to usb=
wifi
> on the wifi-renewal topic.
Until someone(tm) fixes the HC drivers (other than dwc2) to allow >1=20
transfer per endpoint then this is almost certainly a win.
From: Taylor R Campbell <riastradh@NetBSD.org>
To: netbsd-bugs@NetBSD.org, gnats-bugs@NetBSD.org
Cc: Jared McNeill <jmcneill@invisible.ca>,
Martin Husemann <martin@duskware.de>,
Nick Hudson <nick.hudson@gmx.co.uk>,
matthew green <mrg@eterna23.net>
Subject: Re: kern/59939: usbnet(9): support multi-packet tx transfers
Date: Mon, 26 Jan 2026 04:39:05 +0000
This is a multi-part message in MIME format.
--=_su1OON3HGWIM8fZuEZsawKeY4iHDSE1Z
Reworked and adapted to ncm(4) which needs a little more structure
than ure(4) -- while ure(4) just concatenates (header, payload)
sequences in a transfer, ncm(4) uses a transfer header and a table of
packet headers. (Would like to see a third example of a device that
can handle multiple packets per xfer -- suggestions with documentation
references welcome!)
So I split it into
uno_tx_begin
uno_tx_add(m0)
uno_tx_add(m1)
uno_tx_add(m2)
uno_tx_commit(m0)
with m0->m_next = m1, m1->m_next = m2, m2->m_next = NULL in
uno_tx_commit.
This way, the driver has a chance to vet each packet first (so we can
stop and leave a packet in the queue if it would be too long), and
then can format the chosen batch into a transfer all at once.
Drivers can keep state for the tx batch but don't need to;
uno_tx_begin and uno_tx_add say how much overhead is needed and
usbnet(9) will ensure there's space for the overhead so far plus
mi->m_pkthdr.len before even trying uno_tx_add. So most drivers will
just need to return the number of bytes of per-transfer overhead in
uno_tx_begin and per-packet overhead in uno_tx_add, and usbnet(9) will
take care of the rest of bounds checking.
Still no ABI compat to allow us to pull this up later. Thinking of
something like:
#define USBNET_API_VERSION 1
#define usbnet_attach(...) usbnet_attach_real(USBNET_API_VERSION, ...)
but hoping to find a way to avoid copying & pasting the content of
struct usbnet_ops across struct usbnet_ops_v1, struct usbnet_ops_v2,
&c., without making the internal API or the module API a mess.
Also rebased on top of a couple other patches pending for:
kern/59940: usbnet(9): uno_tx_prepare buffer overrun audit
kern/59943: usbnet(9) keeps failed packet in queue to retry indefinitely
--=_su1OON3HGWIM8fZuEZsawKeY4iHDSE1Z
Content-Type: text/plain; charset="ISO-8859-1"; name="pr59939-multipkttxxfer-v2"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="pr59939-multipkttxxfer-v2.patch"
# HG changeset patch
# User Taylor R Campbell <riastradh@NetBSD.org>
# Date 1769290484 0
# Sat Jan 24 21:34:44 2026 +0000
# Branch trunk
# Node ID 187d0d4c9c2915c349bd7475c40fb49b152c0492
# Parent f2a961ab939686586e96a4a42110282d83dcad30
# EXP-Topic riastradh-pr59939-usbnetmultipkttxxfer
WIP: usbnet(9): Support multi-packet tx transfers.
New usbnet_ops:
overhead =3D uno_tx_begin(un)
Returns the number of bytes of overhead in a USB transfer,
independent of the packets being transmitted, or -1 if tx is blocked
for any reason.
If successful (i.e., if returns >=3D0), always followed by a sequence
of calls to uno_tx_add and then either uno_tx_abort or
uno_tx_commit.
overhead =3D uno_tx_add(un, m, pktno, offset)
Returns the number of bytes of overhead in a USB transfer to
transmit the packet m _beyond_ m->m_pkthdr.len, or -1 if tx is
blocked for any reason.
pktno is the zero-based sequence number of m in the transfer, and
offset is the number of bytes reserved so far by uno_tx_begin and
uno_tx_add and all the packet lengths. In other words, if packets
m0, m1, ..., mk have already passed through uno_tx_add, then
pktno =3D k + 1
offset =3D uno_tx_begin(un)
+ uno_tx_add(un, m0, ...) + m0->m_pkthdr.len
+ uno_tx_add(un, m1, ...) + m1->m_pkthdr.len
...
+ uno_tx_add(un, mk, ...) + mk->m_pkthdr.len
Caller has guaranteed the transfer buffer already has space for
offset + m->m_pkthdr.len bytes.
uno_tx_abort(un)
Called if the caller has decided not to transmit the packets it
previously passed to uno_tx_begin/add.
Optional; may be omitted if it has nothing to do.
uno_tx_commit(un, m0, c, npkt, nbyte)
Fill usbnet transfer chain c's buffer with any device-specific
headers and payloads of the linked list of npkt packets headed by m0
linked through m_nexptkt previously passed in order to uno_tx_add,
with a total of nbyte bytes reserved in the transfer, guaranteed to
be at most un->un_tx_bufsz.
XXX kernel revbump -- breaks usbnet(9) module ABI by expanding struct
usbnet_ops with no version or feature flag on which to gate access
kern/59939: usbnet(9): support multi-packet tx transfers
diff -r f2a961ab9396 -r 187d0d4c9c29 sys/dev/usb/usbnet.c
--- a/sys/dev/usb/usbnet.c Mon Jan 26 01:55:52 2026 +0000
+++ b/sys/dev/usb/usbnet.c Sat Jan 24 21:34:44 2026 +0000
@@ -229,6 +229,45 @@ uno_tx_prepare(struct usbnet *un, struct
return (*un->un_ops->uno_tx_prepare)(un, m, c);
}
=20
+static int
+uno_tx_begin(struct usbnet *un)
+{
+ usbnet_isowned_tx(un);
+ return (*un->un_ops->uno_tx_begin)(un);
+}
+
+static int
+uno_tx_add(struct usbnet *un, struct mbuf *m, unsigned pktno, unsigned off=
set)
+{
+ usbnet_isowned_tx(un);
+ return (*un->un_ops->uno_tx_add)(un, m, pktno, offset);
+}
+
+static void
+uno_tx_abort(struct usbnet *un)
+{
+ usbnet_isowned_tx(un);
+ if (un->un_ops->uno_tx_abort)
+ return (*un->un_ops->uno_tx_abort)(un);
+}
+
+static void
+uno_tx_commit(struct usbnet *un, struct mbuf *m, struct usbnet_chain *c,
+ unsigned npkt, unsigned nbyte)
+{
+ usbnet_isowned_tx(un);
+ return (*un->un_ops->uno_tx_commit)(un, m, c, npkt, nbyte);
+}
+
+static bool
+usbnet_batch_tx(struct usbnet *un)
+{
+
+ return un->un_ops->uno_tx_begin !=3D NULL &&
+ un->un_ops->uno_tx_add !=3D NULL &&
+ un->un_ops->uno_tx_commit !=3D NULL;
+}
+
static void
uno_rx_loop(struct usbnet *un, struct usbnet_chain *c, uint32_t total_len)
{
@@ -521,11 +560,16 @@ usbnet_start_locked(struct ifnet *ifp)
idx =3D cd->uncd_tx_prod;
count =3D 0;
while (cd->uncd_tx_cnt < un->un_tx_list_cnt) {
+ struct mbuf *batch =3D NULL, **batch_tail =3D &batch;
+ unsigned npkt =3D 0;
+
IFQ_DEQUEUE(&ifp->if_snd, m);
if (m =3D=3D NULL) {
DPRINTF("start called, queue empty", 0, 0, 0, 0);
break;
}
+ npkt++;
+
if (m->m_pkthdr.len > un->un_tx_bufsz) {
DPRINTF("oversize packet, %ju > %ju",
m->m_pkthdr.len, un->un_tx_bufsz, 0, 0);
@@ -535,9 +579,67 @@ usbnet_start_locked(struct ifnet *ifp)
}
KASSERT(m->m_pkthdr.len <=3D un->un_tx_bufsz);
=20
- struct usbnet_chain *c =3D &cd->uncd_tx_chain[idx];
+ struct usbnet_chain *const c =3D &cd->uncd_tx_chain[idx];
KASSERT(c->unc_xfer !=3D NULL);
=20
+ if (usbnet_batch_tx(un)) {
+ unsigned offset =3D 0;
+ int overhead;
+
+ overhead =3D uno_tx_begin(un);
+ KASSERT(overhead =3D=3D -1 ||
+ (unsigned)overhead <=3D un->un_tx_bufsz);
+ if (overhead =3D=3D -1 ||
+ (unsigned)overhead > un->un_tx_bufsz) {
+ DPRINTF("uno_tx_begin failed", 0, 0, 0, 0);
+ if_statinc(ifp, if_oerrors);
+ m_freem(m);
+ continue;
+ }
+ offset =3D (unsigned)overhead;
+
+ overhead =3D uno_tx_add(un, m, 0, offset);
+ if (overhead =3D=3D -1 ||
+ (unsigned)overhead > un->un_tx_bufsz - offset ||
+ ((unsigned)m->m_pkthdr.len >
+ (un->un_tx_bufsz - offset -
+ (unsigned)overhead))) {
+ DPRINTF("uno_tx_add failed", 0, 0, 0, 0);
+ uno_tx_abort(un);
+ if_statinc(ifp, if_oerrors);
+ m_freem(m);
+ continue;
+ }
+ for (;;) {
+ m->m_nextpkt =3D NULL;
+ *batch_tail =3D m;
+ batch_tail =3D &m->m_nextpkt;
+ IFQ_POLL(&ifp->if_snd, m);
+ if (m =3D=3D NULL)
+ break;
+ KASSERT(m->m_pkthdr.len <=3D un->un_tx_bufsz);
+ if ((unsigned)m->m_pkthdr.len >
+ un->un_tx_bufsz - offset) {
+ m =3D NULL; /* leave in ifp->if_snd q */
+ break;
+ }
+ overhead =3D uno_tx_add(un, m, npkt, offset);
+ if (overhead =3D=3D -1 ||
+ (unsigned)overhead > (un->un_tx_bufsz -
+ offset - (unsigned)m->m_pkthdr.len)) {
+ m =3D NULL; /* leave in ifp->if_snd q */
+ break;
+ }
+ IFQ_DEQUEUE(&ifp->if_snd, m);
+ npkt++;
+ offset +=3D (unsigned)overhead;
+ offset +=3D (unsigned)m->m_pkthdr.len;
+ }
+ uno_tx_commit(un, batch, c, npkt, offset);
+ length =3D offset;
+ goto xfer;
+ }
+
length =3D uno_tx_prepare(un, m, c);
if (length =3D=3D 0) {
DPRINTF("uno_tx_prepare gave zero length", 0, 0, 0, 0);
@@ -546,6 +648,8 @@ usbnet_start_locked(struct ifnet *ifp)
continue;
}
=20
+xfer: KASSERTMSG(length <=3D un->un_tx_bufsz,
+ "length=3D%u bufsz=3D%u", length, un->un_tx_bufsz);
usbd_setup_xfer(c->unc_xfer, c, c->unc_buf, length,
un->un_tx_xfer_flags, 10000, usbnet_txeof);
=20
@@ -554,18 +658,35 @@ usbnet_start_locked(struct ifnet *ifp)
if (err !=3D USBD_IN_PROGRESS) {
DPRINTF("usbd_transfer on %#jx for %ju bytes: %jd",
(uintptr_t)c->unc_buf, length, err, 0);
- if_statinc(ifp, if_oerrors);
- m_freem(m);
+ if_statadd(ifp, if_oerrors, npkt);
+ if (batch) {
+ while (batch !=3D NULL) {
+ m =3D batch;
+ batch =3D m->m_nextpkt;
+ m_freem(m);
+ }
+ } else {
+ m_freem(m);
+ }
continue;
}
done_transmit =3D true;
=20
/*
- * If there's a BPF listener, bounce a copy of this frame
- * to him.
+ * If there's a BPF listener, bounce a copy of these
+ * frames to them, and free them all.
*/
- bpf_mtap(ifp, m, BPF_D_OUT);
- m_freem(m);
+ if (batch) {
+ while (batch !=3D NULL) {
+ m =3D batch;
+ batch =3D m->m_nextpkt;
+ bpf_mtap(ifp, m, BPF_D_OUT);
+ m_freem(m);
+ }
+ } else {
+ bpf_mtap(ifp, m, BPF_D_OUT);
+ m_freem(m);
+ }
=20
idx =3D (idx + 1) % un->un_tx_list_cnt;
cd->uncd_tx_cnt++;
diff -r f2a961ab9396 -r 187d0d4c9c29 sys/dev/usb/usbnet.h
--- a/sys/dev/usb/usbnet.h Mon Jan 26 01:55:52 2026 +0000
+++ b/sys/dev/usb/usbnet.h Sat Jan 24 21:34:44 2026 +0000
@@ -141,6 +141,12 @@ typedef void (*usbnet_mii_statchg_cb)(st
/* Prepare packet to send callback, returns length. */
typedef unsigned (*usbnet_tx_prepare_cb)(struct usbnet *, struct mbuf *,
struct usbnet_chain *);
+typedef int (*usbnet_tx_begin_cb)(struct usbnet *);
+typedef int (*usbnet_tx_add_cb)(struct usbnet *, struct mbuf *,
+ unsigned, unsigned);
+typedef void (*usbnet_tx_abort_cb)(struct usbnet *);
+typedef void (*usbnet_tx_commit_cb)(struct usbnet *, struct mbuf *,
+ struct usbnet_chain *, unsigned, unsigned);
/* Receive some packets callback. */
typedef void (*usbnet_rx_loop_cb)(struct usbnet *, struct usbnet_chain *,
uint32_t);
@@ -181,6 +187,10 @@ struct usbnet_ops {
usbnet_rx_loop_cb uno_rx_loop; /* R */
usbnet_tick_cb uno_tick; /* n */
usbnet_intr_cb uno_intr; /* n */
+ usbnet_tx_begin_cb uno_tx_begin; /* T */
+ usbnet_tx_add_cb uno_tx_add; /* T */
+ usbnet_tx_abort_cb uno_tx_abort; /* T */
+ usbnet_tx_commit_cb uno_tx_commit; /* T */
};
=20
/*
# HG changeset patch
# User Taylor R Campbell <riastradh@NetBSD.org>
# Date 1769290712 0
# Sat Jan 24 21:38:32 2026 +0000
# Branch trunk
# Node ID 747aaf9e3a41dde20b9e50749762a8ab5329e16e
# Parent 187d0d4c9c2915c349bd7475c40fb49b152c0492
# EXP-Topic riastradh-pr59939-usbnetmultipkttxxfer
WIP: ure(4): Support multi-packet tx transfers.
PR kern/59939: usbnet(9): support multi-packet tx transfers
diff -r 187d0d4c9c29 -r 747aaf9e3a41 sys/dev/usb/if_ure.c
--- a/sys/dev/usb/if_ure.c Sat Jan 24 21:34:44 2026 +0000
+++ b/sys/dev/usb/if_ure.c Sat Jan 24 21:38:32 2026 +0000
@@ -93,6 +93,11 @@ static int ure_uno_mii_write_reg(struct=20
static void ure_uno_miibus_statchg(struct ifnet *);
static unsigned ure_uno_tx_prepare(struct usbnet *, struct mbuf *,
struct usbnet_chain *);
+static int ure_uno_tx_begin(struct usbnet *);
+static int ure_uno_tx_add(struct usbnet *, struct mbuf *,
+ unsigned, unsigned);
+static void ure_uno_tx_commit(struct usbnet *, struct mbuf *,
+ struct usbnet_chain *, unsigned, unsigned);
static void ure_uno_rx_loop(struct usbnet *, struct usbnet_chain *,
uint32_t);
static int ure_uno_init(struct ifnet *);
@@ -110,6 +115,9 @@ static const struct usbnet_ops ure_ops =3D
.uno_write_reg =3D ure_uno_mii_write_reg,
.uno_statchg =3D ure_uno_miibus_statchg,
.uno_tx_prepare =3D ure_uno_tx_prepare,
+ .uno_tx_begin =3D ure_uno_tx_begin,
+ .uno_tx_add =3D ure_uno_tx_add,
+ .uno_tx_commit =3D ure_uno_tx_commit,
.uno_rx_loop =3D ure_uno_rx_loop,
.uno_init =3D ure_uno_init,
};
@@ -1024,14 +1032,18 @@ ure_rxcsum(struct ifnet *ifp, struct ure
}
=20
static unsigned
-ure_uno_tx_prepare(struct usbnet *un, struct mbuf *m, struct usbnet_chain =
*c)
+ure_uno_tx_prepare_offset(struct usbnet *un, struct mbuf *m,
+ struct usbnet_chain *c, unsigned offset)
{
struct ure_txpkt txhdr;
uint32_t frm_len =3D 0;
- uint8_t *buf =3D c->unc_buf;
+ uint8_t *buf =3D c->unc_buf + offset;
=20
- if ((unsigned)m->m_pkthdr.len > un->un_tx_bufsz - sizeof(txhdr))
- return 0;
+ __CTASSERT(sizeof(txhdr) <=3D URE_BUFSZ);
+ KASSERT(sizeof(txhdr) <=3D un->un_tx_bufsz);
+ KASSERT(offset <=3D un->un_tx_bufsz - sizeof(txhdr));
+ KASSERT((unsigned)m->m_pkthdr.len <=3D
+ un->un_tx_bufsz - sizeof(txhdr) - offset);
=20
/* header */
txhdr.ure_pktlen =3D htole32(m->m_pkthdr.len | URE_TXPKT_TX_FS |
@@ -1050,6 +1062,51 @@ ure_uno_tx_prepare(struct usbnet *un, st
return frm_len;
}
=20
+static unsigned
+ure_uno_tx_prepare(struct usbnet *un, struct mbuf *m, struct usbnet_chain =
*c)
+{
+
+ __CTASSERT(sizeof(struct ure_txpkt) <=3D URE_BUFSZ);
+ KASSERT(sizeof(struct ure_txpkt) <=3D un->un_tx_bufsz);
+ if ((unsigned)m->m_pkthdr.len >
+ un->un_tx_bufsz - sizeof(struct ure_txpkt))
+ return 0;
+
+ return ure_uno_tx_prepare_offset(un, m, c, 0);
+}
+
+static int
+ure_uno_tx_begin(struct usbnet *un)
+{
+
+ /* zero per-packet overhead */
+ return 0;
+}
+
+static int
+ure_uno_tx_add(struct usbnet *un, struct mbuf *m,
+ unsigned pktno, unsigned offset)
+{
+
+ return sizeof(struct ure_txpkt);
+}
+
+static void
+ure_uno_tx_commit(struct usbnet *un, struct mbuf *m0, struct usbnet_chain =
*c,
+ unsigned npkt, unsigned nbyte)
+{
+ struct mbuf *m;
+ unsigned offset =3D 0;
+
+ for (m =3D m0; m !=3D NULL; m =3D m->m_nextpkt) {
+ KASSERT(offset <=3D nbyte -
+ (sizeof(struct ure_txpkt) + (unsigned)m->m_pkthdr.len));
+ offset +=3D ure_uno_tx_prepare_offset(un, m, c, offset);
+ KASSERT(offset <=3D nbyte);
+ }
+ KASSERT(offset =3D=3D nbyte);
+}
+
/*
* We need to calculate L4 checksum in software, if the offset of
* L4 header is larger than 0x7ff =3D 2047.
# HG changeset patch
# User Taylor R Campbell <riastradh@NetBSD.org>
# Date 1769297008 0
# Sat Jan 24 23:23:28 2026 +0000
# Branch trunk
# Node ID d390134355e39af36750bd2a3046f28380706b53
# Parent 747aaf9e3a41dde20b9e50749762a8ab5329e16e
# EXP-Topic riastradh-pr59939-usbnetmultipkttxxfer
WIP: ncm(4): Support multi-packet tx transfers.
PR kern/59939: usbnet(9): support multi-packet tx transfers
diff -r 747aaf9e3a41 -r d390134355e3 sys/dev/usb/if_ncm.c
--- a/sys/dev/usb/if_ncm.c Sat Jan 24 21:38:32 2026 +0000
+++ b/sys/dev/usb/if_ncm.c Sat Jan 24 23:23:28 2026 +0000
@@ -67,9 +67,17 @@ static void ncm_uno_rx_loop(struct usbne
uint32_t);
static unsigned ncm_uno_tx_prepare(struct usbnet *, struct mbuf *,
struct usbnet_chain *);
+static int ncm_uno_tx_begin(struct usbnet *);
+static int ncm_uno_tx_add(struct usbnet *, struct mbuf *,
+ unsigned, unsigned);
+static void ncm_uno_tx_commit(struct usbnet *, struct mbuf *,
+ struct usbnet_chain *, unsigned, unsigned);
=20
static const struct usbnet_ops ncm_ops =3D {
.uno_tx_prepare =3D ncm_uno_tx_prepare,
+ .uno_tx_begin =3D ncm_uno_tx_begin,
+ .uno_tx_add =3D ncm_uno_tx_add,
+ .uno_tx_commit =3D ncm_uno_tx_commit,
.uno_rx_loop =3D ncm_uno_rx_loop,
};
=20
@@ -328,7 +336,7 @@ ncm_uno_tx_prepare(struct usbnet *un, st
struct ncm_header16 *hdr;
unsigned hdr_len, len;
=20
- hdr_len =3D sizeof(*hdr) + sizeof(*ptr);
+ hdr_len =3D sizeof(*hdr) + offsetof(struct ncm_dptab16, datagram[2]);
len =3D m->m_pkthdr.len;
KASSERT(hdr_len <=3D un->un_tx_bufsz);
if (len > un->un_tx_bufsz - hdr_len)
@@ -348,7 +356,7 @@ ncm_uno_tx_prepare(struct usbnet *un, st
USETW(hdr->wNdpIndex, sizeof(*hdr));
=20
USETDW(ptr->dwSignature, NCM_PTR16_SIG_NO_CRC);
- USETW(ptr->wLength, sizeof(*ptr));
+ USETW(ptr->wLength, offsetof(struct ncm_dptab16, datagram[2]));
USETW(ptr->wNextNdpIndex, 0);
=20
USETW(ptr->datagram[0].wDatagramIndex, hdr_len);
@@ -362,6 +370,72 @@ ncm_uno_tx_prepare(struct usbnet *un, st
return len + hdr_len;
}
=20
+static int
+ncm_uno_tx_begin(struct usbnet *un)
+{
+
+ return sizeof(struct ncm_header16) +
+ sizeof(struct ncm_dptab16) +
+ sizeof(struct ncm_pointer16);
+}
+
+static int
+ncm_uno_tx_add(struct usbnet *un, struct mbuf *m,
+ unsigned pktno, unsigned offset)
+{
+
+ return sizeof(struct ncm_pointer16);
+}
+
+static void
+ncm_uno_tx_commit(struct usbnet *un, struct mbuf *m0, struct usbnet_chain =
*c,
+ unsigned npkt, unsigned nbyte)
+{
+ struct ncm_softc *sc =3D un->un_sc;
+ struct ncm_header16 *hdr;
+ struct ncm_dptab16 *ptr;
+ unsigned i, off, len;
+ struct mbuf *m;
+
+ /*
+ * Where in the buffer does the payload start? We have to pass
+ * the header, and then the pointer table, which contains as
+ * many entries as there are packets plus one more all-zero
+ * pointer to terminate the chain.
+ */
+ off =3D sizeof(*hdr) + sizeof(*ptr) + (npkt + 1)*sizeof(ptr->datagram);
+
+ hdr =3D (void *)c->unc_buf;
+ memset(hdr, 0, sizeof(*hdr));
+ USETDW(hdr->dwSignature, NCM_HDR16_SIG);
+ USETW(hdr->wHeaderLength, sizeof(*hdr));
+ USETW(hdr->wSequence, sc->sc_tx_seq);
+ sc->sc_tx_seq++;
+ USETW(hdr->wBlockLength, nbyte);
+ USETW(hdr->wNdpIndex, sizeof(*hdr));
+
+ ptr =3D (void *)(hdr + 1);
+ memset(ptr, 0, sizeof(*ptr));
+ USETDW(ptr->dwSignature, NCM_PTR16_SIG_NO_CRC);
+ USETW(ptr->wLength, off);
+ USETW(ptr->wNextNdpIndex, 0);
+
+ for (i =3D 0, m =3D m0; m !=3D NULL; i++, m =3D m->m_nextpkt) {
+ KASSERT(off < un->un_tx_bufsz);
+ len =3D (unsigned)m->m_pkthdr.len;
+ KASSERT(len <=3D un->un_tx_bufsz - off);
+ memset(&ptr->datagram[i], 0, sizeof(ptr->datagram[0]));
+ USETW(ptr->datagram[i].wDatagramIndex, off);
+ USETW(ptr->datagram[i].wDatagramLength, len);
+ m_copydata(m, 0, len, &c->unc_buf[off]);
+ off +=3D len;
+ }
+ USETW(ptr->datagram[i].wDatagramIndex, 0);
+ USETW(ptr->datagram[i].wDatagramLength, 0);
+
+ KASSERT(off =3D=3D nbyte);
+}
+
#ifdef _MODULE
#include "ioconf.c"
#endif
diff -r 747aaf9e3a41 -r d390134355e3 sys/dev/usb/if_ncmreg.h
--- a/sys/dev/usb/if_ncmreg.h Sat Jan 24 21:38:32 2026 +0000
+++ b/sys/dev/usb/if_ncmreg.h Sat Jan 24 23:23:28 2026 +0000
@@ -73,7 +73,7 @@ struct ncm_dptab16 {
uDWord dwSignature;
uWord wLength;
uWord wNextNdpIndex;
- struct ncm_pointer16 datagram[2];
+ struct ncm_pointer16 datagram[0];
} UPACKED;
=20
struct ncm_pointer32 {
@@ -89,7 +89,7 @@ struct ncm_dptab32 {
uWord wReserved6;
uWord wNextNdpIndex;
uDWord dwReserved12;
- struct ncm_pointer32 datagram[2];
+ struct ncm_pointer32 datagram[0];
} UPACKED;
=20
#define NCM_GET_NTB_PARAMETERS 0x80
--=_su1OON3HGWIM8fZuEZsawKeY4iHDSE1Z--
(Contact us)
$NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.