Index | Thread | Search

From:
David Gwynne <david@gwynne.id.au>
Subject:
cdcn(4): a driver for USB CDC Network Control Model devices
To:
tech@openbsd.org
Date:
Mon, 9 Jun 2025 22:06:25 +1000

Download raw body.

Thread
  • David Gwynne:

    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, &param,
+            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);