NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: kern/59939: usbnet(9): support multi-packet tx transfers
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
# HG changeset patch
# User Taylor R Campbell <riastradh%NetBSD.org@localhost>
# 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 = 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 >=0), always followed by a sequence
of calls to uno_tx_add and then either uno_tx_abort or
uno_tx_commit.
overhead = 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 = k + 1
offset = 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);
}
+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 offset)
+{
+ 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 != NULL &&
+ un->un_ops->uno_tx_add != NULL &&
+ un->un_ops->uno_tx_commit != 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 = cd->uncd_tx_prod;
count = 0;
while (cd->uncd_tx_cnt < un->un_tx_list_cnt) {
+ struct mbuf *batch = NULL, **batch_tail = &batch;
+ unsigned npkt = 0;
+
IFQ_DEQUEUE(&ifp->if_snd, m);
if (m == 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 <= un->un_tx_bufsz);
- struct usbnet_chain *c = &cd->uncd_tx_chain[idx];
+ struct usbnet_chain *const c = &cd->uncd_tx_chain[idx];
KASSERT(c->unc_xfer != NULL);
+ if (usbnet_batch_tx(un)) {
+ unsigned offset = 0;
+ int overhead;
+
+ overhead = uno_tx_begin(un);
+ KASSERT(overhead == -1 ||
+ (unsigned)overhead <= un->un_tx_bufsz);
+ if (overhead == -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 = (unsigned)overhead;
+
+ overhead = uno_tx_add(un, m, 0, offset);
+ if (overhead == -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 = NULL;
+ *batch_tail = m;
+ batch_tail = &m->m_nextpkt;
+ IFQ_POLL(&ifp->if_snd, m);
+ if (m == NULL)
+ break;
+ KASSERT(m->m_pkthdr.len <= un->un_tx_bufsz);
+ if ((unsigned)m->m_pkthdr.len >
+ un->un_tx_bufsz - offset) {
+ m = NULL; /* leave in ifp->if_snd q */
+ break;
+ }
+ overhead = uno_tx_add(un, m, npkt, offset);
+ if (overhead == -1 ||
+ (unsigned)overhead > (un->un_tx_bufsz -
+ offset - (unsigned)m->m_pkthdr.len)) {
+ m = NULL; /* leave in ifp->if_snd q */
+ break;
+ }
+ IFQ_DEQUEUE(&ifp->if_snd, m);
+ npkt++;
+ offset += (unsigned)overhead;
+ offset += (unsigned)m->m_pkthdr.len;
+ }
+ uno_tx_commit(un, batch, c, npkt, offset);
+ length = offset;
+ goto xfer;
+ }
+
length = uno_tx_prepare(un, m, c);
if (length == 0) {
DPRINTF("uno_tx_prepare gave zero length", 0, 0, 0, 0);
@@ -546,6 +648,8 @@ usbnet_start_locked(struct ifnet *ifp)
continue;
}
+xfer: KASSERTMSG(length <= un->un_tx_bufsz,
+ "length=%u bufsz=%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);
@@ -554,18 +658,35 @@ usbnet_start_locked(struct ifnet *ifp)
if (err != 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 != NULL) {
+ m = batch;
+ batch = m->m_nextpkt;
+ m_freem(m);
+ }
+ } else {
+ m_freem(m);
+ }
continue;
}
done_transmit = true;
/*
- * 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 != NULL) {
+ m = batch;
+ batch = m->m_nextpkt;
+ bpf_mtap(ifp, m, BPF_D_OUT);
+ m_freem(m);
+ }
+ } else {
+ bpf_mtap(ifp, m, BPF_D_OUT);
+ m_freem(m);
+ }
idx = (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 */
};
/*
# HG changeset patch
# User Taylor R Campbell <riastradh%NetBSD.org@localhost>
# 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
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 =
.uno_write_reg = ure_uno_mii_write_reg,
.uno_statchg = ure_uno_miibus_statchg,
.uno_tx_prepare = ure_uno_tx_prepare,
+ .uno_tx_begin = ure_uno_tx_begin,
+ .uno_tx_add = ure_uno_tx_add,
+ .uno_tx_commit = ure_uno_tx_commit,
.uno_rx_loop = ure_uno_rx_loop,
.uno_init = ure_uno_init,
};
@@ -1024,14 +1032,18 @@ ure_rxcsum(struct ifnet *ifp, struct ure
}
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 = 0;
- uint8_t *buf = c->unc_buf;
+ uint8_t *buf = c->unc_buf + offset;
- if ((unsigned)m->m_pkthdr.len > un->un_tx_bufsz - sizeof(txhdr))
- return 0;
+ __CTASSERT(sizeof(txhdr) <= URE_BUFSZ);
+ KASSERT(sizeof(txhdr) <= un->un_tx_bufsz);
+ KASSERT(offset <= un->un_tx_bufsz - sizeof(txhdr));
+ KASSERT((unsigned)m->m_pkthdr.len <=
+ un->un_tx_bufsz - sizeof(txhdr) - offset);
/* header */
txhdr.ure_pktlen = htole32(m->m_pkthdr.len | URE_TXPKT_TX_FS |
@@ -1050,6 +1062,51 @@ ure_uno_tx_prepare(struct usbnet *un, st
return frm_len;
}
+static unsigned
+ure_uno_tx_prepare(struct usbnet *un, struct mbuf *m, struct usbnet_chain *c)
+{
+
+ __CTASSERT(sizeof(struct ure_txpkt) <= URE_BUFSZ);
+ KASSERT(sizeof(struct ure_txpkt) <= 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 = 0;
+
+ for (m = m0; m != NULL; m = m->m_nextpkt) {
+ KASSERT(offset <= nbyte -
+ (sizeof(struct ure_txpkt) + (unsigned)m->m_pkthdr.len));
+ offset += ure_uno_tx_prepare_offset(un, m, c, offset);
+ KASSERT(offset <= nbyte);
+ }
+ KASSERT(offset == nbyte);
+}
+
/*
* We need to calculate L4 checksum in software, if the offset of
* L4 header is larger than 0x7ff = 2047.
# HG changeset patch
# User Taylor R Campbell <riastradh%NetBSD.org@localhost>
# 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);
static const struct usbnet_ops ncm_ops = {
.uno_tx_prepare = ncm_uno_tx_prepare,
+ .uno_tx_begin = ncm_uno_tx_begin,
+ .uno_tx_add = ncm_uno_tx_add,
+ .uno_tx_commit = ncm_uno_tx_commit,
.uno_rx_loop = ncm_uno_rx_loop,
};
@@ -328,7 +336,7 @@ ncm_uno_tx_prepare(struct usbnet *un, st
struct ncm_header16 *hdr;
unsigned hdr_len, len;
- hdr_len = sizeof(*hdr) + sizeof(*ptr);
+ hdr_len = sizeof(*hdr) + offsetof(struct ncm_dptab16, datagram[2]);
len = m->m_pkthdr.len;
KASSERT(hdr_len <= 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));
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);
USETW(ptr->datagram[0].wDatagramIndex, hdr_len);
@@ -362,6 +370,72 @@ ncm_uno_tx_prepare(struct usbnet *un, st
return len + hdr_len;
}
+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 = 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 = sizeof(*hdr) + sizeof(*ptr) + (npkt + 1)*sizeof(ptr->datagram);
+
+ hdr = (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 = (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 = 0, m = m0; m != NULL; i++, m = m->m_nextpkt) {
+ KASSERT(off < un->un_tx_bufsz);
+ len = (unsigned)m->m_pkthdr.len;
+ KASSERT(len <= 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 += len;
+ }
+ USETW(ptr->datagram[i].wDatagramIndex, 0);
+ USETW(ptr->datagram[i].wDatagramLength, 0);
+
+ KASSERT(off == 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;
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;
#define NCM_GET_NTB_PARAMETERS 0x80
Home |
Main Index |
Thread Index |
Old Index