diff -r 823a0086cd5b sys/dev/usb/if_ncm.c --- a/sys/dev/usb/if_ncm.c Sun Jun 28 11:38:58 2026 +0000 +++ b/sys/dev/usb/if_ncm.c Mon Jun 29 02:09:55 2026 +0000 @@ -54,6 +54,7 @@ struct ncm_softc { struct usbnet ncm_un; + struct ncm_ntb_parameters sc_ntb_param; uint32_t sc_tx_seq; }; @@ -67,10 +68,22 @@ 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 = { +static const struct usbnet_ops_v2 ncm_ops = { + .v1 = { .uno_tx_prepare = ncm_uno_tx_prepare, .uno_rx_loop = ncm_uno_rx_loop, + }, + .v2 = { + .uno_tx_begin = ncm_uno_tx_begin, + .uno_tx_add = ncm_uno_tx_add, + .uno_tx_commit = ncm_uno_tx_commit, + }, }; static int @@ -102,8 +115,6 @@ ncm_attach(device_t parent, device_t sel const usb_cdc_ethernet_descriptor_t *ue; char eaddr_str[USB_MAX_ENCODED_STRING_LEN]; usb_device_request_t req; - struct ncm_ntb_parameters np; - aprint_naive("\n"); aprint_normal("\n"); @@ -114,7 +125,7 @@ ncm_attach(device_t parent, device_t sel un->un_dev = self; un->un_udev = dev; un->un_sc = sc; - un->un_ops = &ncm_ops; + un->un_ops = &ncm_ops.v1; un->un_rx_xfer_flags = USBD_SHORT_XFER_OK; un->un_tx_xfer_flags = USBD_FORCE_SHORT_XFER; @@ -226,24 +237,62 @@ ncm_attach(device_t parent, device_t sel req.bRequest = NCM_GET_NTB_PARAMETERS; USETW(req.wValue, 0); USETW(req.wIndex, uiaa->uiaa_ifaceno); - USETW(req.wLength, sizeof(np)); - if (usbd_do_request(un->un_udev, &req, &np) != USBD_NORMAL_COMPLETION || - UGETW(np.wLength) != sizeof(np)) { + USETW(req.wLength, sizeof(sc->sc_ntb_param)); + if (usbd_do_request(un->un_udev, &req, &sc->sc_ntb_param) != + USBD_NORMAL_COMPLETION || + UGETW(sc->sc_ntb_param.wLength) != sizeof(sc->sc_ntb_param)) { aprint_error_dev(un->un_dev, "NCM_GET_NTB_PARAMETERS failed\n"); return; } + aprint_verbose_dev(un->un_dev, "formats: 0x%02x%02x\n", + sc->sc_ntb_param.bmNtbFormatsSupported[1], + sc->sc_ntb_param.bmNtbFormatsSupported[0]); + aprint_verbose_dev(un->un_dev, + "in: max %u, =%u mod %u, align %u\n", + UGETDW(sc->sc_ntb_param.dwNtbInMaxSize), + UGETW(sc->sc_ntb_param.wNdpInPayloadRemainder), + UGETW(sc->sc_ntb_param.wNdpInDivisor), + UGETW(sc->sc_ntb_param.wNdpInAlignment)); + aprint_verbose_dev(un->un_dev, + "out: max %u (%u pkt), =%u mod %u, align %u\n", + UGETDW(sc->sc_ntb_param.dwNtbOutMaxSize), + UGETW(sc->sc_ntb_param.wNtbOutMaxDatagrams), + UGETW(sc->sc_ntb_param.wNdpOutPayloadRemainder), + UGETW(sc->sc_ntb_param.wNdpOutDivisor), + UGETW(sc->sc_ntb_param.wNdpOutAlignment)); + + /* paranoia */ + if (UGETW(sc->sc_ntb_param.wNdpInDivisor) == 0) + USETW(sc->sc_ntb_param.wNdpInDivisor, 1); + if (UGETW(sc->sc_ntb_param.wNdpOutDivisor) == 0) + USETW(sc->sc_ntb_param.wNdpOutDivisor, 1); + if (UGETW(sc->sc_ntb_param.wNdpInPayloadRemainder) >= + UGETW(sc->sc_ntb_param.wNdpInDivisor)) { + USETW(sc->sc_ntb_param.wNdpInPayloadRemainder, + UGETW(sc->sc_ntb_param.wNdpInPayloadRemainder) % + UGETW(sc->sc_ntb_param.wNdpInDivisor)); + } + if (UGETW(sc->sc_ntb_param.wNdpOutPayloadRemainder) >= + UGETW(sc->sc_ntb_param.wNdpOutDivisor)) { + USETW(sc->sc_ntb_param.wNdpOutPayloadRemainder, + UGETW(sc->sc_ntb_param.wNdpOutPayloadRemainder) % + UGETW(sc->sc_ntb_param.wNdpOutDivisor)); + } + un->un_rx_list_cnt = 1; un->un_tx_list_cnt = 1; - un->un_rx_bufsz = UGETDW(np.dwNtbInMaxSize); - un->un_tx_bufsz = UGETDW(np.dwNtbOutMaxSize); - if (un->un_tx_bufsz < NCM_MIN_TX_BUFSZ) { + un->un_rx_bufsz = UGETDW(sc->sc_ntb_param.dwNtbInMaxSize); + un->un_tx_bufsz = UGETDW(sc->sc_ntb_param.dwNtbOutMaxSize); + if (un->un_tx_bufsz < NCM_MIN_TX_BUFSZ || + un->un_tx_bufsz - NCM_MIN_TX_BUFSZ < + (unsigned)UGETW(sc->sc_ntb_param.wNdpOutDivisor)) { aprint_error_dev(un->un_dev, "dwNtbOutMaxSize %u too small\n", un->un_tx_bufsz); return; } - usbnet_attach(un); + usbnet_attach_v2(un, &ncm_ops); usbnet_attach_ifp(un, IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST, 0, NULL); @@ -300,7 +349,7 @@ ncm_uno_rx_loop(struct usbnet *un, struc return; } - const unsigned max_datagrams = (UGETW(ptr->wLength) - + const unsigned max_datagrams = (UGETW(ptr->wLength) - offsetof(struct ncm_dptab16, datagram))/sizeof(ptr->datagram[0]); for (i = 0; i < max_datagrams; i++) { uint16_t data_start = UGETW(ptr->datagram[i].wDatagramIndex); @@ -320,46 +369,236 @@ ncm_uno_rx_loop(struct usbnet *un, struc } } +/* + * The layout of a tx xfer buffer is: + * + * +--------------+-------------+---------------+---------------+-----+ + * | ncm_header16 | ncm_dptab16 | ncm_pointer16 | ncm_pointer16 | ... | + * +--------------+-------------+---------------+---------------+-----+ + * +-----+----------------+-------+-----------+------------+-------------- + * | pad | payload 0 | pad | payload 1 | pad | payload 2 ... + * +-----+----------------+-------+-----------+------------+-------------- + * | | | + * k*d + r (k + 1)*d + r (k + 2)*d + r + * + * The ncm_header16 gives the offset of the datagram pointer table, + * headed by ncm_dptab16. There is one more ncm_pointer16 than packet, + * with zero offset/length to mark the end of the transfer. + * + * The NTB parameters require the packet payloads to start at specific + * offsets modulo a divisor: + * + * offset % wNdpOutDivisor = wNdpOutPayloadRemainder + * + * In other words, each offset is k*d + r for some k, where d is + * wNdpOutDivisor and r is wNdpOutPayloadRemainder. + */ + +/* + * ncm_out_align(sc, off0) + * + * Return the smallest offset off >= off0 such that + * + * off % wNdpOutDivisor = wNdpOutPayloadRemainder. + */ +static unsigned +ncm_out_align(struct ncm_softc *sc, unsigned off0) +{ + unsigned off; + + KASSERTMSG(off0 <= sc->ncm_un.un_tx_bufsz, "off0=%u bufsz=%u", + off0, sc->ncm_un.un_tx_bufsz); + + off = rounddown(off0, UGETW(sc->sc_ntb_param.wNdpOutDivisor)); + off += UGETW(sc->sc_ntb_param.wNdpOutPayloadRemainder); + if (off < off0) + off += UGETW(sc->sc_ntb_param.wNdpOutDivisor); + + KASSERTMSG(((off % UGETW(sc->sc_ntb_param.wNdpOutDivisor)) == + (unsigned)UGETW(sc->sc_ntb_param.wNdpOutPayloadRemainder)), + "off=%u divisor=%u remainder=%u", + off, + UGETW(sc->sc_ntb_param.wNdpOutDivisor), + UGETW(sc->sc_ntb_param.wNdpOutPayloadRemainder)); + return off; +} + static unsigned ncm_uno_tx_prepare(struct usbnet *un, struct mbuf *m, struct usbnet_chain *c) { struct ncm_softc *sc = un->un_sc; struct ncm_dptab16 *ptr; struct ncm_header16 *hdr; - unsigned hdr_len, len; + unsigned hdr_len, off, len; - hdr_len = sizeof(*hdr) + sizeof(*ptr); + hdr_len = sizeof(*hdr) + offsetof(struct ncm_dptab16, datagram[2]); + off = ncm_out_align(sc, hdr_len); len = m->m_pkthdr.len; - KASSERT(hdr_len <= un->un_tx_bufsz); - if (len > un->un_tx_bufsz - hdr_len) + KASSERTMSG(hdr_len <= un->un_tx_bufsz, + "hdr_len=%u bufsz=%u", hdr_len, un->un_tx_bufsz); + if (off > un->un_tx_bufsz || len > un->un_tx_bufsz - off) return 0; hdr = (struct ncm_header16 *)c->unc_buf; ptr = (struct ncm_dptab16 *)(hdr + 1); memset(hdr, 0, sizeof(*hdr)); + memset(ptr, 0, offsetof(struct ncm_dptab16, datagram[2])); + + 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, off + len); + USETW(hdr->wNdpIndex, sizeof(*hdr)); + + USETDW(ptr->dwSignature, NCM_PTR16_SIG_NO_CRC); + USETW(ptr->wLength, offsetof(struct ncm_dptab16, datagram[2])); + USETW(ptr->wNextNdpIndex, 0); + + USETW(ptr->datagram[0].wDatagramIndex, off); + USETW(ptr->datagram[0].wDatagramLength, len); + + USETW(ptr->datagram[1].wDatagramIndex, 0); + USETW(ptr->datagram[1].wDatagramLength, 0); + + m_copydata(m, 0, len, c->unc_buf + off); + + return off + len; +} + +/* + * ncm_uno_tx_begin(un) + * + * Called when starting a batch of packets for tx in a single + * transfer. Returns the number of bytes of overhead in the + * transfer buffer, no matter how many packets are being + * transmitted. + */ +static int +ncm_uno_tx_begin(struct usbnet *un) +{ + struct ncm_softc *sc = un->un_sc; + + /* + * Find how many bytes of overhead we need for: + * - a transfer header + * - a datagram pointer table header + * - padding to align the first payload. + */ + return ncm_out_align(sc, sizeof(struct ncm_header16) + + offsetof(struct ncm_dptab16, datagram[1])); +} + +/* + * ncm_uno_tx_add(un, m, pktno, offset) + * + * Called for each candidate packet in a batch for tx in a single + * transfer. Returns the number of bytes of overhead, on top of + * m->m_pkthdr.len, to store this packet in the transfer buffer, + * given that pktno packets are already included. + * + * offset is the sum of ncm_uno_tx_begin(un) and + * ncm_uno_tx_add(un, m0, ...) for each previous packet m0 already + * included in the batch. + * + * Caller guarantees the transfer buffer already has enough space + * for offset + m->m_pkthdr.len bytes, i.e., that + * + * offset + m->m_pkthdr.len <= un->un_tx_bufsz. + */ +static int +ncm_uno_tx_add(struct usbnet *un, struct mbuf *m, + unsigned pktno, unsigned off) +{ + struct ncm_softc *sc = un->un_sc; + unsigned old_start, new_start; + + KASSERTMSG(off <= un->un_tx_bufsz, "off=%u bufsz=%u", + off, un->un_tx_bufsz); + KASSERTMSG((unsigned)m->m_pkthdr.len <= un->un_tx_bufsz - off, + "off=%u m->m_pkthdr.len=%u bufsz=%u", + off, (unsigned)m->m_pkthdr.len, un->un_tx_bufsz); + + if (UGETW(sc->sc_ntb_param.wNtbOutMaxDatagrams) != 0 && + pktno >= (unsigned)UGETW(sc->sc_ntb_param.wNtbOutMaxDatagrams)) + return -1; + + /* + * Find how many bytes we would need for header and padding to + * align the first payload with the previous number of packets, + * so we can subtract that from the number of bytes for the + * next number of packets. + * + * Note that we always need one extra entry in the datagram + * pointer table with zero index/length to terminate the list. + */ + old_start = ncm_out_align(sc, sizeof(struct ncm_header16) + + offsetof(struct ncm_dptab16, datagram[pktno + 1])); + new_start = ncm_out_align(sc, sizeof(struct ncm_header16) + + offsetof(struct ncm_dptab16, datagram[pktno + 2])); + + return (new_start - old_start) + (ncm_out_align(sc, off) - off); +} + +/* + * ncm_uno_tx_commit(un, m0, c, npkt, nbyte) + * + * Called when a batch of npkt packets, totalling nbyte bytes of + * payload and overhead (as returned by ncm_uno_tx_begin and + * ncm_uno_tx_add), has been gathered for tx in a single transfer. + * Formats the packets m0, m1 := m0->m_nextpkt, m2 := + * m1->m_nextpkt, ..., with appropriate headers into the buffer c. + */ +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; + + KASSERTMSG(nbyte <= un->un_tx_bufsz, "nbyte=%u bufsz=%u", + nbyte, un->un_tx_bufsz); + + hdr = (struct ncm_header16 *)c->unc_buf; + ptr = (struct ncm_dptab16 *)(hdr + 1); + memset(hdr, 0, sizeof(*hdr)); memset(ptr, 0, sizeof(*ptr)); 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, hdr_len + len); + USETW(hdr->wBlockLength, nbyte); 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[npkt + 1])); USETW(ptr->wNextNdpIndex, 0); - USETW(ptr->datagram[0].wDatagramIndex, hdr_len); - USETW(ptr->datagram[0].wDatagramLength, len); + off = (unsigned)((const uint8_t *)&ptr->datagram[npkt + 1] - + c->unc_buf); + for (i = 0, m = m0; i < npkt; i++, m = m->m_nextpkt) { + KASSERTMSG(m != NULL, "i=%u npkt=%u", i, npkt); + KASSERTMSG(off <= nbyte, "off=%u nbyte=%u", off, nbyte); + off = ncm_out_align(sc, off); + KASSERTMSG(off <= nbyte, "off=%u nbyte=%u", off, nbyte); + len = (unsigned)m->m_pkthdr.len; + KASSERTMSG(len <= nbyte - off, "off=%u len=%u nbyte=%u", + off, len, nbyte); + 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; + } + KASSERTMSG(i == npkt, "i=%u npkt=%u", i, npkt); + KASSERTMSG(off == nbyte, "off=%u nbyte=%u", off, nbyte); - USETW(ptr->datagram[1].wDatagramIndex, 0); - USETW(ptr->datagram[1].wDatagramLength, 0); - - m_copydata(m, 0, len, c->unc_buf + hdr_len); - - return len + hdr_len; + USETW(ptr->datagram[npkt].wDatagramIndex, 0); + USETW(ptr->datagram[npkt].wDatagramLength, 0); } #ifdef _MODULE diff -r 823a0086cd5b sys/dev/usb/if_ncmreg.h --- a/sys/dev/usb/if_ncmreg.h Sun Jun 28 11:38:58 2026 +0000 +++ b/sys/dev/usb/if_ncmreg.h Mon Jun 29 02:09:55 2026 +0000 @@ -42,7 +42,7 @@ #define NCM_MIN_TX_BUFSZ \ (ETHERMTU + ETHER_HDR_LEN \ + sizeof(struct ncm_header16) \ - + sizeof(struct ncm_pointer16)) + + offsetof(struct ncm_dptab16, datagram[2])) struct ncm_header16 { #define NCM_HDR16_SIG 0x484d434e @@ -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 diff -r 823a0086cd5b sys/dev/usb/if_ure.c --- a/sys/dev/usb/if_ure.c Sun Jun 28 11:38:58 2026 +0000 +++ b/sys/dev/usb/if_ure.c Mon Jun 29 02:09:55 2026 +0000 @@ -95,6 +95,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 *); @@ -105,7 +110,8 @@ static void ure_attach(device_t, device_ CFATTACH_DECL_NEW(ure, sizeof(struct usbnet), ure_match, ure_attach, usbnet_detach, usbnet_activate); -static const struct usbnet_ops ure_ops = { +static const struct usbnet_ops_v2 ure_ops = { + .v1 = { .uno_stop = ure_uno_stop, .uno_mcast = ure_uno_mcast, .uno_read_reg = ure_uno_mii_read_reg, @@ -114,6 +120,12 @@ static const struct usbnet_ops ure_ops = .uno_tx_prepare = ure_uno_tx_prepare, .uno_rx_loop = ure_uno_rx_loop, .uno_init = ure_uno_init, + }, + .v2 = { + .uno_tx_begin = ure_uno_tx_begin, + .uno_tx_add = ure_uno_tx_add, + .uno_tx_commit = ure_uno_tx_commit, + } }; static int @@ -818,7 +830,7 @@ ure_attach(device_t parent, device_t sel un->un_dev = self; un->un_udev = dev; un->un_sc = un; - un->un_ops = &ure_ops; + un->un_ops = &ure_ops.v1; un->un_rx_xfer_flags = USBD_SHORT_XFER_OK; un->un_tx_xfer_flags = USBD_FORCE_SHORT_XFER; un->un_rx_list_cnt = URE_RX_LIST_CNT; @@ -864,7 +876,7 @@ ure_attach(device_t parent, device_t sel } /* Set these up now for ure_ctl(). */ - usbnet_attach(un); + usbnet_attach_v2(un, &ure_ops); un->un_phyno = 0; @@ -1036,14 +1048,20 @@ 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_TX_BUFSZ); + KASSERTMSG((sizeof(txhdr) <= un->un_tx_bufsz && + offset <= un->un_tx_bufsz - sizeof(txhdr) && + (unsigned)m->m_pkthdr.len <= (un->un_tx_bufsz - sizeof(txhdr) - + offset)), + "offset=%u sizeof(txhdr)=%zu len=%u bufsz=%u", + offset, sizeof(txhdr), m->m_pkthdr.len, un->un_tx_bufsz); /* header */ txhdr.ure_pktlen = htole32(m->m_pkthdr.len | URE_TXPKT_TX_FS | @@ -1062,6 +1080,52 @@ 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_TX_BUFSZ); + KASSERTMSG(sizeof(struct ure_txpkt) <= un->un_tx_bufsz, + "sizeof(struct ure_txpkt)=%zu bufsz=%u", + 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-xfer 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 i, offset = 0; + + for (i = 0, m = m0; i < npkt; i++, m = m->m_nextpkt) { + KASSERT(m != NULL); + offset += ure_uno_tx_prepare_offset(un, m, c, offset); + KASSERT(offset <= nbyte); + } + KASSERTMSG(offset == nbyte, "offset=%u nbyte=%u", offset, nbyte); +} + /* * We need to calculate L4 checksum in software, if the offset of * L4 header is larger than 0x7ff = 2047. diff -r 823a0086cd5b sys/dev/usb/usbnet.c --- a/sys/dev/usb/usbnet.c Sun Jun 28 11:38:58 2026 +0000 +++ b/sys/dev/usb/usbnet.c Mon Jun 29 02:09:55 2026 +0000 @@ -94,6 +94,11 @@ struct usbnet_private { struct timeval unp_rx_notice; struct timeval unp_tx_notice; struct timeval unp_intr_notice; + + enum usbnet_version { + USBNET_V1 = 1, + USBNET_V2, + } unp_version; }; #define un_cdata(un) (&(un)->un_pri->unp_cdata) @@ -229,6 +234,60 @@ uno_tx_prepare(struct usbnet *un, struct return (*un->un_ops->uno_tx_prepare)(un, m, c); } +static const struct usbnet_v2_ops * +usbnet_get_v2_ops(struct usbnet *un) +{ + + KASSERT(un->un_pri->unp_version >= USBNET_V2); + switch (un->un_pri->unp_version) { + case USBNET_V2: + return &const_container_of(un->un_ops, struct usbnet_ops_v2, + v1)->v2; + default: + __unreachable(); + } +} + +static int +uno_tx_begin(struct usbnet *un) +{ + usbnet_isowned_tx(un); + return (*usbnet_get_v2_ops(un)->uno_tx_begin)(un); +} + +static int +uno_tx_add(struct usbnet *un, struct mbuf *m, unsigned pktno, unsigned offset) +{ + usbnet_isowned_tx(un); + return (*usbnet_get_v2_ops(un)->uno_tx_add)(un, m, pktno, offset); +} + +static void +uno_tx_abort(struct usbnet *un) +{ + usbnet_isowned_tx(un); + if (usbnet_get_v2_ops(un)->uno_tx_abort) + return (*usbnet_get_v2_ops(un)->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 (*usbnet_get_v2_ops(un)->uno_tx_commit)(un, m, c, npkt, nbyte); +} + +static bool +usbnet_batch_tx(struct usbnet *un) +{ + + return un->un_pri->unp_version >= USBNET_V2 && + usbnet_get_v2_ops(un)->uno_tx_begin != NULL && + usbnet_get_v2_ops(un)->uno_tx_add != NULL && + usbnet_get_v2_ops(un)->uno_tx_commit != NULL; +} + static void uno_rx_loop(struct usbnet *un, struct usbnet_chain *c, uint32_t total_len) { @@ -521,11 +580,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 ((unsigned)m->m_pkthdr.len > un->un_tx_bufsz) { DPRINTF("oversize packet, %ju > %ju", (unsigned)m->m_pkthdr.len, un->un_tx_bufsz, 0, 0); @@ -540,6 +604,127 @@ usbnet_start_locked(struct ifnet *ifp) 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); + KASSERTMSG((overhead == -1 || + (unsigned)overhead <= un->un_tx_bufsz), + "overhead=%d bufsz=%u", overhead, un->un_tx_bufsz); + if (overhead == -1) { + DPRINTF("uno_tx_begin failed", 0, 0, 0, 0); + if_statinc(ifp, if_oerrors); + m_freem(m); + continue; + } + if ((unsigned)overhead > + (un->un_tx_bufsz - (unsigned)m->m_pkthdr.len)) { + DPRINTF("uno_tx_begin overhead too large", + 0, 0, 0, 0); + uno_tx_abort(un); + if_statinc(ifp, if_oerrors); + m_freem(m); + continue; + } + offset = (unsigned)overhead; + + KASSERTMSG(((unsigned)m->m_pkthdr.len <= + un->un_tx_bufsz - offset), + "offset=%u m->m_pkthdr.len=%d bufsz=%u", + offset, m->m_pkthdr.len, un->un_tx_bufsz); + overhead = uno_tx_add(un, m, 0, offset); + KASSERTMSG((overhead == -1 || + (unsigned)overhead <= un->un_tx_bufsz), + "overhead=%d bufsz=%u", + overhead, un->un_tx_bufsz); + if (overhead == -1 || + (unsigned)overhead > (un->un_tx_bufsz - offset - + (unsigned)m->m_pkthdr.len)) { + DPRINTF("uno_tx_add failed", 0, 0, 0, 0); + uno_tx_abort(un); + if_statinc(ifp, if_oerrors); + m_freem(m); + continue; + } + for (;;) { + /* + * Loop invariant: We have a packet m, + * and we know its overhead and length + * both fit in the remaining buffer + * space. + * + * Queue it up, and then examine the + * next packet. + */ + KASSERT(m != NULL); + KASSERTMSG(((unsigned)overhead <= + un->un_tx_bufsz - offset), + "offset=%u overhead=%d len=%d bufsz=%u", + offset, overhead, m->m_pkthdr.len, + un->un_tx_bufsz); + KASSERTMSG(((unsigned)m->m_pkthdr.len <= + un->un_tx_bufsz - offset - + (unsigned)overhead), + "offset=%u overhead=%d len=%d bufsz=%u", + offset, overhead, m->m_pkthdr.len, + un->un_tx_bufsz); + offset += (unsigned)overhead; + offset += (unsigned)m->m_pkthdr.len; + KASSERTMSG(offset <= un->un_tx_bufsz, + "offset=%u bufsz=%u", + offset, un->un_tx_bufsz); + m->m_nextpkt = NULL; + *batch_tail = m; + batch_tail = &m->m_nextpkt; + + IFQ_POLL(&ifp->if_snd, m); + if (m == NULL) + break; + KASSERTMSG(((unsigned)m->m_pkthdr.len <= + un->un_tx_bufsz), + "m->m_pkthdr.len=%d bufsz=%u", + m->m_pkthdr.len, un->un_tx_bufsz); + if ((unsigned)m->m_pkthdr.len > + un->un_tx_bufsz - offset) { + /* packet payload too large */ + m = NULL; /* leave in ifp->if_snd q */ + break; + } + KASSERTMSG(((unsigned)m->m_pkthdr.len <= + un->un_tx_bufsz - offset), + "offset=%u m->m_pkthdr.len=%d bufsz=%u", + offset, m->m_pkthdr.len, un->un_tx_bufsz); + overhead = uno_tx_add(un, m, npkt, offset); + KASSERTMSG((overhead == -1 || + (unsigned)overhead <= un->un_tx_bufsz), + "overhead=%d bufsz=%u", + overhead, un->un_tx_bufsz); + if (overhead == -1 || + (unsigned)overhead > (un->un_tx_bufsz - + offset - (unsigned)m->m_pkthdr.len)) { + /* + * driver didn't like the + * packet, or packet overhead + * too large + */ + m = NULL; /* leave in ifp->if_snd q */ + break; + } + IFQ_DEQUEUE(&ifp->if_snd, m); + npkt++; + } + + /* + * We have gathered a nonempty batch of packets + * to transfer. Ask the driver to fill up the + * transfer buffer with the packets. + */ + 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); @@ -548,6 +733,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); @@ -556,18 +743,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++; @@ -1439,8 +1643,8 @@ usbnet_empty_eaddr(struct usbnet * const * the un_eaddr[] is fully zero. */ -void -usbnet_attach(struct usbnet *un) +static void +usbnet_attach_version(struct usbnet *un, enum usbnet_version ver) { USBNETHIST_FUNC(); USBNETHIST_CALLED(); @@ -1480,6 +1684,23 @@ usbnet_attach(struct usbnet *un) unp->unp_rxstopped = true; unp->unp_txstopped = true; unp->unp_attached = true; + + unp->unp_version = ver; +} + +void +usbnet_attach(struct usbnet *un) +{ + + usbnet_attach_version(un, USBNET_V1); +} + +void +usbnet_attach_v2(struct usbnet *un, const struct usbnet_ops_v2 *v2ops) +{ + + KASSERT(un->un_ops == &v2ops->v1); + usbnet_attach_version(un, USBNET_V2); } static void diff -r 823a0086cd5b sys/dev/usb/usbnet.h --- a/sys/dev/usb/usbnet.h Sun Jun 28 11:38:58 2026 +0000 +++ b/sys/dev/usb/usbnet.h Mon Jun 29 02:09:55 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); @@ -183,6 +189,18 @@ struct usbnet_ops { usbnet_intr_cb uno_intr; /* n */ }; +struct usbnet_v2_ops { + 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 */ +}; + +struct usbnet_ops_v2 { + struct usbnet_ops v1; + struct usbnet_v2_ops v2; +}; + /* * USB interrupt pipe support. Use this if usbd_open_pipe_intr() should * be used for the interrupt pipe. @@ -314,6 +332,7 @@ void usbnet_input(struct usbnet * const, /* autoconf */ void usbnet_attach(struct usbnet *); +void usbnet_attach_v2(struct usbnet *, const struct usbnet_ops_v2 *); void usbnet_attach_ifp(struct usbnet *, unsigned, unsigned, const struct usbnet_mii *); int usbnet_detach(device_t, int);