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--

NetBSD Home
NetBSD PR Database Search

(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.