Download raw body.
cdcn(4): a driver for USB CDC Network Control Model devices
i started working on this a couple of years ago in a failed attempt to
talk to some dodgy telco branded 4g dongle thing. i was bored on the
weekend and tried it on a realtek usb ethernet dongle that provides cdc
ncm as a fallback if you dont use the ure(4) interface, and it only
needed a few fixes to move packets. this makes me hate the dodgy 4g
dongle even more.
turns out some android phones can present themselves as cdc ncm if you
tell them to do tethering when plugged into a computer. i tried this
too, and it also mostly worked. i did hit an issue with fetching the mac
address from the device, but using usbd_get_string was able to work
around that. i can tether to my phone now, but it doesnt feel super
reliable.
i have run out of enthusiasm for hacking on this at the minute
though. im sending this out in case someone else wants to tinker
with it. i think the transmit path in particular needs some attention,
it doesnt really respect the params around alignment and modulus
and stuff that the device tells us. it should also be possible to
pack multiple packets into the one usb transfer too.
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 <wpaul@windriver.com>
+ * 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/sockio.h>
+#include <sys/mbuf.h>
+#include <sys/kernel.h>
+#include <sys/socket.h>
+#include <sys/device.h>
+#include <sys/kstat.h>
+
+#include <net/if.h>
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbdevs.h>
+#include <dev/usb/usbcdc.h>
+
+/*
+ * 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);
cdcn(4): a driver for USB CDC Network Control Model devices