Index: files.usb =================================================================== RCS file: /cvs/src/sys/dev/usb/files.usb,v diff -u -p -r1.150 files.usb --- files.usb 11 Nov 2022 06:48:38 -0000 1.150 +++ files.usb 9 Jun 2025 00:42:38 -0000 @@ -255,6 +255,11 @@ device cdce: ether, ifnet, ifmedia attach cdce at uhub file dev/usb/if_cdce.c cdce +# CDC Ethernet Network Control Mode +device cdcn: ether, ifnet +attach cdcn at uhub +file dev/usb/if_cdcn.c cdcn + # RNDIS device urndis: ether, ifnet, ifmedia attach urndis at uhub Index: if_cdcn.c =================================================================== RCS file: if_cdcn.c diff -N if_cdcn.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ if_cdcn.c 9 Jun 2025 00:42:38 -0000 @@ -0,0 +1,1812 @@ +/* $OpenBSD: if_cdcn.c,v 1.80 2021/01/29 17:12:19 sthen Exp $ */ + +/* + * Copyright (c) 1997, 1998, 1999, 2000-2003 Bill Paul + * Copyright (c) 2003 Craig Boston + * Copyright (c) 2004 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul, THE VOICES IN HIS HEAD OR + * THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * USB Communication Device Class (Ethernet Networking Control Model) + * https://www.usb.org/sites/default/files/CDC1.2_WMC1.1_012011.zip + */ + +#include "bpfilter.h" +#include "kstat.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if NBPFILTER > 0 +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include + +/* + * Network Control Model, NCM16 + NCM32, protocol definitions + */ + +struct usb_ncm16_hdr { + uDWord dwSignature; +#define USB_NTH16_SIGNATURE 0x484d434e /* NCMH */ + uWord wHeaderLength; + uWord wSequence; + uWord wBlockLength; + uWord wDptIndex; +} __packed; + +struct usb_ncm16_dpt { + uDWord dwSignature; +#define USB_NDP16_SIGNATURE 0x304d434e /* NCM0 */ + uWord wLength; + uWord wNextNdpIndex; + + /* followed by usb_ncm16_dp */ +} __packed; + +struct usb_ncm16_dp { + uWord wFrameIndex; + uWord wFrameLength; +} __packed; + +struct usb_ncm32_hdr { + uDWord dwSignature; +#define USB_NTH32_SIGNATURE 0x686d636e /* ncmh */ + uWord wHeaderLength; + uWord wSequence; + uDWord dwBlockLength; + uDWord dwDptIndex; +} __packed; + +struct usb_ncm32_dpt { + uDWord dwSignature; +#define USB_NDP32_SIGNATURE 0x306d636e /* ncm0 */ + uWord wLength; + uWord wReserved6; + uDWord dwNextNdpIndex; + uDWord dwReserved12; + + /* followed by usb_ncm32_dp */ +} __packed; + +struct usb_ncm32_dp { + uDWord dwFrameIndex; + uDWord dwFrameLength; +} __packed; + +/* + * the data payload is aligned, so we can use proper loads/stores + */ + +struct cdcn_ncm16_hdr { + uint32_t dwSignature; + uint16_t wHeaderLength; + uint16_t wSequence; + uint16_t wBlockLength; + uint16_t wDptIndex; +} __packed __aligned(4); + +struct cdcn_ncm16_dpt { + uint32_t dwSignature; + uint16_t wLength; + uint16_t wNextNdpIndex; + + /* followed by usb_ncm16_dp */ +} __packed __aligned(4); + +struct cdcn_ncm16_dp { + uint16_t wFrameIndex; + uint16_t wFrameLength; +} __packed __aligned(4); + +struct cdcn_ncm32_hdr { + uint32_t dwSignature; +#define CDCN_NTH32_SIGNATURE 0x6e636d68 /* ncmh */ + uint16_t wHeaderLength; + uint16_t wSequence; + uint32_t dwBlockLength; + uint32_t dwDptIndex; +} __packed __aligned(4); + +struct cdcn_ncm32_dpt { + uint32_t dwSignature; +#define CDCN_NDP32_SIGNATURE 0x6e636d30 /* ncm0 */ + uint16_t wLength; + uint16_t wReserved6; + uint32_t dwNextNdpIndex; + uint32_t dwReserved12; + + /* followed by cdcn_ncm32_dp */ +} __packed __aligned(4); + +struct cdcn_ncm32_dp { + uint32_t dwFrameIndex; + uint32_t dwFrameLength; +} __packed __aligned(4); + +/* Communications interface class specific descriptors */ + +#define UCDC_NCM_FUNC_DESC_SUBTYPE 0x1A + +struct usb_ncm_func_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bcdNcmVersion[2]; + uByte bmNetworkCapabilities; +#define UCDC_NCM_CAP_FILTER (1 << 0) +#define UCDC_NCM_CAP_MAC_ADDR (1 << 1) +#define UCDC_NCM_CAP_ENCAP (1 << 2) +#define UCDC_NCM_CAP_MAX_DGRAM (1 << 3) +#define UCDC_NCM_CAP_CRCMODE (1 << 4) +} __packed; + +#define CDCN_NETWORK_CAP_BITS "\20" \ + "\1" "Filter" \ + "\2" "NetAddress" \ + "\3" "Encap" \ + "\4" "MaxDgram" \ + "\5" "CRCMode" + +/* Communications interface specific class request codes */ + +#define UCDC_NCM_SET_ETHERNET_MULTICAST_FILTERS 0x40 +#define UCDC_NCM_SET_ETHERNET_POWER_MGMT_PATTERN_FILTER 0x41 +#define UCDC_NCM_GET_ETHERNET_POWER_MGMT_PATTERN_FILTER 0x42 +#define UCDC_NCM_SET_ETHERNET_PACKET_FILTER 0x43 +#define UCDC_NCM_GET_ETHERNET_STATISTIC 0x44 +#define UCDC_NCM_GET_NTB_PARAMETERS 0x80 +#define UCDC_NCM_GET_NET_ADDRESS 0x81 +#define UCDC_NCM_SET_NET_ADDRESS 0x82 +#define UCDC_NCM_GET_NTB_FORMAT 0x83 +#define UCDC_NCM_SET_NTB_FORMAT 0x84 +#define UCDC_NCM_GET_NTB_INPUT_SIZE 0x85 +#define UCDC_NCM_SET_NTB_INPUT_SIZE 0x86 +#define UCDC_NCM_GET_MAX_DATAGRAM_SIZE 0x87 +#define UCDC_NCM_SET_MAX_DATAGRAM_SIZE 0x88 +#define UCDC_NCM_GET_CRC_MODE 0x89 +#define UCDC_NCM_SET_CRC_MODE 0x8A + +/* + * UCDC_NCM_SET_ETHERNET_PACKET_FILTER bits + */ +#define UCDC_NCM_FILTER_PACKET_TYPE_PROMISCUOUS (1 << 0) +#define UCDC_NCM_FILTER_PACKET_TYPE_ALL_MULTICAST (1 << 1) +#define UCDC_NCM_FILTER_PACKET_TYPE_DIRECTED (1 << 2) +#define UCDC_NCM_FILTER_PACKET_TYPE_BROADCAST (1 << 3) +#define UCDC_NCM_FILTER_PACKET_TYPE_MULTICAST (1 << 4) + +struct usb_ncm_parameters { + uWord wLength; + uWord bmNtbFormatsSupported; +#define UCDC_NCM_FORMAT_NTB16 0x0001 +#define UCDC_NCM_FORMAT_NTB32 0x0002 + uDWord dwNtbInMaxSize; + uWord wNdpInDivisor; + uWord wNdpInPayloadRemainder; + uWord wNdpInAlignment; + uWord wReserved14; + uDWord dwNtbOutMaxSize; + uWord wNdpOutDivisor; + uWord wNdpOutPayloadRemainder; + uWord wNdpOutAlignment; + uWord wNtbOutMaxDatagrams; +} __packed __aligned(sizeof(uDWord)); + +/* Communications interface specific class notification codes */ +#define UCDC_NCM_NOTIF_NETWORK_CONNECTION 0x00 +#define UCDC_NCM_NOTIF_RESPONSE_AVAILABLE 0x01 +#define UCDC_NCM_NOTIF_CONNECTION_SPEED_CHANGE 0x2A + +#define DPRINTF(_sc, fmt...) do { \ + if (ISSET((_sc)->sc_if.if_flags, IFF_DEBUG)) \ + printf(fmt); \ +} while (0) + +#if NKSTAT > 0 +struct cdcn_stats { + struct kstat_kv st_usb_tx; + struct kstat_kv st_usb_rx; + struct kstat_kv st_usb_rx_ntb16; + struct kstat_kv st_usb_rx_ntb32; +}; +#endif + +struct cdcn_softc { + struct device sc_dev; + struct arpcom sc_ac; +#define sc_if sc_ac.ac_if + + caddr_t sc_bpf; + + struct usbd_device *sc_udev; + struct usbd_interface *sc_ctl_iface; + int sc_intr_no; + struct usbd_pipe *sc_intr_pipe; + struct usb_cdc_notification sc_intr_buf; + int sc_intr_size; + struct usbd_interface *sc_data_iface; + int sc_bulkin_no; + struct usbd_pipe *sc_bulkin_pipe; + int sc_bulkout_no; + struct usbd_pipe *sc_bulkout_pipe; + int sc_rxeof_errors; + + struct usbd_xfer *sc_tx_xfer; + uint8_t *sc_tx_buf; + struct usbd_xfer *sc_rx_xfer; + uint8_t *sc_rx_buf; + + uint16_t sc_tx_seq; + + uint32_t sc_rx_max; + uint32_t sc_tx_max; + uint16_t sc_tx_remainder; + uint16_t sc_tx_modulus; + uint16_t sc_tx_struct_align; + uint16_t sc_tx_nframe; + + uint16_t sc_ntb_formats; + uint8_t sc_ctl_ifaceno; + uint8_t sc_ncm_cap; + uint16_t sc_mc_filters; + char sc_attached; + + uint32_t sc_eth_stats; +#if NKSTAT > 0 + struct rwlock sc_eth_kstat_lock; + struct kstat *sc_eth_kstat; + + struct mutex sc_kstat_mtx; + struct cdcn_stats sc_stats; + struct kstat *sc_kstat; + +#endif +}; + +#define DEVNAME(_sc) ((_sc)->sc_dev.dv_xname) + +int cdcn_match(struct device *, void *, void *); +void cdcn_attach(struct device *, struct device *, void *); +int cdcn_detach(struct device *, int); + +struct cfdriver cdcn_cd = { + NULL, "cdcn", DV_IFNET +}; + +const struct cfattach cdcn_ca = { + sizeof(struct cdcn_softc), cdcn_match, cdcn_attach, cdcn_detach +}; + +static void cdcn_rxeof(struct usbd_xfer *, void *, usbd_status); +static void cdcn_txeof(struct usbd_xfer *, void *, usbd_status); +static void cdcn_start32(struct ifnet *); +static void cdcn_start16(struct ifnet *); +static int cdcn_ioctl(struct ifnet *, u_long, caddr_t); +static int cdcn_up(struct cdcn_softc *); +static int cdcn_down(struct cdcn_softc *); +static int cdcn_iff(struct cdcn_softc *); +static int cdcn_setlladdr(struct cdcn_softc *, const struct ifreq *); +static void cdcn_watchdog(struct ifnet *); +static void cdcn_intr(struct usbd_xfer *, void *, usbd_status); + +static int cdcn_params(struct cdcn_softc *); +static int cdcn_ncm_set(struct cdcn_softc *, uint8_t, uint16_t); + +#if NKSTAT > 0 +static void cdcn_kstat_attach(struct cdcn_softc *); +static void cdcn_kstat_detach(struct cdcn_softc *); +#endif + +int +cdcn_match(struct device *parent, void *match, void *aux) +{ + struct usb_attach_arg *uaa = aux; + usb_interface_descriptor_t *id; + + if (uaa->iface == NULL) + return (UMATCH_NONE); + + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) + return (UMATCH_NONE); + + if (id->bInterfaceClass == UICLASS_CDC && + id->bInterfaceSubClass == UISUBCLASS_NETWORK_CONTROL_MODEL) + return (UMATCH_IFACECLASS_GENERIC); + + return (UMATCH_NONE); +} + +void +cdcn_attach(struct device *parent, struct device *self, void *aux) +{ + struct cdcn_softc *sc = (struct cdcn_softc *)self; + struct usb_attach_arg *uaa = aux; + struct ifnet *ifp = &sc->sc_if; + struct usbd_device *dev = uaa->device; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + struct usb_cdc_union_descriptor *ud; + struct usb_cdc_ethernet_descriptor *ethd = NULL; + struct usb_ncm_func_descriptor *ufd = NULL; + usb_config_descriptor_t *cd; + const usb_descriptor_t *desc; + struct usbd_desc_iter iter; + char eaddr[ETHER_ADDR_LEN * 2]; + int i, j, numalts; + int ctl_ifcno = -1; + int data_ifcno = -1; + + sc->sc_udev = uaa->device; + sc->sc_ctl_iface = uaa->iface; + sc->sc_ctl_ifaceno = uaa->ifaceno; + id = usbd_get_interface_descriptor(sc->sc_ctl_iface); + ctl_ifcno = id->bInterfaceNumber; + + /* Get the data interface no. and capabilities */ + usbd_desc_iter_init(dev, &iter); + while ((desc = usbd_desc_iter_next(&iter)) != NULL) { + if (desc->bDescriptorType != UDESC_CS_INTERFACE) + continue; + + switch(desc->bDescriptorSubtype) { + case UDESCSUB_CDC_UNION: + ud = (struct usb_cdc_union_descriptor *)desc; + if (ud->bMasterInterface == ctl_ifcno) + data_ifcno = ud->bSlaveInterface[0]; + break; + case UDESCSUB_CDC_ENF: + if (desc->bLength < sizeof(*ethd)) + break; + if (ethd) { + printf("%s: extra ethernet descriptor\n", + DEVNAME(sc)); + return; + } + ethd = (struct usb_cdc_ethernet_descriptor *)desc; + break; + case UCDC_NCM_FUNC_DESC_SUBTYPE: + if (desc->bLength < sizeof(*ufd)) + break; + if (ufd != NULL) { + printf("%s: extra ncm descriptor\n", + DEVNAME(sc)); + return; + } + ufd = (struct usb_ncm_func_descriptor *)desc; + sc->sc_ncm_cap = ufd->bmNetworkCapabilities; + break; + } + } + + if (ethd == NULL) { + printf("%s: no CDC Ethernet descriptor\n", DEVNAME(sc)); + return; + } + if (ufd == NULL) { + printf("%s: no NCM descriptor\n", DEVNAME(sc)); + return; + } + + if (data_ifcno == -1) { + sc->sc_data_iface = sc->sc_ctl_iface; + } else { + for (i = 0; i < uaa->nifaces; i++) { + if (usbd_iface_claimed(sc->sc_udev, i)) + continue; + id = usbd_get_interface_descriptor(uaa->ifaces[i]); + if (id != NULL && id->bInterfaceNumber == data_ifcno) { + sc->sc_data_iface = uaa->ifaces[i]; + usbd_claim_iface(sc->sc_udev, i); + } + } + } + + if (sc->sc_data_iface == NULL) { + printf("%s: no data interface\n", DEVNAME(sc)); + return; + } + + id = usbd_get_interface_descriptor(sc->sc_ctl_iface); + sc->sc_intr_no = -1; + for (i = 0; i < id->bNumEndpoints && sc->sc_intr_no == -1; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_ctl_iface, i); + if (!ed) { + printf("%s: no descriptor for interrupt endpoint %d\n", + DEVNAME(sc), i); + return; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->sc_intr_no = ed->bEndpointAddress; + sc->sc_intr_size = sizeof(sc->sc_intr_buf); + } + } + + id = usbd_get_interface_descriptor(sc->sc_data_iface); + cd = usbd_get_config_descriptor(sc->sc_udev); + numalts = usbd_get_no_alts(cd, id->bInterfaceNumber); + + for (j = 0; j < numalts; j++) { + if (usbd_set_interface(sc->sc_data_iface, j)) { + printf("%s: interface alternate setting %d failed\n", + DEVNAME(sc), j); + return; + } + /* Find endpoints. */ + id = usbd_get_interface_descriptor(sc->sc_data_iface); + sc->sc_bulkin_no = sc->sc_bulkout_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor( + sc->sc_data_iface, i); + if (!ed) { + printf("%s: no descriptor for bulk endpoint " + "%d\n", DEVNAME(sc), i); + return; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_bulkin_no = ed->bEndpointAddress; + } else if ( + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_bulkout_no = ed->bEndpointAddress; + } +#ifdef CDCE_DEBUG + else if ( + UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) != UE_INTERRUPT) { + printf("%s: unexpected endpoint, ep=%x attr=%x" + "\n", DEVNAME(sc), + ed->bEndpointAddress, ed->bmAttributes); + } +#endif + } + if ((sc->sc_bulkin_no != -1) && (sc->sc_bulkout_no != -1)) { + printf("%s: intr=0x%x, in=0x%x, out=0x%x\n", + DEVNAME(sc), + sc->sc_intr_no, sc->sc_bulkin_no, + sc->sc_bulkout_no); + goto found; + } + } + + if (sc->sc_bulkin_no == -1) { + printf("%s: could not find data bulk in\n", + DEVNAME(sc)); + return; + } + if (sc->sc_bulkout_no == -1 ) { + printf("%s: could not find data bulk out\n", + DEVNAME(sc)); + return; + } + +found: + if (usbd_set_interface(sc->sc_data_iface, 1) != 0) + printf("%s: unable to toggle altsetting\n", DEVNAME(sc)); + if (usbd_set_interface(sc->sc_data_iface, 0) != 0) { + printf("%s: unable to reset altsetting\n", DEVNAME(sc)); + return; + } + + if (cdcn_params(sc) != 0) { + /* error has already been printed */ + return; + } + + if (sc->sc_ntb_formats == 0) { + printf("%s: no supported formats\n", DEVNAME(sc)); + return; + } + + if (usbd_get_string(sc->sc_udev, ethd->iMacAddress, + eaddr, sizeof(eaddr)) != NULL) { + for (i = 0; i < sizeof(eaddr); i++) { + int c = eaddr[i]; + + if ('0' <= c && c <= '9') + c -= '0'; + else if ('A' <= c && c <= 'F') + c -= 'A' - 10; + else if ('a' <= c && c <= 'f') + c -= 'a' - 10; + c &= 0xf; + if (i % 2 == 0) + c <<= 4; + sc->sc_ac.ac_enaddr[i / 2] |= c; + } + } else + ether_fakeaddr(ifp); + + ifp->if_hardmtu = UGETW(ethd->wMaxSegmentSize) - + sizeof(struct ether_header); + + sc->sc_mc_filters = UGETW(ethd->wNumberMCFilters) & 0x7fff; + sc->sc_eth_stats = UGETDW(ethd->bmEthernetStatistics); + + /* bcdNcmVersion is little endian */ + printf("%s: NCM %u.%u", DEVNAME(sc), + ufd->bcdNcmVersion[1], ufd->bcdNcmVersion[0]); + //printf(", capabilities <%b>", sc->sc_ncm_cap, CDCN_NETWORK_CAP_BITS); + printf(", address %s", ether_sprintf(sc->sc_ac.ac_enaddr)); + printf("\n"); + + strlcpy(ifp->if_xname, DEVNAME(sc), IFNAMSIZ); + ifp->if_softc = sc; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = cdcn_ioctl; + ifp->if_start = ISSET(sc->sc_ntb_formats, UCDC_NCM_FORMAT_NTB32) ? + cdcn_start32 : cdcn_start16; + ifp->if_watchdog = cdcn_watchdog; + ifp->if_link_state = LINK_STATE_UNKNOWN; + + if_attach(ifp); + ether_ifattach(ifp); + +#if NBPFILTER > 0 + bpfattach(&sc->sc_bpf, ifp, DLT_NULL, 0); +#endif +#if NKSTAT > 0 + cdcn_kstat_attach(sc); +#endif /* NKSTAT */ + + sc->sc_attached = 1; +} + +static int +cdcn_params(struct cdcn_softc *sc) +{ + struct usb_ncm_parameters param; + struct usb_device_request req; + int error; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_NCM_GET_NTB_PARAMETERS; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ctl_ifaceno; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof(param)); + + error = usbd_do_request_flags(sc->sc_udev, &req, ¶m, + 0, NULL, USBD_DEFAULT_TIMEOUT); + if (error != 0) { + printf("%s: unable to request NCM NTB params\n", DEVNAME(sc)); + return (error); + } + + sc->sc_ntb_formats = UGETW(param.bmNtbFormatsSupported) & + (UCDC_NCM_FORMAT_NTB16|UCDC_NCM_FORMAT_NTB32); + + sc->sc_rx_max = UGETDW(param.dwNtbInMaxSize); + sc->sc_tx_max = UGETDW(param.dwNtbOutMaxSize); + sc->sc_tx_remainder = UGETW(param.wNdpOutPayloadRemainder); + sc->sc_tx_modulus = UGETW(param.wNdpOutDivisor); + sc->sc_tx_struct_align = roundup(UGETW(param.wNdpOutAlignment), 4); + sc->sc_tx_nframe = UGETW(param.wNtbOutMaxDatagrams); + + printf("%s: fmts %x\n", DEVNAME(sc), + UGETW(param.bmNtbFormatsSupported)); + printf("%s: dwNtbInMaxSize %u\n", DEVNAME(sc), + UGETDW(param.dwNtbInMaxSize)); + printf("%s: wNdpInDivisor %u\n", DEVNAME(sc), + UGETW(param.wNdpInDivisor)); + printf("%s: wNdpInPayloadRemainder %u\n", DEVNAME(sc), + UGETW(param.wNdpInPayloadRemainder)); + printf("%s: wNdpInAlignment %u\n", DEVNAME(sc), + UGETW(param.wNdpInAlignment)); + printf("%s: dwNtbOutMaxSize %u\n", DEVNAME(sc), + UGETDW(param.dwNtbOutMaxSize)); + printf("%s: wNdpOutDivisor %u\n", DEVNAME(sc), + UGETW(param.wNdpOutDivisor)); + printf("%s: wNdpOutPayloadRemainder %u\n", DEVNAME(sc), + UGETW(param.wNdpOutPayloadRemainder)); + printf("%s: wNdpOutAlignment %u\n", DEVNAME(sc), + UGETW(param.wNdpOutAlignment)); + printf("%s: wNtbOutMaxDatagrams %u\n", DEVNAME(sc), + UGETW(param.wNtbOutMaxDatagrams)); + + return (0); +} + +static int +cdcn_ncm_set(struct cdcn_softc *sc, uint8_t request, uint16_t value) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = request; + USETW(req.wValue, value); + req.wIndex[0] = sc->sc_ctl_ifaceno; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + return (usbd_do_request_flags(sc->sc_udev, &req, NULL, + 0, NULL, USBD_DEFAULT_TIMEOUT)); +} + +int +cdcn_detach(struct device *self, int flags) +{ + struct cdcn_softc *sc = (struct cdcn_softc *)self; + struct ifnet *ifp = &sc->sc_if; + int s; + + if (!sc->sc_attached) + return (0); + +#if NKSTAT > 0 + cdcn_kstat_detach(sc); +#endif /* NKSTAT */ + + s = splusb(); + + if (ifp->if_flags & IFF_RUNNING) + cdcn_down(sc); + + if (ifp->if_softc != NULL) { + ether_ifdetach(ifp); + if_detach(ifp); + } + + sc->sc_attached = 0; + splx(s); + + return (0); +} + +void +cdcn_start32(struct ifnet *ifp) +{ + struct cdcn_softc *sc = ifp->if_softc; + struct mbuf *m; + struct cdcn_ncm32_hdr *nth; + struct cdcn_ncm32_dpt *ndp; + struct cdcn_ncm32_dp *sgl; + + uint8_t *buf, *pkt, *end; + uint32_t off; + int err; +#if NBPFILTER > 0 + caddr_t if_bpf; + + if (usbd_is_dying(sc->sc_udev) || ifq_is_oactive(&ifp->if_snd)) + return; + + m = ifq_dequeue(&ifp->if_snd); + if (m == NULL) + return; + + /* lay it out */ + + buf = sc->sc_tx_buf; + nth = (struct cdcn_ncm32_hdr *)(buf); + + off = roundup(sizeof(*nth), sc->sc_tx_struct_align); + pkt = buf + off; + + off = roundup(off + m->m_pkthdr.len, sc->sc_tx_struct_align); + ndp = (struct cdcn_ncm32_dpt *)(buf + off); + sgl = (struct cdcn_ncm32_dp *)(ndp + 1); + end = (uint8_t *)(sgl + 2); + + /* fill it in */ + + nth->dwSignature = htole32(USB_NTH32_SIGNATURE); + htolem16(&nth->wHeaderLength, sizeof(*nth)); + htolem16(&nth->wSequence, sc->sc_tx_seq++); + htolem32(&nth->dwBlockLength, end - buf); + htolem32(&nth->dwDptIndex, (uint8_t *)ndp - buf); + + m_copydata(m, 0, m->m_pkthdr.len, pkt); + + ndp->dwSignature = htole32(USB_NDP32_SIGNATURE); + htolem16(&ndp->wLength, end - (uint8_t *)ndp); + ndp->wReserved6 = htole16(0); + ndp->dwNextNdpIndex = htole32(0); + ndp->dwReserved12 = htole32(0); + + htolem32(&sgl[0].dwFrameIndex, pkt - buf); + htolem32(&sgl[0].dwFrameLength, m->m_pkthdr.len); + sgl[1].dwFrameIndex = htole32(0); + sgl[1].dwFrameLength = htole32(0); + +#if NBPFILTER > 0 + if_bpf = ifp->if_bpf; + if (if_bpf) + bpf_mtap(if_bpf, m, BPF_DIRECTION_OUT); + +#if 0 + if_bpf = sc->sc_bpf; + if (if_bpf) { + bpf_tap_hdr(sc_bpf, NULL, 0, buf, end - buf, + BPF_DIRECTION_OUT); +#endif +#endif + m_freem(m); + + ifq_set_oactive(&ifp->if_snd); + ifp->if_timer = 6; + +#if NKSTAT > 0 + mtx_enter(&sc->sc_kstat_mtx); + kstat_kv_u64(&sc->sc_stats.st_usb_tx)++; + mtx_leave(&sc->sc_kstat_mtx); +#endif + + usbd_setup_xfer(sc->sc_tx_xfer, sc->sc_bulkout_pipe, sc, + buf, end - buf, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, + USBD_DEFAULT_TIMEOUT, cdcn_txeof); + err = usbd_transfer(sc->sc_tx_xfer); + if (err != USBD_IN_PROGRESS && ISSET(ifp->if_flags, IFF_DEBUG)) { + printf("%s: start tx error: %s\n", DEVNAME(sc), + usbd_errstr(err)); + } +} + +void +cdcn_start16(struct ifnet *ifp) +{ + struct cdcn_softc *sc = ifp->if_softc; + struct mbuf *m; + struct cdcn_ncm16_hdr *nth; + struct cdcn_ncm16_dpt *ndp; + struct cdcn_ncm16_dp *sgl; + + uint8_t *buf, *end; + uint16_t off, len; + int err; + + if (usbd_is_dying(sc->sc_udev) || ifq_is_oactive(&ifp->if_snd)) + return; + + m = ifq_dequeue(&ifp->if_snd); + if (m == NULL) + return; + + /* lay it out */ + + buf = sc->sc_tx_buf; + nth = (struct cdcn_ncm16_hdr *)(buf); + + off = roundup(sizeof(*nth), sc->sc_tx_struct_align); + + ndp = (struct cdcn_ncm16_dpt *)(buf + off); + sgl = (struct cdcn_ncm16_dp *)(ndp + 1); + end = (uint8_t *)(sgl + 2); + + len = end - buf; + len += m->m_pkthdr.len; + + /* fill it in */ + + nth->dwSignature = htole32(USB_NTH16_SIGNATURE); + htolem16(&nth->wHeaderLength, sizeof(*nth)); + htolem16(&nth->wSequence, sc->sc_tx_seq++); + htolem16(&nth->wBlockLength, len); + htolem16(&nth->wDptIndex, (uint8_t *)ndp - buf); + + ndp->dwSignature = htole32(USB_NDP16_SIGNATURE); + htolem16(&ndp->wLength, end - (uint8_t *)ndp); + ndp->wNextNdpIndex = htole16(0); + + htolem16(&sgl[0].wFrameIndex, end - buf); + htolem16(&sgl[0].wFrameLength, m->m_pkthdr.len); + sgl[1].wFrameIndex = htole16(0); + sgl[1].wFrameLength = htole16(0); + + m_copydata(m, 0, m->m_pkthdr.len, end); + +#if NBPFILTER > 0 + { + caddr_t if_bpf = ifp->if_bpf; + if (if_bpf) { + bpf_mtap(if_bpf, m, BPF_DIRECTION_OUT); + bpf_tap_hdr(if_bpf, NULL, 0, buf, len, + BPF_DIRECTION_OUT); + } + } + { + caddr_t sc_bpf = sc->sc_bpf; + if (sc_bpf) { + bpf_tap_hdr(sc_bpf, NULL, 0, buf, len, + BPF_DIRECTION_OUT); + } + } +#endif + m_freem(m); + + ifq_set_oactive(&ifp->if_snd); + ifp->if_timer = 6; + +#if NKSTAT > 0 + mtx_enter(&sc->sc_kstat_mtx); + kstat_kv_u64(&sc->sc_stats.st_usb_tx)++; + mtx_leave(&sc->sc_kstat_mtx); +#endif + + usbd_setup_xfer(sc->sc_tx_xfer, sc->sc_bulkout_pipe, sc, + buf, len, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, + USBD_DEFAULT_TIMEOUT, cdcn_txeof); + err = usbd_transfer(sc->sc_tx_xfer); + if (err != USBD_IN_PROGRESS && ISSET(ifp->if_flags, IFF_DEBUG)) { + printf("%s: start tx error: %s\n", DEVNAME(sc), + usbd_errstr(err)); + } +} +#endif + +static int +cdcn_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct cdcn_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + int error = 0; + + if (usbd_is_dying(sc->sc_udev)) + return ENXIO; + + switch(command) { + case SIOCSIFADDR: + break; + + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_flags & IFF_RUNNING) + error = ENETRESET; + else + error = cdcn_up(sc); + } else { + if (ifp->if_flags & IFF_RUNNING) + error = cdcn_down(sc); + } + break; + + case SIOCSIFLLADDR: + error = cdcn_setlladdr(sc, ifr); + break; + + default: + error = ether_ioctl(ifp, &sc->sc_ac, command, data); + break; + } + + if (error == ENETRESET) + error = cdcn_iff(sc); + + return (error); +} + +void +cdcn_watchdog(struct ifnet *ifp) +{ + struct cdcn_softc *sc = ifp->if_softc; + + if (usbd_is_dying(sc->sc_udev)) + return; + + ifp->if_oerrors++; + printf("%s: watchdog timeout\n", DEVNAME(sc)); +} + +static int +cdcn_up(struct cdcn_softc *sc) +{ + struct ifnet *ifp = &sc->sc_if; + usbd_status err; + + if (ISSET(sc->sc_ncm_cap, UCDC_NCM_CAP_CRCMODE) && + cdcn_ncm_set(sc, UCDC_NCM_SET_CRC_MODE, 0) != 0) { + printf("%s: unable to disable CRC mode\n", DEVNAME(sc)); + /* oh well, we tried */ + } + + if (sc->sc_ntb_formats != UCDC_NCM_FORMAT_NTB16 && + cdcn_ncm_set(sc, UCDC_NCM_SET_NTB_FORMAT, 1 /* NTB-16 */) != 0) { + printf("%s: unable to set NTB-16 descriptor format\n", + DEVNAME(sc)); + /* oh well, we tried */ + } + + if (usbd_set_interface(sc->sc_data_iface, 1) != 0) { + printf("%s: unable to set altsetting\n", DEVNAME(sc)); + return (EIO); + } + + if (sc->sc_intr_no != -1) { + err = usbd_open_pipe_intr(sc->sc_ctl_iface, sc->sc_intr_no, + USBD_SHORT_XFER_OK, &sc->sc_intr_pipe, sc, + &sc->sc_intr_buf, sc->sc_intr_size, cdcn_intr, + USBD_DEFAULT_INTERVAL); + if (err) { + printf("%s: open interrupt pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + goto errout; + } + } + + sc->sc_tx_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_tx_xfer == NULL) { + printf("%s: unable to alloc tx xfer\n", DEVNAME(sc)); + goto close_intr; + } + sc->sc_tx_buf = usbd_alloc_buffer(sc->sc_tx_xfer, sc->sc_tx_max); + if (sc->sc_tx_buf == NULL) { + printf("%s: unable to alloc tx buf\n", DEVNAME(sc)); + goto free_tx_xfer; + } + + sc->sc_rx_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_rx_xfer == NULL) { + printf("%s: unable to alloc rx xfer\n", DEVNAME(sc)); + goto free_tx_buf; + } + sc->sc_rx_buf = usbd_alloc_buffer(sc->sc_rx_xfer, sc->sc_rx_max); + if (sc->sc_rx_buf == NULL) { + printf("%s: unable to alloc rx buf\n", DEVNAME(sc)); + goto free_rx_xfer; + } + + /* Maybe set multicast / broadcast here??? */ + + err = usbd_open_pipe(sc->sc_data_iface, sc->sc_bulkin_no, + USBD_EXCLUSIVE_USE, &sc->sc_bulkin_pipe); + if (err) { + printf("%s: open rx pipe failed: %s\n", DEVNAME(sc), + usbd_errstr(err)); + goto free_rx_buf; + } + + err = usbd_open_pipe(sc->sc_data_iface, sc->sc_bulkout_no, + USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe); + if (err) { + printf("%s: open tx pipe failed: %s\n", DEVNAME(sc), + usbd_errstr(err)); + goto close_rx; + } + + sc->sc_tx_seq = 0; + + SET(ifp->if_flags, IFF_RUNNING); + ifq_clr_oactive(&ifp->if_snd); + + usbd_setup_xfer(sc->sc_rx_xfer, sc->sc_bulkin_pipe, sc, + sc->sc_rx_buf, sc->sc_rx_max, USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, cdcn_rxeof); + usbd_transfer(sc->sc_rx_xfer); + + return (ENETRESET); + +close_rx: + err = usbd_close_pipe(sc->sc_bulkin_pipe); + if (err) { + printf("%s: close rx pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + } +free_rx_buf: + /* implicit usbd_free_buffer */ +free_rx_xfer: + usbd_free_xfer(sc->sc_rx_xfer); +free_tx_buf: + /* implicit usbd_free_buffer */ +free_tx_xfer: + usbd_free_xfer(sc->sc_tx_xfer); +close_intr: + if (sc->sc_intr_no != -1) { + err = usbd_close_pipe(sc->sc_intr_pipe); + if (err) { + printf("%s: close interrupt pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + } + } +errout: + if (usbd_set_interface(sc->sc_data_iface, 0) != 0) + printf("%s: unable to reset altsetting\n", DEVNAME(sc)); + return (EIO); +} + +static int +cdcn_down(struct cdcn_softc *sc) +{ + struct ifnet *ifp = &sc->sc_if; + usbd_status err; + + ifp->if_timer = 0; + CLR(ifp->if_flags, IFF_RUNNING); + ifq_clr_oactive(&ifp->if_snd); + + err = usbd_close_pipe(sc->sc_bulkout_pipe); + if (err) { + printf("%s: close rx pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + } + + err = usbd_close_pipe(sc->sc_bulkin_pipe); + if (err) { + printf("%s: close rx pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + } + + /* implicit usbd_free_buffer */ + usbd_free_xfer(sc->sc_rx_xfer); + + /* implicit usbd_free_buffer */ + usbd_free_xfer(sc->sc_tx_xfer); + + if (usbd_set_interface(sc->sc_data_iface, 0) != 0) { + printf("%s: unable to set altsetting\n", DEVNAME(sc)); + } + + if (sc->sc_intr_no != -1) { + err = usbd_close_pipe(sc->sc_intr_pipe); + if (err) { + printf("%s: close interrupt pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + } + } + + if (ifp->if_link_state != LINK_STATE_UNKNOWN) { + ifp->if_link_state = LINK_STATE_UNKNOWN; + if_link_state_change(ifp); + } + + return (0); +} + +static int +cdcn_iff(struct cdcn_softc *sc) +{ + struct arpcom *ac = &sc->sc_ac; + struct ifnet *ifp = &ac->ac_if; + struct usb_device_request req; + uint16_t filter; + int error; + + if (!ISSET(sc->sc_ncm_cap, UCDC_NCM_CAP_FILTER)) { + /* not much we can do here */ + return (0); + } + + CLR(ifp->if_flags, IFF_ALLMULTI); + + filter = UCDC_NCM_FILTER_PACKET_TYPE_DIRECTED | + UCDC_NCM_FILTER_PACKET_TYPE_BROADCAST; + + if (ISSET(ifp->if_flags, IFF_PROMISC)) + SET(filter, UCDC_NCM_FILTER_PACKET_TYPE_PROMISCUOUS); + else if (ac->ac_multirangecnt > 0 || + ac->ac_multicnt > sc->sc_mc_filters) { +allmulti: + SET(filter, UCDC_NCM_FILTER_PACKET_TYPE_ALL_MULTICAST); + SET(ifp->if_flags, IFF_ALLMULTI); + } else if (ac->ac_multicnt > 0) { + struct ether_multi *enm; + struct ether_multistep step; + struct ether_addr *eas; + int n = 0; + + eas = mallocarray(ac->ac_multicnt, sizeof(*eas), M_TEMP, + M_WAITOK|M_CANFAIL); + if (eas == NULL) + goto allmulti; + + ETHER_FIRST_MULTI(step, ac, enm); + while (enm != NULL) { + memcpy(&eas[n++], enm->enm_addrlo, sizeof(*eas)); + ETHER_NEXT_MULTI(step, enm); + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_NCM_SET_ETHERNET_MULTICAST_FILTERS; + USETW(req.wValue, n); + req.wIndex[0] = sc->sc_ctl_ifaceno; + req.wIndex[1] = 0; + USETW(req.wLength, n * sizeof(*eas)); + + error = usbd_do_request_flags(sc->sc_udev, &req, eas, + 0, NULL, USBD_DEFAULT_TIMEOUT); + free(eas, M_TEMP, ac->ac_multicnt * sizeof(*eas)); + if (error != 0) + return (EIO); + + SET(filter, UCDC_NCM_FILTER_PACKET_TYPE_MULTICAST); + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_NCM_SET_ETHERNET_PACKET_FILTER; + USETW(req.wValue, filter); + req.wIndex[0] = sc->sc_ctl_ifaceno; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + if (usbd_do_request_flags(sc->sc_udev, &req, NULL, + 0, NULL, USBD_DEFAULT_TIMEOUT) != 0) + return (EIO); + + return (0); +} + +static int +cdcn_setlladdr(struct cdcn_softc *sc, const struct ifreq *ifr) +{ + struct ifnet *ifp = &sc->sc_if; + struct usb_device_request req; + + if (!ISSET(sc->sc_ncm_cap, UCDC_NCM_CAP_MAC_ADDR)) + return (EOPNOTSUPP); + + /* + * The host shall only send this command while the NCM Data + * Interface is in alternate setting 0. + */ + if (ISSET(ifp->if_flags, IFF_RUNNING)) + return (EBUSY); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_NCM_SET_NET_ADDRESS; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ctl_ifaceno; + req.wIndex[1] = 0; + USETW(req.wLength, ETHER_ADDR_LEN); + + if (usbd_do_request_flags(sc->sc_udev, &req, + (void *)ifr->ifr_addr.sa_data, + 0, NULL, USBD_DEFAULT_TIMEOUT) != 0) + return (EIO); + + return (if_setlladdr(ifp, ifr->ifr_addr.sa_data)); +} + +static void +cdcn_rxeof_ntb16(struct cdcn_softc *sc, struct mbuf_list *ml, + const uint8_t *buf, size_t len) +{ + struct ifnet *ifp = &sc->sc_if; + const struct usb_ncm16_hdr *nth; + const struct usb_ncm16_dpt *ndp; + const struct usb_ncm16_dp *sgl; + uint32_t sig; + size_t hlen, dlen, blen, off; + struct mbuf *m; + + if (len < sizeof(*nth)) { + DPRINTF(sc, "%s: short nth16\n", DEVNAME(sc)); + goto error; + } + + nth = (const struct usb_ncm16_hdr *)buf; + hlen = UGETW(nth->wHeaderLength); + if (hlen < sizeof(*nth)) { + DPRINTF(sc, "%s: nth16 wHeaderLength is too short\n", + DEVNAME(sc)); + goto error; + } + + blen = UGETW(nth->wBlockLength); + if (blen > len) { + DPRINTF(sc, "%s: nth16 wBlockLength is greater than " + "available data\n", DEVNAME(sc)); + goto error; + } + len = blen; + + off = UGETW(nth->wDptIndex); + if (off < hlen) { + DPRINTF(sc, "%s: nth16 wNdpIndex overlaps header\n", + DEVNAME(sc)); + goto error; + } + if (off + sizeof(*ndp) > len) { + DPRINTF(sc, "%s: ndp16 outside available data\n", + DEVNAME(sc)); + goto error; + } + + ndp = (const struct usb_ncm16_dpt *)(buf + off); + sig = UGETDW(ndp->dwSignature); + if ((sig & ~1U) != USB_NDP16_SIGNATURE) { + DPRINTF(sc, "%s: unexpected ndp16 signature\n", + DEVNAME(sc)); + goto error; + } + + dlen = UGETW(ndp->wLength); + if (dlen < sizeof(*ndp)) { + DPRINTF(sc, "%s: ndp16 wHeaderLength is too short\n", + DEVNAME(sc)); + goto error; + } + dlen -= sizeof(*ndp); + off += sizeof(*ndp); + + sgl = (const struct usb_ncm16_dp *)(ndp + 1); + + do { + uint16_t didx, dlen; + unsigned int mlen; + + off += sizeof(*sgl); + if (off > len) { + DPRINTF(sc, "%s: sgl falls out of the buffer\n", + DEVNAME(sc)); + break; + } + + didx = UGETW(sgl->wFrameIndex); + dlen = UGETW(sgl->wFrameLength); + if (didx == 0 || dlen == 0) { + /* all done */ + break; + } + + if (didx < hlen) { + DPRINTF(sc, "%s: dgram overlaps header\n", + DEVNAME(sc)); + break; + } + if (didx + dlen > len) { + DPRINTF(sc, "%s: dgram is too long\n", + DEVNAME(sc)); + break; + } + mlen = dlen + max_linkhdr + ETHER_ALIGN; + + m = m_gethdr(M_DONTWAIT, MT_DATA); + if (m == NULL) + break; + + if (mlen > MHLEN) { + m_clget(m, M_DONTWAIT, mlen); + if (!ISSET(m->m_flags, M_EXT)) { + m_freem(m); + break; + } + } + + m_align(m, mlen); + m->m_data += max_linkhdr + ETHER_ALIGN; + m->m_pkthdr.len = m->m_len = dlen; + + memcpy(mtod(m, caddr_t), buf + didx, dlen); + + ml_enqueue(ml, m); + + sgl++; + dlen -= sizeof(*sgl); + } while (dlen >= sizeof(*sgl)); + + return; + +error: + ifp->if_ierrors++; +} + +static void +cdcn_rxeof_ntb32(struct cdcn_softc *sc, struct mbuf_list *ml, + const void *buf, size_t len) +{ + struct ifnet *ifp = &sc->sc_if; + const struct usb_ncm32_hdr *nth; + const struct usb_ncm32_dpt *ndp; + const struct usb_ncm32_dp *sgl; + uint32_t sig; + size_t hlen, dlen, blen, off; + struct mbuf *m; + + if (len < sizeof(*nth)) { + DPRINTF(sc, "%s: short nth32\n", DEVNAME(sc)); + goto error; + } + + nth = (const struct usb_ncm32_hdr *)buf; + hlen = UGETW(nth->wHeaderLength); + if (hlen < sizeof(*nth)) { + DPRINTF(sc, "%s: nth32 wHeaderLength is too short\n", + DEVNAME(sc)); + goto error; + } + + blen = UGETDW(nth->dwBlockLength); + if (blen > len) { + DPRINTF(sc, "%s: nth32 wBlockLength is greater than " + "available data\n", DEVNAME(sc)); + goto error; + } + len = blen; + + off = UGETDW(nth->dwDptIndex); + if (off < hlen) { + DPRINTF(sc, "%s: nth32 wNdpIndex overlaps header\n", + DEVNAME(sc)); + goto error; + } + if (off + sizeof(*ndp) > len) { + DPRINTF(sc, "%s: ndp32 outside available data\n", + DEVNAME(sc)); + goto error; + } + + ndp = (const struct usb_ncm32_dpt *)(buf + off); + sig = UGETDW(ndp->dwSignature); + if ((sig & ~1U) != USB_NDP32_SIGNATURE) { + DPRINTF(sc, "%s: unexpected ndp32 signature\n", + DEVNAME(sc)); + goto error; + } + + dlen = UGETW(ndp->wLength); + if (dlen < sizeof(*ndp)) { + DPRINTF(sc, "%s: ndp32 wHeaderLength is too short\n", + DEVNAME(sc)); + goto error; + } + dlen -= sizeof(*ndp); + off += sizeof(*ndp); + + sgl = (const struct usb_ncm32_dp *)(ndp + 1); + + do { + uint32_t didx, dlen; + unsigned int mlen; + + off += sizeof(*sgl); + if (off > len) { + DPRINTF(sc, "%s: sgl falls out of the buffer\n", + DEVNAME(sc)); + break; + } + + didx = UGETDW(sgl->dwFrameIndex); + dlen = UGETDW(sgl->dwFrameLength); + if (didx == 0 || dlen == 0) { + /* all done */ + break; + } + + if (didx < hlen) { + DPRINTF(sc, "%s: dgram overlaps header\n", + DEVNAME(sc)); + break; + } + if (didx + dlen > len) { + DPRINTF(sc, "%s: dgram is too long\n", + DEVNAME(sc)); + break; + } + mlen = dlen + max_linkhdr + ETHER_ALIGN; + + m = m_gethdr(M_DONTWAIT, MT_DATA); + if (m == NULL) + break; + + if (mlen > MHLEN) { + m_clget(m, M_DONTWAIT, mlen); + if (!ISSET(m->m_flags, M_EXT)) { + m_freem(m); + break; + } + } + + m_align(m, mlen); + m->m_data += max_linkhdr + ETHER_ALIGN; + m->m_pkthdr.len = m->m_len = dlen; + + memcpy(mtod(m, caddr_t), buf + didx, dlen); + + ml_enqueue(ml, m); + + sgl++; + dlen -= sizeof(*sgl); + } while (dlen >= sizeof(*sgl)); + + return; + +error: + ifp->if_ierrors++; + +} + +void +cdcn_rxeof(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + struct cdcn_softc *sc = priv; + struct ifnet *ifp = &sc->sc_if; + int total_len = 0; + uint32_t *peek; + struct mbuf_list ml = MBUF_LIST_INITIALIZER(); + + if (usbd_is_dying(sc->sc_udev) || !ISSET(ifp->if_flags, IFF_RUNNING)) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + if (sc->sc_rxeof_errors == 0) + printf("%s: usb error on rx: %s\n", + DEVNAME(sc), usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe); + DELAY(sc->sc_rxeof_errors * 10000); + if (sc->sc_rxeof_errors++ > 10) { + printf("%s: too many errors, disabling\n", + DEVNAME(sc)); + usbd_deactivate(sc->sc_udev); + return; + } + goto done; + } + + sc->sc_rxeof_errors = 0; + +#if NKSTAT > 0 + mtx_enter(&sc->sc_kstat_mtx); + kstat_kv_u64(&sc->sc_stats.st_usb_rx)++; + mtx_leave(&sc->sc_kstat_mtx); +#endif + + usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); + if (total_len <= 1) + goto done; + +#if NBPFILTER > 0 + { + caddr_t sc_bpf = sc->sc_bpf; + if (sc_bpf) { + bpf_tap_hdr(sc_bpf, NULL, 0, sc->sc_rx_buf, total_len, + BPF_DIRECTION_IN); + } + } +#endif + + if (total_len < sizeof(*peek)) { + ifp->if_ierrors++; + goto done; + } + + switch (lemtoh32((uint32_t *)sc->sc_rx_buf)) { + case USB_NTH16_SIGNATURE: +#if NKSTAT > 0 + mtx_enter(&sc->sc_kstat_mtx); + kstat_kv_u64(&sc->sc_stats.st_usb_rx_ntb16)++; + mtx_leave(&sc->sc_kstat_mtx); +#endif + cdcn_rxeof_ntb16(sc, &ml, sc->sc_rx_buf, total_len); + break; + case USB_NTH32_SIGNATURE: +#if NKSTAT > 0 + mtx_enter(&sc->sc_kstat_mtx); + kstat_kv_u64(&sc->sc_stats.st_usb_rx_ntb32)++; + mtx_leave(&sc->sc_kstat_mtx); +#endif + cdcn_rxeof_ntb32(sc, &ml, sc->sc_rx_buf, total_len); + break; + default: + DPRINTF(sc, "%s: unexpected signature\n", DEVNAME(sc)); + ifp->if_ierrors++; + goto done; + } + + if_input(&sc->sc_if, &ml); + +done: + usbd_setup_xfer(sc->sc_rx_xfer, sc->sc_bulkin_pipe, sc, + sc->sc_rx_buf, sc->sc_rx_max, USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, cdcn_rxeof); + usbd_transfer(sc->sc_rx_xfer); +} + +static void +cdcn_txeof(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + struct cdcn_softc *sc = priv; + struct ifnet *ifp = &sc->sc_if; + usbd_status err; + + if (usbd_is_dying(sc->sc_udev)) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + return; + } + ifp->if_oerrors++; + printf("%s: usb error on tx: %s\n", DEVNAME(sc), + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulkout_pipe); + return; + } + + usbd_get_xfer_status(sc->sc_tx_xfer, NULL, NULL, NULL, &err); + if (err) + ifp->if_oerrors++; + + ifp->if_timer = 0; + ifq_restart(&ifp->if_snd); +} + +static void +cdcn_intr(struct usbd_xfer *xfer, void *addr, usbd_status status) +{ + struct cdcn_softc *sc = addr; + struct ifnet *ifp = &sc->sc_if; + struct usb_cdc_notification *buf = &sc->sc_intr_buf; + struct usb_cdc_connection_speed *speed; + u_int32_t count; + int link_state; + + if (status == USBD_CANCELLED) + return; + + if (status != USBD_NORMAL_COMPLETION) { + //DPRINTFN(2, ("cdcn_intr: status=%d\n", status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); + return; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + + if (buf->bmRequestType == UCDC_NOTIFICATION) { + switch (buf->bNotification) { + case UCDC_N_NETWORK_CONNECTION: + link_state = UGETW(buf->wValue) ? + LINK_STATE_FULL_DUPLEX : LINK_STATE_DOWN; + if (ifp->if_link_state != link_state) { + ifp->if_link_state = link_state; + if_link_state_change(ifp); + } + return; + case UCDC_NCM_NOTIF_CONNECTION_SPEED_CHANGE: + if (!ISSET(ifp->if_flags, IFF_DEBUG)) + break; + + speed = (struct usb_cdc_connection_speed *)&buf->data; + printf("%s: speed up=%u down=%u\n", DEVNAME(sc), + UGETDW(speed->dwUSBitRate), + UGETDW(speed->dwDSBitRate)); + return; + } + } + + DPRINTF(sc, "%s: %s " + "bmRequestType=0x%x wValue=0x%x wIndex=%u wLength=%u\n", + DEVNAME(sc), __func__, + buf->bmRequestType, UGETW(buf->wValue), + UGETW(buf->wIndex), UGETW(buf->wLength)); +} + +#if NKSTAT > 0 +struct cdcn_eth_kstat { + uint32_t r; + const char *name; + enum kstat_kv_type type; + enum kstat_kv_unit unit; +}; + +static const struct cdcn_eth_kstat cdcn_eth_kstats[] = { + [0] = { 0x01, "tx ok", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [1] = { 0x02, "rx ok", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [2] = { 0x03, "tx error", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [3] = { 0x04, "rx error", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [4] = { 0x05, "rx no buffers", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [5] = { 0x06, "tx unicast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_BYTES }, + [6] = { 0x07, "tx unicast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [7] = { 0x08, "tx multicast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_BYTES }, + [8] = { 0x09, "tx multicast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [9] = { 0x0a, "tx broadcast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_BYTES }, + [10] = { 0x0b, "tx broadcast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [11] = { 0x0c, "rx unicast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_BYTES }, + [12] = { 0x0d, "rx unicast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [13] = { 0x0e, "rx multicast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_BYTES }, + [14] = { 0x0f, "rx multicast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [15] = { 0x10, "rx broadcast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_BYTES }, + [16] = { 0x11, "rx broadcast", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [17] = { 0x12, "rx crc error", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [18] = { 0x13, "tx queue len", + KSTAT_KV_T_UINT32, KSTAT_KV_U_PACKETS }, + [19] = { 0x14, "rx align error", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [20] = { 0x15, "tx single col", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [21] = { 0x16, "tx multi col", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [22] = { 0x17, "tx deferred", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [23] = { 0x18, "tx max col", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [24] = { 0x19, "rx overrun", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [25] = { 0x1a, "tx underrun", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [26] = { 0x1b, "tx heartbeat", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_PACKETS }, + [27] = { 0x1c, "tx crs lost", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_NONE }, + [28] = { 0x1c, "tx late col", + KSTAT_KV_T_COUNTER32, KSTAT_KV_U_NONE }, +}; + +static int +cdcn_eth_kstat_read(struct kstat *ks) +{ + struct cdcn_softc *sc = ks->ks_softc; + struct usb_device_request req; + uint32_t stat; + struct kstat_kv *kvs = ks->ks_data; + + size_t i, n = 0; + + KERNEL_LOCK(); /* XXX usb? */ + for (i = 0; i < nitems(cdcn_eth_kstats); i++) { + const struct cdcn_eth_kstat *cek; + + if (!ISSET(sc->sc_eth_stats, 1U << i)) + continue; + + cek = &cdcn_eth_kstats[i]; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_NCM_GET_ETHERNET_STATISTIC; + USETW(req.wValue, cek->r); + req.wIndex[0] = sc->sc_ctl_ifaceno; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof(stat)); + + if (usbd_do_request_flags(sc->sc_udev, &req, &stat, + 0, NULL, USBD_DEFAULT_TIMEOUT) != 0) { + KERNEL_UNLOCK(); + return (EIO); + } + + kstat_kv_u32(&kvs[n++]) = lemtoh32(&stat); + } + KERNEL_UNLOCK(); + + getnanouptime(&ks->ks_updated); + + return (0); +} + +static void +cdcn_kstat_attach(struct cdcn_softc *sc) +{ + struct kstat *ks; + struct kstat_kv *kvs; + size_t i, n = 0; + + mtx_init(&sc->sc_kstat_mtx, IPL_USB); + + kstat_kv_init(&sc->sc_stats.st_usb_tx, "usb tx xfers", + KSTAT_KV_T_COUNTER64); + kstat_kv_init(&sc->sc_stats.st_usb_rx, "usb rx xfers", + KSTAT_KV_T_COUNTER64); + kstat_kv_init(&sc->sc_stats.st_usb_rx_ntb16, "usb rx ntb16", + KSTAT_KV_T_COUNTER64); + kstat_kv_init(&sc->sc_stats.st_usb_rx_ntb32, "usb rx ntb32", + KSTAT_KV_T_COUNTER64); + + ks = kstat_create(DEVNAME(sc), 0, "cdcn-stats", 0, KSTAT_T_KV, 0); + if (ks == NULL) + return; + + kstat_set_mutex(ks, &sc->sc_kstat_mtx); + ks->ks_data = &sc->sc_stats; + ks->ks_datalen = sizeof(sc->sc_stats); + + ks->ks_softc = sc; + sc->sc_kstat = ks; + + kstat_install(ks); + + if (sc->sc_eth_stats == 0) + return; + + for (i = 0; i < nitems(cdcn_eth_kstats); i++) { + if (!ISSET(sc->sc_eth_stats, 1U << i)) + continue; + n++; + } + + if (n == 0) + return; + + rw_init(&sc->sc_eth_kstat_lock, "cdcnkstat"); + + ks = kstat_create(DEVNAME(sc), 0, "cdc-eth-stats", 0, KSTAT_T_KV, 0); + if (ks == NULL) + return; + + kvs = mallocarray(n, sizeof(*kvs), M_DEVBUF, M_WAITOK); + + n = 0; /* start again */ + for (i = 0; i < nitems(cdcn_eth_kstats); i++) { + const struct cdcn_eth_kstat *cek; + + if (!ISSET(sc->sc_eth_stats, 1U << i)) + continue; + + cek = &cdcn_eth_kstats[i]; + kstat_kv_unit_init(&kvs[n++], cek->name, cek->type, cek->unit); + } + + kstat_set_wlock(ks, &sc->sc_eth_kstat_lock); + ks->ks_data = kvs; + ks->ks_datalen = sizeof(*kvs) * n; + ks->ks_read = cdcn_eth_kstat_read; + + ks->ks_softc = sc; + sc->sc_eth_kstat = ks; + + kstat_install(ks); +} + +static void +cdcn_kstat_detach(struct cdcn_softc *sc) +{ + kstat_destroy(sc->sc_kstat); +} +#endif Index: usb_subr.c =================================================================== RCS file: /cvs/src/sys/dev/usb/usb_subr.c,v diff -u -p -r1.164 usb_subr.c --- usb_subr.c 1 Mar 2025 14:43:03 -0000 1.164 +++ usb_subr.c 9 Jun 2025 00:42:38 -0000 @@ -59,7 +59,6 @@ extern int usbdebug; usbd_status usbd_set_config(struct usbd_device *, int); void usbd_devinfo(struct usbd_device *, int, char *, size_t); -char *usbd_get_string(struct usbd_device *, int, char *, size_t); int usbd_getnewaddr(struct usbd_bus *); int usbd_print(void *, const char *); void usbd_free_iface_data(struct usbd_device *, int); Index: usbdi_util.h =================================================================== RCS file: /cvs/src/sys/dev/usb/usbdi_util.h,v diff -u -p -r1.31 usbdi_util.h --- usbdi_util.h 24 Feb 2021 03:54:05 -0000 1.31 +++ usbdi_util.h 9 Jun 2025 00:42:38 -0000 @@ -53,6 +53,7 @@ usbd_status usbd_get_report_descriptor(s usbd_status usbd_get_config(struct usbd_device *dev, u_int8_t *conf); usbd_status usbd_get_string_desc(struct usbd_device *dev, int sindex, int langid,usb_string_descriptor_t *sdesc, int *sizep); +char *usbd_get_string(struct usbd_device *, int, char *, size_t); void usbd_delay_ms(struct usbd_device *, u_int);