Index | Thread | Search

From:
"Jan Schreiber" <jes@posteo.de>
Subject:
USB network control mode driver uncm(4)
To:
<tech@openbsd.org>
Date:
Mon, 05 Oct 2026 18:38:11 +0000

Download raw body.

Thread
Hi,

I noticed newer Android devices (since Android 6 if I'm not mistaken)
do not use urndis(4) for USB tethering any more.
They now use CDC NCM which is missing a driver in OpenBSD.

Below is my first go at it.
I moved some defines and structs out of mbim.h into usbcdc.h
to not include everything for mobile broadband. (No functional change
for umb(4)).

uncm(4) is based on umb(4).
I tested the diff with my Google Pixel 9 Pro running GrapheneOS on amd64.

If this is the right direction I'm happy to send a manpage in the next run.

Known limitations:

- Only the first NDP in an NTB is processed (wNextNdpIndex is not
  followed). Same as umb(4); my device sends one NDP per NTB. I have no
  device that sends more, so I left it for now.
- The NETWORK_CONNECTION notification is handled, but the Pixel never
  sends one, so the link state stays "unknown" with this device and that
  code path is untested. (Again I do not have a device that sends the
  notification.)
- One RX transfer in flight, like umb(4). I tested it and reached 320Mbps
  via fast.com - max speed at my location is 333Mbps and the phone was
  connected via wifi.

The mac address part took a while and looks a bit weird with the 0x0409
value, but it needs a valid language value that is recognized in the Linux
gadget stack (0x0409 is basically en-us).

If this diff is too big to review I can split it up into two parts,
the headers and the driver in itself (plus the manpage down the line).

Jan


diff --git sys/arch/amd64/conf/GENERIC sys/arch/amd64/conf/GENERIC
index 24dcce2008d..b669fbe8e2c 100644
--- sys/arch/amd64/conf/GENERIC
+++ sys/arch/amd64/conf/GENERIC
@@ -324,6 +324,7 @@ kue*	at uhub?		# Kawasaki KL5KUSB101B based Ethernet
 smsc*	at uhub?		# SMSC LAN95xx Ethernet
 cdce*	at uhub?		# CDC Ethernet
 urndis*	at uhub?		# Remote NDIS Ethernet
+uncm*   at uhub?        # Network Controll Model
 upl*	at uhub?		# Prolific PL2301/PL2302 host-to-host `network'
 ugl*	at uhub?		# Genesys Logic GL620USB-A host-to-host `network'
 udav*	at uhub?		# Davicom DM9601 based Ethernet
diff --git sys/dev/usb/files.usb sys/dev/usb/files.usb
index 88fff1b7c0f..84be1628818 100644
--- sys/dev/usb/files.usb
+++ sys/dev/usb/files.usb
@@ -260,6 +260,11 @@ device urndis: ether, ifnet, ifmedia
 attach urndis at uhub
 file	dev/usb/if_urndis.c		urndis
 
+# NCM Ethernet
+device uncm: ether, ifnet, ifmedia
+attach uncm at uhub
+file    dev/usb/if_uncm.c       uncm
+
 # Moschip MCS7x30 Ethernet
 device	mos: ether, ifnet, mii, ifmedia
 attach	mos at uhub
diff --git sys/dev/usb/if_uncm.c sys/dev/usb/if_uncm.c
new file mode 100644
index 00000000000..8be9fdf7589
--- /dev/null
+++ sys/dev/usb/if_uncm.c
@@ -0,0 +1,1226 @@
+/*	$OpenBSD$ */
+
+/*
+ * Copyright (c) 2016 genua mbH
+ * Copyright (c) 2026 Jan Schreiber
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Based on if_umb.c
+ */
+
+/*
+ * USB CDC NCM 1.0 specification:
+ * https://www.usb.org/sites/default/files/NCM10_012011.zip
+ */
+
+#include "bpfilter.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/sockio.h>
+#include <sys/mbuf.h>
+#include <sys/device.h>
+#include <sys/syslog.h>
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+#include <net/if.h>
+#include <net/if_var.h>
+#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>
+
+#ifdef UNCM_DEBUG
+#define DPRINTF(x...)							\
+		do { if (uncm_debug) log(LOG_DEBUG, x); } while (0)
+#define DPRINTFN(n, x...)						\
+		do { if (uncm_debug >= (n)) log(LOG_DEBUG, x); } while (0)
+int	 uncm_debug = 0;
+#else
+#define DPRINTF(x...)		do { } while (0)
+#define DPRINTFN(n, x...)	do { } while (0)
+#endif
+
+#define DEVNAM(sc)		(((struct uncm_softc *)(sc))->sc_dev.dv_xname)
+
+struct uncm_softc {
+	struct device		 sc_dev;
+	struct arpcom		 sc_ac;
+#define GET_IFP(sc)	(&(sc)->sc_ac.ac_if)
+	struct usbd_device	*sc_udev;
+
+	uint32_t		 sc_maxpktlen;
+	unsigned int		 sc_ncm_supported_formats;
+	int			 sc_ncm_format;
+
+	int			 sc_maxdgram;
+	int			 sc_align;
+	int			 sc_ndp_div;
+	int			 sc_ndp_remainder;
+
+	uint8_t			 sc_ctrl_ifaceno;
+	struct usbd_interface	*sc_ctrl_iface;
+	struct usbd_pipe	*sc_ctrl_pipe;
+	struct usb_cdc_notification sc_intr_msg;
+	struct usbd_interface	*sc_data_iface;
+
+	int			 sc_data_ifaceidx;
+
+	int			 sc_rx_ep;
+	struct usbd_xfer	*sc_rx_xfer;
+	void			*sc_rx_buf;
+	int			 sc_rx_bufsz;
+	struct usbd_pipe	*sc_rx_pipe;
+	unsigned int		 sc_rx_nerr;
+
+	int			 sc_tx_ep;
+	struct usbd_xfer	*sc_tx_xfer;
+	void			*sc_tx_buf;
+	int			 sc_tx_bufsz;
+	struct usbd_pipe	*sc_tx_pipe;
+	struct mbuf_list	 sc_tx_ml;
+	uint32_t		 sc_tx_seq;
+
+	struct usb_task		 sc_link_task;
+	int			 sc_link;
+	uint64_t		 sc_baudrate;
+};
+
+#define UNCM_RX_NTB_MAX		16384
+
+int		 uncm_match(struct device *, void *, void *);
+void		 uncm_attach(struct device *, struct device *, void *);
+int		 uncm_detach(struct device *, int);
+void		 uncm_ntb_setup(struct uncm_softc *);
+void		 uncm_ntb_setup_format(struct uncm_softc *);
+void		 uncm_ntb_setup_input_size(struct uncm_softc *);
+void		 uncm_get_eaddr(struct uncm_softc *, int);
+int		 uncm_alloc_xfers(struct uncm_softc *);
+void		 uncm_free_xfers(struct uncm_softc *);
+int		 uncm_ioctl(struct ifnet *, u_long, caddr_t);
+void		 uncm_init(struct uncm_softc *);
+void		 uncm_stop(struct uncm_softc *);
+void		 uncm_start(struct ifnet *);
+void		 uncm_watchdog(struct ifnet *);
+void		 uncm_rx(struct uncm_softc *);
+void		 uncm_rxeof(struct usbd_xfer *, void *, usbd_status);
+int		 uncm_encap(struct uncm_softc *, int);
+void		 uncm_txeof(struct usbd_xfer *, void *, usbd_status);
+void		 uncm_decap(struct uncm_softc *, struct usbd_xfer *);
+void		 uncm_intr(struct usbd_xfer *, void *, usbd_status);
+void		 uncm_link_task(void *);
+
+int uncm_xfer_tout = USBD_DEFAULT_TIMEOUT;
+
+struct cfdriver uncm_cd = {
+	NULL, "uncm", DV_IFNET
+};
+
+const struct cfattach uncm_ca = {
+	sizeof(struct uncm_softc),
+	uncm_match,
+	uncm_attach,
+	uncm_detach,
+	NULL,
+};
+
+int
+uncm_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_IFACESUBCLASS;
+
+	return UMATCH_NONE;
+}
+
+void
+uncm_attach(struct device *parent, struct device *self, void *aux)
+{
+	struct uncm_softc *sc = (struct uncm_softc *)self;
+	struct usb_attach_arg *uaa = aux;
+	usbd_status status;
+	struct usbd_desc_iter iter;
+	const usb_descriptor_t *desc;
+	struct usb_cdc_union_descriptor *ud;
+	struct usb_cdc_ethernet_descriptor *ed_eth;
+	struct usb_cdc_ncm_descriptor *nd;
+	usb_interface_descriptor_t *id;
+	usb_config_descriptor_t	*cd;
+	usb_endpoint_descriptor_t *ed;
+	usb_interface_assoc_descriptor_t *ad;
+	struct ifnet *ifp;
+	int	current_ifaceno = -1;
+	int	data_ifaceno = -1;
+	int	mac_idx = -1;
+	int	ctrl_ep;
+	int	altnum, i, s;
+	uint32_t maxpktlen;
+
+	sc->sc_udev = uaa->device;
+	sc->sc_ctrl_iface = uaa->iface;
+	sc->sc_ctrl_ifaceno = uaa->ifaceno;
+	sc->sc_maxpktlen = ETHER_MAX_LEN - ETHER_CRC_LEN;
+	sc->sc_link = LINK_STATE_UNKNOWN;
+	ml_init(&sc->sc_tx_ml);
+	usb_init_task(&sc->sc_link_task, uncm_link_task, sc,
+	    USB_TASK_TYPE_GENERIC);
+	usbd_desc_iter_init(sc->sc_udev, &iter);
+	while ((desc = usbd_desc_iter_next(&iter))) {
+		if (desc->bDescriptorType == UDESC_IFACE_ASSOC) {
+			ad = (usb_interface_assoc_descriptor_t *)desc;
+			if (ad->bFirstInterface == uaa->ifaceno &&
+			    ad->bInterfaceCount > 1) {
+				/* Fall back to the interface following control. */
+				data_ifaceno = uaa->ifaceno + 1;
+			}
+			continue;
+		}
+		if (desc->bDescriptorType == UDESC_INTERFACE) {
+			id = (usb_interface_descriptor_t *)desc;
+			current_ifaceno = id->bInterfaceNumber;
+			continue;
+		}
+		if (current_ifaceno != uaa->ifaceno)
+			continue;
+		if (desc->bDescriptorType != UDESC_CS_INTERFACE)
+			continue;
+		switch (desc->bDescriptorSubtype) {
+		case UDESCSUB_CDC_UNION:
+			if (desc->bLength < sizeof(*ud))
+				break;
+			ud = (struct usb_cdc_union_descriptor *)desc;
+			data_ifaceno = ud->bSlaveInterface[0];
+			break;
+		case UDESCSUB_CDC_ENF:
+			if (desc->bLength < sizeof(*ed_eth))
+				break;
+			ed_eth = (struct usb_cdc_ethernet_descriptor *)desc;
+			mac_idx = ed_eth->iMacAddress;
+			maxpktlen = UGETW(ed_eth->wMaxSegmentSize);
+			if (maxpktlen >= ETHER_MIN_LEN &&
+			    maxpktlen <= ETHER_MAX_LEN - ETHER_CRC_LEN)
+				sc->sc_maxpktlen = maxpktlen;
+			break;
+		case UDESCSUB_CDC_NCM:
+			if (desc->bLength < sizeof(*nd))
+				break;
+			nd = (struct usb_cdc_ncm_descriptor *)desc;
+			DPRINTFN(2, "%s: NCM version %x.%x, caps 0x%02x\n",
+			    DEVNAM(sc), UGETW(nd->bcdNcmVersion) >> 8,
+			    UGETW(nd->bcdNcmVersion) & 0xff,
+			    nd->bmNetworkCapabilities);
+			break;
+		default:
+			break;
+		}
+	}
+	if (data_ifaceno == -1) {
+		printf("%s: no data interface number\n", DEVNAM(sc));
+		goto fail;
+	}
+
+	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_ifaceno) {
+			sc->sc_data_iface = uaa->ifaces[i];
+			sc->sc_data_ifaceidx = i;
+			usbd_claim_iface(sc->sc_udev, i);
+		}
+	}
+	if (sc->sc_data_iface == NULL) {
+		printf("%s: no data interface found\n", DEVNAM(sc));
+		goto fail;
+	}
+
+	id = usbd_get_interface_descriptor(sc->sc_ctrl_iface);
+	ctrl_ep = -1;
+	for (i = 0; i < id->bNumEndpoints && ctrl_ep == -1; i++) {
+		ed = usbd_interface2endpoint_descriptor(sc->sc_ctrl_iface, i);
+		if (ed == NULL)
+			break;
+		if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT &&
+		    UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN)
+			ctrl_ep = ed->bEndpointAddress;
+	}
+	if (ctrl_ep == -1) {
+		printf("%s: missing interrupt endpoint\n", DEVNAM(sc));
+		goto fail;
+	}
+
+	cd = usbd_get_config_descriptor(sc->sc_udev);
+	altnum = usbd_get_no_alts(cd, data_ifaceno);
+	for (i = 0; i < altnum; i++) {
+		id = usbd_find_idesc(cd, sc->sc_data_ifaceidx, i);
+		if (id == NULL)
+			continue;
+		if (id->bInterfaceClass == UICLASS_CDC_DATA &&
+		    id->bNumEndpoints == 2)
+			break;
+	}
+	if (i == altnum || id == NULL) {
+		printf("%s: missing alt setting for interface #%d\n",
+		    DEVNAM(sc), data_ifaceno);
+		goto fail;
+	}
+	status = usbd_set_interface(sc->sc_data_iface, i);
+	if (status) {
+		printf("%s: select alt setting %d for interface #%d "
+		    "failed: %s\n", DEVNAM(sc), i, data_ifaceno,
+		    usbd_errstr(status));
+		goto fail;
+	}
+
+	id = usbd_get_interface_descriptor(sc->sc_data_iface);
+	sc->sc_rx_ep = sc->sc_tx_ep = -1;
+	for (i = 0; i < id->bNumEndpoints; i++) {
+		if ((ed = usbd_interface2endpoint_descriptor(sc->sc_data_iface,
+		    i)) == NULL)
+			break;
+		if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK &&
+		    UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN)
+			sc->sc_rx_ep = ed->bEndpointAddress;
+		else if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK &&
+		    UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT)
+			sc->sc_tx_ep = ed->bEndpointAddress;
+	}
+	if (sc->sc_rx_ep == -1 || sc->sc_tx_ep == -1) {
+		printf("%s: missing bulk endpoints\n", DEVNAM(sc));
+		goto fail;
+	}
+
+	DPRINTFN(2, "%s: ctrl-ifno#%d: ep-ctrl=%d, data-ifno#%d: ep-rx=%d, "
+	    "ep-tx=%d\n", DEVNAM(sc), sc->sc_ctrl_ifaceno,
+	    UE_GET_ADDR(ctrl_ep), data_ifaceno,
+	    UE_GET_ADDR(sc->sc_rx_ep), UE_GET_ADDR(sc->sc_tx_ep));
+
+	if (usbd_open_pipe_intr(sc->sc_ctrl_iface, ctrl_ep, USBD_SHORT_XFER_OK,
+	    &sc->sc_ctrl_pipe, sc, &sc->sc_intr_msg, sizeof(sc->sc_intr_msg),
+	    uncm_intr, USBD_DEFAULT_INTERVAL)) {
+		printf("%s: failed to open control pipe\n", DEVNAM(sc));
+		goto fail;
+	}
+
+	sc->sc_ncm_format = NCM_FORMAT_NTB16;
+	uncm_ntb_setup(sc);
+	uncm_ntb_setup_format(sc);
+	if (sc->sc_ncm_supported_formats == 0)
+		goto fail;
+	uncm_ntb_setup_input_size(sc);
+	DPRINTFN(2, "%s: rx/tx size %d/%d\n", DEVNAM(sc),
+	    sc->sc_rx_bufsz, sc->sc_tx_bufsz);
+
+	ifp = GET_IFP(sc);
+	uncm_get_eaddr(sc, mac_idx);
+
+	s = splnet();
+	printf("%s: address %s\n", DEVNAM(sc),
+	    ether_sprintf(sc->sc_ac.ac_enaddr));
+
+	ifp->if_softc = sc;
+	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+	ifp->if_ioctl = uncm_ioctl;
+	ifp->if_start = uncm_start;
+	ifp->if_watchdog = uncm_watchdog;
+	strlcpy(ifp->if_xname, DEVNAM(sc), IFNAMSIZ);
+	ifp->if_hardmtu = sc->sc_maxpktlen - ETHER_HDR_LEN;
+
+	if_attach(ifp);
+	ether_ifattach(ifp);
+	splx(s);
+	return;
+
+fail:
+	usbd_deactivate(sc->sc_udev);
+	return;
+}
+
+int
+uncm_detach(struct device *self, int flags)
+{
+	struct uncm_softc *sc = (struct uncm_softc *)self;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 s;
+
+	s = splnet();
+	if (ifp->if_flags & IFF_RUNNING)
+		uncm_stop(sc);
+
+	usb_rem_wait_task(sc->sc_udev, &sc->sc_link_task);
+	if (sc->sc_ctrl_pipe) {
+		usbd_close_pipe(sc->sc_ctrl_pipe);
+		sc->sc_ctrl_pipe = NULL;
+	}
+	if (ifp->if_softc != NULL) {
+		ether_ifdetach(ifp);
+		if_detach(ifp);
+	}
+	splx(s);
+	return 0;
+}
+
+static int
+uncm_hexval(int c)
+{
+	if (c >= '0' && c <= '9')
+		return c - '0';
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 10;
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 10;
+	return -1;
+}
+
+void
+uncm_get_eaddr(struct uncm_softc *sc, int idx)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+	usb_string_descriptor_t us;
+	int	i, len, hi, lo;
+
+	if (idx <= 0)
+		goto fake;
+
+	if (usbd_get_string_desc(sc->sc_udev, idx, USB_LANGID_EN_US, &us,
+	    &len) || len < 2 + ETHER_ADDR_LEN * 2 * 2)
+		goto fake;
+
+	for (i = 0; i < ETHER_ADDR_LEN; i++) {
+		hi = uncm_hexval(UGETW(us.bString[2 * i]));
+		lo = uncm_hexval(UGETW(us.bString[2 * i + 1]));
+		if (hi < 0 || lo < 0)
+			goto fake;
+		sc->sc_ac.ac_enaddr[i] = (hi << 4) | lo;
+	}
+	if (ETHER_IS_MULTICAST(sc->sc_ac.ac_enaddr) ||
+	    ETHER_IS_ANYADDR(sc->sc_ac.ac_enaddr))
+		goto fake;
+	return;
+
+fake:
+	ether_fakeaddr(ifp);
+}
+
+void
+uncm_ntb_setup(struct uncm_softc *sc)
+{
+	usb_device_request_t req;
+	struct ncm_ntb_parameters np;
+
+	req.bmRequestType = UT_READ_CLASS_INTERFACE;
+	req.bRequest = NCM_GET_NTB_PARAMETERS;
+	USETW(req.wValue, 0);
+	USETW(req.wIndex, sc->sc_ctrl_ifaceno);
+	USETW(req.wLength, sizeof(np));
+	if (usbd_do_request(sc->sc_udev, &req, &np) == USBD_NORMAL_COMPLETION &&
+	    UGETW(np.wLength) == sizeof(np)) {
+		sc->sc_rx_bufsz = MIN(UGETDW(np.dwNtbInMaxSize), UINT16_MAX);
+		sc->sc_tx_bufsz = MIN(UGETDW(np.dwNtbOutMaxSize), UINT16_MAX);
+		sc->sc_maxdgram = UGETW(np.wNtbOutMaxDatagrams);
+		sc->sc_align = UGETW(np.wNdpOutAlignment);
+		sc->sc_ndp_div = UGETW(np.wNdpOutDivisor);
+		sc->sc_ndp_remainder = UGETW(np.wNdpOutPayloadRemainder);
+
+		if (!powerof2(sc->sc_align) || sc->sc_align == 0 ||
+		    sc->sc_align >= sc->sc_tx_bufsz)
+			sc->sc_align = sizeof(uint32_t);
+		if (!powerof2(sc->sc_ndp_div) || sc->sc_ndp_div == 0 ||
+		    sc->sc_ndp_div >= sc->sc_tx_bufsz)
+			sc->sc_ndp_div = sizeof(uint32_t);
+		if (sc->sc_ndp_remainder >= sc->sc_ndp_div)
+			sc->sc_ndp_remainder = 0;
+		DPRINTF("%s: NCM align=%d div=%d rem=%d in=%u out=%u "
+		    "maxdgram=%d\n", DEVNAM(sc), sc->sc_align, sc->sc_ndp_div,
+		    sc->sc_ndp_remainder, UGETDW(np.dwNtbInMaxSize),
+		    UGETDW(np.dwNtbOutMaxSize), sc->sc_maxdgram);
+		sc->sc_ncm_supported_formats = UGETW(np.bmNtbFormatsSupported);
+	} else {
+		sc->sc_rx_bufsz = sc->sc_tx_bufsz = 8 * 1024;
+		sc->sc_maxdgram = 0;
+		sc->sc_align = sc->sc_ndp_div = sizeof(uint32_t);
+		sc->sc_ndp_remainder = 0;
+		DPRINTF("%s: align=default div=default rem=default\n",
+		    DEVNAM(sc));
+		sc->sc_ncm_supported_formats = NCM_FORMAT_NTB16_MASK;
+	}
+
+	if (sc->sc_rx_bufsz < sizeof(struct ncm_header16) +
+	    sizeof(struct ncm_pointer16) +
+	    sizeof(struct ncm_pointer16_dgram) ||
+	    sc->sc_tx_bufsz < sizeof(struct ncm_header16) +
+	    sizeof(struct ncm_pointer16) +
+	    sizeof(struct ncm_pointer16_dgram) ||
+	    ((sc->sc_ncm_supported_formats & NCM_FORMAT_NTB32_MASK) &&
+	    (sc->sc_rx_bufsz < sizeof(struct ncm_header32) +
+	    sizeof(struct ncm_pointer32) +
+	    sizeof(struct ncm_pointer32_dgram) ||
+	    sc->sc_tx_bufsz < sizeof(struct ncm_header32) +
+	    sizeof(struct ncm_pointer32) +
+	    sizeof(struct ncm_pointer32_dgram)))) {
+		DPRINTF("%s: invalid NTB size %d/%d\n", DEVNAM(sc),
+		    sc->sc_rx_bufsz, sc->sc_tx_bufsz);
+		sc->sc_ncm_supported_formats = 0;
+	}
+}
+
+void
+uncm_ntb_setup_format(struct uncm_softc *sc)
+{
+	usb_device_request_t req;
+	uWord wFmt;
+	uint16_t fmt;
+
+	if (sc->sc_ncm_supported_formats == 0)
+		goto fail;
+
+	if (sc->sc_ncm_supported_formats == NCM_FORMAT_NTB16_MASK) {
+		DPRINTF("%s: only NTB16 format supported\n", DEVNAM(sc));
+		sc->sc_ncm_format = NCM_FORMAT_NTB16;
+		return;
+	}
+
+	req.bmRequestType = UT_READ_CLASS_INTERFACE;
+	req.bRequest = NCM_GET_NTB_FORMAT;
+	USETW(req.wValue, 0);
+	USETW(req.wIndex, sc->sc_ctrl_ifaceno);
+	USETW(req.wLength, sizeof(wFmt));
+	if (usbd_do_request(sc->sc_udev, &req, wFmt) != USBD_NORMAL_COMPLETION)
+		goto fail;
+	fmt = UGETW(wFmt);
+	if ((sc->sc_ncm_supported_formats & (1UL << fmt)) == 0)
+		goto fail;
+	if (fmt != NCM_FORMAT_NTB16 && fmt != NCM_FORMAT_NTB32)
+		goto fail;
+	sc->sc_ncm_format = fmt;
+
+	DPRINTF("%s: using NCM format %d, supported 0x%x\n",
+	    DEVNAM(sc), sc->sc_ncm_format, sc->sc_ncm_supported_formats);
+	return;
+
+fail:
+	DPRINTF("%s: cannot set up NCM format\n", DEVNAM(sc));
+	sc->sc_ncm_supported_formats = 0;
+}
+
+void
+uncm_ntb_setup_input_size(struct uncm_softc *sc)
+{
+	usb_device_request_t req;
+	uDWord dwSize;
+
+	if (sc->sc_rx_bufsz <= UNCM_RX_NTB_MAX)
+		return;
+
+	USETDW(dwSize, UNCM_RX_NTB_MAX);
+	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+	req.bRequest = NCM_SET_NTB_INPUT_SIZE;
+	USETW(req.wValue, 0);
+	USETW(req.wIndex, sc->sc_ctrl_ifaceno);
+	USETW(req.wLength, sizeof(dwSize));
+	if (usbd_do_request(sc->sc_udev, &req, dwSize) ==
+	    USBD_NORMAL_COMPLETION)
+		sc->sc_rx_bufsz = UNCM_RX_NTB_MAX;
+	else
+		DPRINTF("%s: SET_NTB_INPUT_SIZE refused, keeping %d\n",
+		    DEVNAM(sc), sc->sc_rx_bufsz);
+}
+
+int
+uncm_alloc_xfers(struct uncm_softc *sc)
+{
+	if (sc->sc_rx_xfer == NULL) {
+		if ((sc->sc_rx_xfer = usbd_alloc_xfer(sc->sc_udev)) != NULL)
+			sc->sc_rx_buf = usbd_alloc_buffer(sc->sc_rx_xfer,
+			    sc->sc_rx_bufsz);
+	}
+
+    if (sc->sc_tx_xfer == NULL) {
+		if ((sc->sc_tx_xfer = usbd_alloc_xfer(sc->sc_udev)) != NULL)
+			sc->sc_tx_buf = usbd_alloc_buffer(sc->sc_tx_xfer,
+			    sc->sc_tx_bufsz);
+    }
+
+	return (sc->sc_rx_buf != NULL && sc->sc_tx_buf != NULL);
+}
+
+void
+uncm_free_xfers(struct uncm_softc *sc)
+{
+	if (sc->sc_rx_xfer != NULL) {
+		usbd_free_xfer(sc->sc_rx_xfer);
+		sc->sc_rx_xfer = NULL;
+		sc->sc_rx_buf = NULL;
+	}
+
+    if (sc->sc_tx_xfer != NULL) {
+		usbd_free_xfer(sc->sc_tx_xfer);
+		sc->sc_tx_xfer = NULL;
+		sc->sc_tx_buf = NULL;
+	}
+
+    ml_purge(&sc->sc_tx_ml);
+}
+
+void
+uncm_init(struct uncm_softc *sc)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+
+	splassert(IPL_NET);
+
+	if (ifp->if_flags & IFF_RUNNING)
+		return;
+
+	sc->sc_tx_seq = 0;
+	if (!uncm_alloc_xfers(sc)) {
+		printf("%s: allocation of xfers failed\n", DEVNAM(sc));
+		uncm_free_xfers(sc);
+		return;
+	}
+	if (usbd_open_pipe(sc->sc_data_iface, sc->sc_rx_ep,
+	    USBD_EXCLUSIVE_USE, &sc->sc_rx_pipe)) {
+		printf("%s: open rx pipe failed\n", DEVNAM(sc));
+		goto fail;
+	}
+	if (usbd_open_pipe(sc->sc_data_iface, sc->sc_tx_ep,
+	    USBD_EXCLUSIVE_USE, &sc->sc_tx_pipe)) {
+		printf("%s: open tx pipe failed\n", DEVNAM(sc));
+		goto fail;
+	}
+
+	ifp->if_flags |= IFF_RUNNING;
+	ifq_clr_oactive(&ifp->if_snd);
+	uncm_rx(sc);
+	return;
+
+fail:
+	uncm_stop(sc);
+}
+
+void
+uncm_stop(struct uncm_softc *sc)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+
+	splassert(IPL_NET);
+
+	ifp->if_flags &= ~IFF_RUNNING;
+	ifq_clr_oactive(&ifp->if_snd);
+	ifp->if_timer = 0;
+	if (sc->sc_rx_pipe) {
+		usbd_close_pipe(sc->sc_rx_pipe);
+		sc->sc_rx_pipe = NULL;
+	}
+	if (sc->sc_tx_pipe) {
+		usbd_close_pipe(sc->sc_tx_pipe);
+		sc->sc_tx_pipe = NULL;
+	}
+	uncm_free_xfers(sc);
+}
+
+int
+uncm_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+	struct uncm_softc *sc = ifp->if_softc;
+	int	 s, error = 0;
+
+	if (usbd_is_dying(sc->sc_udev))
+		return ENXIO;
+
+	s = splnet();
+	switch (cmd) {
+	case SIOCSIFADDR:
+		ifp->if_flags |= IFF_UP;
+		if (!(ifp->if_flags & IFF_RUNNING))
+			uncm_init(sc);
+		break;
+
+	case SIOCSIFFLAGS:
+		if (ifp->if_flags & IFF_UP) {
+			if (ifp->if_flags & IFF_RUNNING)
+				error = ENETRESET;
+			else
+				uncm_init(sc);
+		} else {
+			if (ifp->if_flags & IFF_RUNNING)
+				uncm_stop(sc);
+		}
+		break;
+
+	default:
+		error = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
+		break;
+	}
+
+	if (error == ENETRESET)
+		error = 0;
+
+	splx(s);
+	return error;
+}
+
+static inline int
+uncm_align(size_t bufsz, int offs, int alignment, int remainder)
+{
+	size_t	 m = alignment - 1;
+	int	 newoff;
+
+	newoff = (((size_t)offs + m) & ~m) - alignment + remainder;
+	if (newoff < offs)
+		newoff += alignment;
+	if (newoff > bufsz)
+		newoff = bufsz;
+	return newoff - offs;
+}
+
+static inline int
+uncm_padding(void *buf, size_t bufsz, int offs, int alignment, int remainder)
+{
+	int	 nb;
+
+	nb = uncm_align(bufsz, offs, alignment, remainder);
+	if (nb > 0)
+		memset(buf + offs, 0, nb);
+	return nb;
+}
+
+void
+uncm_start(struct ifnet *ifp)
+{
+	struct uncm_softc *sc = ifp->if_softc;
+	struct mbuf *m = NULL;
+	int	 ndgram = 0;
+	int	 offs, len, mlen;
+	int	 maxoverhead;
+
+	if (usbd_is_dying(sc->sc_udev) ||
+	    !(ifp->if_flags & IFF_RUNNING) ||
+	    ifq_is_oactive(&ifp->if_snd))
+		return;
+
+	KASSERT(ml_empty(&sc->sc_tx_ml));
+	KASSERT(sc->sc_ncm_format == NCM_FORMAT_NTB16 ||
+	    sc->sc_ncm_format == NCM_FORMAT_NTB32);
+
+	switch (sc->sc_ncm_format) {
+	case NCM_FORMAT_NTB16:
+		offs = sizeof(struct ncm_header16);
+		offs += uncm_align(sc->sc_tx_bufsz, offs, sc->sc_align, 0);
+		offs += sizeof(struct ncm_pointer16);
+		maxoverhead = sizeof(struct ncm_pointer16_dgram);
+		break;
+	case NCM_FORMAT_NTB32:
+		offs = sizeof(struct ncm_header32);
+		offs += uncm_align(sc->sc_tx_bufsz, offs, sc->sc_align, 0);
+		offs += sizeof(struct ncm_pointer32);
+		maxoverhead = sizeof(struct ncm_pointer32_dgram);
+		break;
+	}
+
+	maxoverhead += sc->sc_ndp_div - 1;
+
+	len = 0;
+	while (1) {
+		m = ifq_deq_begin(&ifp->if_snd);
+		if (m == NULL)
+			break;
+
+		mlen = maxoverhead +  m->m_pkthdr.len;
+		if ((sc->sc_maxdgram != 0 && ndgram >= sc->sc_maxdgram) ||
+		    (offs + len + mlen > sc->sc_tx_bufsz)) {
+			ifq_deq_rollback(&ifp->if_snd, m);
+			break;
+		}
+		ifq_deq_commit(&ifp->if_snd, m);
+
+		ndgram++;
+		len += mlen;
+		ml_enqueue(&sc->sc_tx_ml, m);
+
+#if NBPFILTER > 0
+		if (ifp->if_bpf)
+			bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
+#endif
+	}
+	if (ml_empty(&sc->sc_tx_ml))
+		return;
+	if (uncm_encap(sc, ndgram)) {
+		ifq_set_oactive(&ifp->if_snd);
+		ifp->if_timer = (2 * uncm_xfer_tout) / 1000;
+	}
+}
+
+void
+uncm_watchdog(struct ifnet *ifp)
+{
+	struct uncm_softc *sc = ifp->if_softc;
+
+	if (usbd_is_dying(sc->sc_udev))
+		return;
+
+	ifp->if_oerrors++;
+	printf("%s: watchdog timeout\n", DEVNAM(sc));
+	usbd_abort_pipe(sc->sc_tx_pipe);
+}
+
+void
+uncm_rx(struct uncm_softc *sc)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+	usbd_status err;
+
+	usbd_setup_xfer(sc->sc_rx_xfer, sc->sc_rx_pipe, sc, sc->sc_rx_buf,
+	    sc->sc_rx_bufsz, USBD_SHORT_XFER_OK | USBD_NO_COPY,
+	    USBD_NO_TIMEOUT, uncm_rxeof);
+	err = usbd_transfer(sc->sc_rx_xfer);
+	if (err != USBD_IN_PROGRESS) {
+		ifp->if_ierrors++;
+		DPRINTF("%s: start rx error: %s\n", DEVNAM(sc),
+		    usbd_errstr(err));
+	}
+}
+
+void
+uncm_rxeof(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+	struct uncm_softc *sc = priv;
+	struct ifnet *ifp = GET_IFP(sc);
+
+	if (usbd_is_dying(sc->sc_udev) || !(ifp->if_flags & IFF_RUNNING))
+		return;
+
+	if (status != USBD_NORMAL_COMPLETION) {
+		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
+			return;
+		DPRINTF("%s: rx error: %s\n", DEVNAM(sc), usbd_errstr(status));
+		if (status == USBD_STALLED)
+			usbd_clear_endpoint_stall_async(sc->sc_rx_pipe);
+		if (++sc->sc_rx_nerr > 100) {
+			log(LOG_ERR, "%s: too many rx errors, disabling\n",
+			    DEVNAM(sc));
+			usbd_deactivate(sc->sc_udev);
+		}
+	} else {
+		sc->sc_rx_nerr = 0;
+		uncm_decap(sc, xfer);
+	}
+
+	uncm_rx(sc);
+}
+
+int
+uncm_encap(struct uncm_softc *sc, int ndgram)
+{
+	struct ncm_header16 *hdr16 = NULL;
+	struct ncm_header32 *hdr32 = NULL;
+	struct ncm_pointer16 *ptr16 = NULL;
+	struct ncm_pointer32 *ptr32 = NULL;
+	struct ncm_pointer16_dgram *dgram16 = NULL;
+	struct ncm_pointer32_dgram *dgram32 = NULL;
+	int	 offs = 0, plen = 0;
+	int	 dgoffs = 0, poffs;
+	struct mbuf *m;
+	usbd_status  err;
+
+	KASSERT(sc->sc_ncm_format == NCM_FORMAT_NTB16 ||
+	    sc->sc_ncm_format == NCM_FORMAT_NTB32);
+
+	switch (sc->sc_ncm_format) {
+	case NCM_FORMAT_NTB16:
+		hdr16 = sc->sc_tx_buf;
+		USETDW(hdr16->dwSignature, NCM_HDR16_SIG);
+		USETW(hdr16->wHeaderLength, sizeof(*hdr16));
+		USETW(hdr16->wSequence, sc->sc_tx_seq);
+		USETW(hdr16->wBlockLength, 0);
+		offs = sizeof(*hdr16);
+		break;
+	case NCM_FORMAT_NTB32:
+		hdr32 = sc->sc_tx_buf;
+		USETDW(hdr32->dwSignature, NCM_HDR32_SIG);
+		USETW(hdr32->wHeaderLength, sizeof(*hdr32));
+		USETW(hdr32->wSequence, sc->sc_tx_seq);
+		USETDW(hdr32->dwBlockLength, 0);
+		offs = sizeof(*hdr32);
+		break;
+	}
+	offs += uncm_padding(sc->sc_tx_buf, sc->sc_tx_bufsz, offs,
+	    sc->sc_align, 0);
+	poffs = offs;
+
+	switch (sc->sc_ncm_format) {
+	case NCM_FORMAT_NTB16:
+		USETW(hdr16->wNdpIndex, poffs);
+		ptr16 = (struct ncm_pointer16 *)(sc->sc_tx_buf + poffs);
+		plen = sizeof(*ptr16) + ndgram * sizeof(*dgram16);
+		USETDW(ptr16->dwSignature, NCM_NDP16_SIG_NOCRC);
+		USETW(ptr16->wLength, plen);
+		USETW(ptr16->wNextNdpIndex, 0);
+		dgram16 = ptr16->dgram;
+		break;
+	case NCM_FORMAT_NTB32:
+		USETDW(hdr32->dwNdpIndex, poffs);
+		ptr32 = (struct ncm_pointer32 *)(sc->sc_tx_buf + poffs);
+		plen = sizeof(*ptr32) + ndgram * sizeof(*dgram32);
+		USETDW(ptr32->dwSignature, NCM_NDP32_SIG_NOCRC);
+		USETW(ptr32->wLength, plen);
+		USETW(ptr32->wReserved6, 0);
+		USETDW(ptr32->dwNextNdpIndex, 0);
+		USETDW(ptr32->dwReserved12, 0);
+		dgram32 = ptr32->dgram;
+		break;
+	}
+	dgoffs = offs + plen;
+
+	sc->sc_tx_seq++;
+	while ((m = ml_dequeue(&sc->sc_tx_ml)) != NULL) {
+		dgoffs += uncm_padding(sc->sc_tx_buf, sc->sc_tx_bufsz, dgoffs,
+		    sc->sc_ndp_div, sc->sc_ndp_remainder);
+		switch (sc->sc_ncm_format) {
+		case NCM_FORMAT_NTB16:
+			USETW(dgram16->wDatagramIndex, dgoffs);
+			USETW(dgram16->wDatagramLen, m->m_pkthdr.len);
+			dgram16++;
+			break;
+		case NCM_FORMAT_NTB32:
+			USETDW(dgram32->dwDatagramIndex, dgoffs);
+			USETDW(dgram32->dwDatagramLen, m->m_pkthdr.len);
+			dgram32++;
+			break;
+		}
+		m_copydata(m, 0, m->m_pkthdr.len, sc->sc_tx_buf + dgoffs);
+		dgoffs += m->m_pkthdr.len;
+		m_freem(m);
+	}
+	offs = dgoffs;
+
+	switch (sc->sc_ncm_format) {
+	case NCM_FORMAT_NTB16:
+		USETW(dgram16->wDatagramIndex, 0);
+		USETW(dgram16->wDatagramLen, 0);
+		USETW(hdr16->wBlockLength, offs);
+		KASSERT(dgram16 - ptr16->dgram == ndgram);
+		break;
+	case NCM_FORMAT_NTB32:
+		USETDW(dgram32->dwDatagramIndex, 0);
+		USETDW(dgram32->dwDatagramLen, 0);
+		USETDW(hdr32->dwBlockLength, offs);
+		KASSERT(dgram32 - ptr32->dgram == ndgram);
+		break;
+	}
+
+	DPRINTFN(3, "%s: encap %d bytes, %d dgrams\n", DEVNAM(sc), offs,
+	    ndgram);
+	KASSERT(offs <= sc->sc_tx_bufsz);
+
+	usbd_setup_xfer(sc->sc_tx_xfer, sc->sc_tx_pipe, sc, sc->sc_tx_buf, offs,
+	    USBD_FORCE_SHORT_XFER | USBD_NO_COPY, uncm_xfer_tout, uncm_txeof);
+	err = usbd_transfer(sc->sc_tx_xfer);
+	if (err != USBD_IN_PROGRESS) {
+		GET_IFP(sc)->if_oerrors++;
+		DPRINTF("%s: start tx error: %s\n", DEVNAM(sc),
+		    usbd_errstr(err));
+		return 0;
+	}
+	return 1;
+}
+
+void
+uncm_txeof(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+	struct uncm_softc *sc = priv;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 s;
+
+	s = splnet();
+	ifq_clr_oactive(&ifp->if_snd);
+	ifp->if_timer = 0;
+
+	if (status != USBD_NORMAL_COMPLETION) {
+		if (status != USBD_NOT_STARTED && status != USBD_CANCELLED) {
+			ifp->if_oerrors++;
+			DPRINTF("%s: tx error: %s\n", DEVNAM(sc),
+			    usbd_errstr(status));
+			if (status == USBD_STALLED)
+				usbd_clear_endpoint_stall_async(sc->sc_tx_pipe);
+		}
+	}
+	if (ifq_empty(&ifp->if_snd) == 0)
+		uncm_start(ifp);
+
+	splx(s);
+}
+
+/*
+ * XXX Only the first NDP is processed; wNextNdpIndex not followed
+ * (same as umb(4)).
+ */
+void
+uncm_decap(struct uncm_softc *sc, struct usbd_xfer *xfer)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+	int	s;
+	void *buf;
+	uint32_t len;
+	char *dp;
+	struct ncm_header16 *hdr16;
+	struct ncm_header32 *hdr32;
+	struct ncm_pointer16 *ptr16;
+	struct ncm_pointer16_dgram *dgram16;
+	struct ncm_pointer32_dgram *dgram32;
+	uint32_t hsig, psig;
+	uint32_t ptrlen, dgentryoff;
+	uint64_t blen, ptroff, doff, dlen;
+	struct mbuf_list ml = MBUF_LIST_INITIALIZER();
+	struct mbuf *m;
+
+	usbd_get_xfer_status(xfer, NULL, &buf, &len, NULL);
+	DPRINTFN(4, "%s: recv %d bytes\n", DEVNAM(sc), len);
+	s = splnet();
+	if (len < sizeof(*hdr16))
+		goto toosmall;
+
+	hdr16 = (struct ncm_header16 *)buf;
+	hsig = UGETDW(hdr16->dwSignature);
+
+	switch (hsig) {
+	case NCM_HDR16_SIG:
+		blen = UGETW(hdr16->wBlockLength);
+		ptroff = UGETW(hdr16->wNdpIndex);
+		if (UGETW(hdr16->wHeaderLength) != sizeof(*hdr16)) {
+			DPRINTF("%s: bad header len %d for NTH16 (exp %zu)\n",
+			    DEVNAM(sc), UGETW(hdr16->wHeaderLength),
+			    sizeof(*hdr16));
+			goto fail;
+		}
+		break;
+	case NCM_HDR32_SIG:
+		if (len < sizeof(*hdr32))
+			goto toosmall;
+		hdr32 = (struct ncm_header32 *)hdr16;
+		blen = UGETDW(hdr32->dwBlockLength);
+		ptroff = UGETDW(hdr32->dwNdpIndex);
+		if (UGETW(hdr32->wHeaderLength) != sizeof(*hdr32)) {
+			DPRINTF("%s: bad header len %d for NTH32 (exp %zu)\n",
+			    DEVNAM(sc), UGETW(hdr32->wHeaderLength),
+			    sizeof(*hdr32));
+			goto fail;
+		}
+		break;
+	default:
+		DPRINTF("%s: unsupported NCM header signature (0x%08x)\n",
+		    DEVNAM(sc), hsig);
+		goto fail;
+	}
+	if (blen != 0 && len < blen) {
+		DPRINTF("%s: bad NTB len (%llu) for %d bytes of data\n",
+		    DEVNAM(sc), blen, len);
+		goto fail;
+	}
+
+	if ((uint64_t)len < ptroff + sizeof(*ptr16))
+		goto toosmall;
+	ptr16 = (struct ncm_pointer16 *)(buf + ptroff);
+	psig = UGETDW(ptr16->dwSignature);
+	ptrlen = UGETW(ptr16->wLength);
+	if ((uint64_t)len < (uint64_t)ptrlen + ptroff)
+		goto toosmall;
+
+	switch (psig) {
+	case NCM_NDP16_SIG_NOCRC:
+		if (hsig != NCM_HDR16_SIG)
+			goto badsig;
+		dgentryoff = offsetof(struct ncm_pointer16, dgram);
+		if (ptrlen < dgentryoff + sizeof(*dgram16))
+			goto toosmall;
+		break;
+	case NCM_NDP32_SIG_NOCRC:
+		if (hsig != NCM_HDR32_SIG)
+			goto badsig;
+		dgentryoff = offsetof(struct ncm_pointer32, dgram);
+		if (ptrlen < dgentryoff + sizeof(*dgram32))
+			goto toosmall;
+		break;
+	case NCM_NDP16_SIG_CRC:
+	case NCM_NDP32_SIG_CRC:
+	default:
+	badsig:
+		DPRINTF("%s: unsupported NCM pointer signature (0x%08x)\n",
+		    DEVNAM(sc), psig);
+		goto fail;
+	}
+
+	while (dgentryoff < ptrlen) {
+		switch (hsig) {
+		case NCM_HDR16_SIG:
+			if (ptrlen - dgentryoff < sizeof(*dgram16))
+				goto done;
+			dgram16 = (struct ncm_pointer16_dgram *)
+			    (buf + ptroff + dgentryoff);
+			dgentryoff += sizeof(*dgram16);
+			dlen = UGETW(dgram16->wDatagramLen);
+			doff = UGETW(dgram16->wDatagramIndex);
+			break;
+		case NCM_HDR32_SIG:
+			if (ptrlen - dgentryoff < sizeof(*dgram32))
+				goto done;
+			dgram32 = (struct ncm_pointer32_dgram *)
+			    (buf + ptroff + dgentryoff);
+			dgentryoff += sizeof(*dgram32);
+			dlen = UGETDW(dgram32->dwDatagramLen);
+			doff = UGETDW(dgram32->dwDatagramIndex);
+			break;
+		default:
+			ifp->if_ierrors++;
+			goto done;
+		}
+
+		if (dlen == 0 || doff == 0)
+			break;
+		if ((uint64_t)len < dlen + doff ||
+		    (blen != 0 && blen < dlen + doff)) {
+			DPRINTF("%s: datagram too large (%llu @ off %llu)\n",
+			    DEVNAM(sc), dlen, doff);
+			ifp->if_ierrors++;
+			continue;
+		}
+		if (dlen < ETHER_HDR_LEN || dlen > sc->sc_maxpktlen) {
+			ifp->if_ierrors++;
+			continue;
+		}
+
+		dp = buf + doff;
+		DPRINTFN(3, "%s: decap %llu bytes\n", DEVNAM(sc), dlen);
+		m = m_devget(dp, (int)dlen, ETHER_ALIGN);
+		if (m == NULL) {
+			ifp->if_iqdrops++;
+			continue;
+		}
+		ml_enqueue(&ml, m);
+	}
+done:
+	if_input(ifp, &ml);
+	splx(s);
+	return;
+toosmall:
+	DPRINTF("%s: packet too small (%d)\n", DEVNAM(sc), len);
+fail:
+	ifp->if_ierrors++;
+	splx(s);
+}
+
+void
+uncm_intr(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+	struct uncm_softc *sc = priv;
+	struct ifnet *ifp = GET_IFP(sc);
+	uint32_t up, down;
+	int	total_len;
+
+	if (usbd_is_dying(sc->sc_udev))
+		return;
+
+	if (status != USBD_NORMAL_COMPLETION) {
+		DPRINTF("%s: notification error: %s\n", DEVNAM(sc),
+		    usbd_errstr(status));
+		if (status == USBD_STALLED)
+			usbd_clear_endpoint_stall_async(sc->sc_ctrl_pipe);
+		return;
+	}
+	usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL);
+	if (total_len < UCDC_NOTIFICATION_LENGTH) {
+		DPRINTF("%s: short notification (%d<%d)\n", DEVNAM(sc),
+		    total_len, UCDC_NOTIFICATION_LENGTH);
+		return;
+	}
+	if (sc->sc_intr_msg.bmRequestType != UCDC_NOTIFICATION) {
+		DPRINTF("%s: unexpected notification (type=0x%02x)\n",
+		    DEVNAM(sc), sc->sc_intr_msg.bmRequestType);
+		return;
+	}
+
+	switch (sc->sc_intr_msg.bNotification) {
+	case UCDC_N_NETWORK_CONNECTION:
+		if (ifp->if_flags & IFF_DEBUG)
+			log(LOG_DEBUG, "%s: network %sconnected\n", DEVNAM(sc),
+			    UGETW(sc->sc_intr_msg.wValue) ? "" : "dis");
+		sc->sc_link = UGETW(sc->sc_intr_msg.wValue) ?
+		    LINK_STATE_UP : LINK_STATE_DOWN;
+		usb_add_task(sc->sc_udev, &sc->sc_link_task);
+		break;
+	case UCDC_N_CONNECTION_SPEED_CHANGE:
+		if (total_len >= UCDC_NOTIFICATION_LENGTH + 8) {
+			down = UGETDW(&sc->sc_intr_msg.data[0]);
+			up = UGETDW(&sc->sc_intr_msg.data[4]);
+			sc->sc_baudrate = MIN(up, down);
+			DPRINTFN(2, "%s: speed down %u up %u\n", DEVNAM(sc),
+			    down, up);
+			usb_add_task(sc->sc_udev, &sc->sc_link_task);
+		}
+		break;
+	default:
+		DPRINTF("%s: unexpected notification (0x%02x)\n",
+		    DEVNAM(sc), sc->sc_intr_msg.bNotification);
+		break;
+	}
+}
+
+void
+uncm_link_task(void *arg)
+{
+	struct uncm_softc *sc = arg;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 s;
+
+	if (usbd_is_dying(sc->sc_udev))
+		return;
+
+	s = splnet();
+	ifp->if_baudrate = sc->sc_baudrate;
+	if (sc->sc_link != LINK_STATE_UNKNOWN &&
+	    ifp->if_link_state != sc->sc_link) {
+		if (ifp->if_flags & IFF_DEBUG)
+			log(LOG_DEBUG, "%s: link state changed from %s to %s\n",
+			    DEVNAM(sc),
+			    LINK_STATE_IS_UP(ifp->if_link_state) ? "up" : "down",
+			    LINK_STATE_IS_UP(sc->sc_link) ? "up" : "down");
+		ifp->if_link_state = sc->sc_link;
+		if_link_state_change(ifp);
+		if (LINK_STATE_IS_UP(sc->sc_link) &&
+		    (ifp->if_flags & IFF_RUNNING) && !ifq_empty(&ifp->if_snd))
+			uncm_start(ifp);
+	}
+	splx(s);
+}
+
diff --git sys/dev/usb/mbim.h sys/dev/usb/mbim.h
index 2f6d30a82e4..60ce897cbef 100644
--- sys/dev/usb/mbim.h
+++ sys/dev/usb/mbim.h
@@ -601,97 +601,6 @@ struct mbim_descriptor {
 	uByte	bmNetworkCapabilities;
 } __packed;
 
-/*
- * NCM Parameters
- */
-#define NCM_GET_NTB_PARAMETERS	0x80
-#define NCM_GET_NTB_FORMAT	0x83	/* Current format returned as uWord */
-#define NCM_SET_NTB_FORMAT	0x84	/* Desired format is in wValue */
-
-#define NCM_FORMAT_NTB16	0x00
-#define NCM_FORMAT_NTB32	0x01
-
-struct ncm_ntb_parameters {
-	uWord	wLength;
-	uWord	bmNtbFormatsSupported;
-#define NCM_FORMAT_NTB16_MASK	(1U << NCM_FORMAT_NTB16)
-#define NCM_FORMAT_NTB32_MASK	(1U << NCM_FORMAT_NTB32)
-	uDWord	dwNtbInMaxSize;
-	uWord	wNdpInDivisor;
-	uWord	wNdpInPayloadRemainder;
-	uWord	wNdpInAlignment;
-	uWord	wReserved1;
-	uDWord	dwNtbOutMaxSize;
-	uWord	wNdpOutDivisor;
-	uWord	wNdpOutPayloadRemainder;
-	uWord	wNdpOutAlignment;
-	uWord	wNtbOutMaxDatagrams;
-} __packed;
-
-/*
- * NCM Encoding
- */
-struct ncm_header16 {
-#define NCM_HDR16_SIG		0x484d434e
-	uDWord	dwSignature;
-	uWord	wHeaderLength;
-	uWord	wSequence;
-	uWord	wBlockLength;
-	uWord	wNdpIndex;
-} __packed;
-
-struct ncm_header32 {
-#define NCM_HDR32_SIG		0x686d636e
-	uDWord	dwSignature;
-	uWord	wHeaderLength;
-	uWord	wSequence;
-	uDWord	dwBlockLength;
-	uDWord	dwNdpIndex;
-} __packed;
-
-
-#define MBIM_NCM_NTH_SIDSHIFT	24
-#define MBIM_NCM_NTH_GETSID(s)	(((s) > MBIM_NCM_NTH_SIDSHIFT) & 0xff)
-
-struct ncm_pointer16_dgram {
-	uWord	wDatagramIndex;
-	uWord	wDatagramLen;
-} __packed;
-
-struct ncm_pointer16 {
-#define MBIM_NCM_NTH16_IPS	 0x00535049
-#define MBIM_NCM_NTH16_ISISG(s) (((s) & 0x00ffffff) == MBIM_NCM_NTH16_IPS)
-#define MBIM_NCM_NTH16_SIG(s)	\
-		((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH16_IPS)
-	uDWord	dwSignature;
-	uWord	wLength;
-	uWord	wNextNdpIndex;
-
-	/* Minimum is two datagrams, but can be more */
-	struct ncm_pointer16_dgram dgram[1];
-} __packed;
-
-struct ncm_pointer32_dgram {
-	uDWord	dwDatagramIndex;
-	uDWord	dwDatagramLen;
-} __packed;
-
-struct ncm_pointer32 {
-#define MBIM_NCM_NTH32_IPS	0x00737069
-#define MBIM_NCM_NTH32_ISISG(s)	\
-		(((s) & 0x00ffffff) == MBIM_NCM_NTH32_IPS)
-#define MBIM_NCM_NTH32_SIG(s)		\
-		((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH32_IPS)
-	uDWord	dwSignature;
-	uWord	wLength;
-	uWord	wReserved6;
-	uDWord	dwNextNdpIndex;
-	uDWord	dwReserved12;
-
-	/* Minimum is two datagrams, but can be more */
-	struct ncm_pointer32_dgram dgram[1];
-} __packed;
-
 #endif /* _KERNEL */
 
 #endif /* _MBIM_H_ */
diff --git sys/dev/usb/usb.h sys/dev/usb/usb.h
index d8e18d3b340..30641c40776 100644
--- sys/dev/usb/usb.h
+++ sys/dev/usb/usb.h
@@ -297,8 +297,9 @@ struct usb_string_descriptor {
 	uWord		bString[126];
 } __packed;
 typedef struct usb_string_descriptor usb_string_descriptor_t;
-#define USB_MAX_STRING_LEN 127
-#define USB_LANGUAGE_TABLE 0	/* # of the string language id table */
+#define USB_MAX_STRING_LEN  127
+#define USB_LANGUAGE_TABLE  0	/* # of the string language id table */
+#define USB_LANGID_EN_US    0x0409	/* English (United States) */
 
 /* Hub specific request */
 #define UR_GET_BUS_STATE	0x02
diff --git sys/dev/usb/usbcdc.h sys/dev/usb/usbcdc.h
index 1ac2a35640a..665dad121a2 100644
--- sys/dev/usb/usbcdc.h
+++ sys/dev/usb/usbcdc.h
@@ -52,6 +52,7 @@
 #define UDESCSUB_CDC_CCMF	14 /* CAPI Control Management */
 #define UDESCSUB_CDC_ENF	15 /* Ethernet Networking */
 #define UDESCSUB_CDC_ANF	16 /* ATM Networking */
+#define UDESCSUB_CDC_NCM	26 /* Network Control Model */
 
 struct usb_cdc_header_descriptor {
 	uByte		bLength;
@@ -100,6 +101,20 @@ struct usb_cdc_ethernet_descriptor {
 	uByte		bNumberPowerFilters;
 } __packed;
 
+struct usb_cdc_ncm_descriptor {
+	uByte		bLength;
+	uByte		bDescriptorType;
+	uByte		bDescriptorSubtype;
+	uWord		bcdNcmVersion;
+	uByte		bmNetworkCapabilities;
+#define UCDC_NCM_NCAP_ETH_FILTER	0x01
+#define UCDC_NCM_NCAP_NET_ADDRESS	0x02
+#define UCDC_NCM_NCAP_ENCAP_COMMAND	0x04
+#define UCDC_NCM_NCAP_MAX_DATAGRAM	0x08
+#define UCDC_NCM_NCAP_CRC_MODE		0x10
+#define UCDC_NCM_NCAP_NTB_INPUT_SIZE	0x20
+} __packed;
+
 #define UCDC_SEND_ENCAPSULATED_COMMAND	0x00
 #define UCDC_GET_ENCAPSULATED_RESPONSE	0x01
 #define UCDC_SET_COMM_FEATURE		0x02
@@ -176,4 +191,105 @@ struct usb_cdc_connection_speed {
 #define UCDC_N_SERIAL_DSR		0x02
 #define UCDC_N_SERIAL_DCD		0x01
 
+/*
+ * Network Control Model (NCM 1.0)
+ */
+
+#define NCM_GET_NTB_PARAMETERS		0x80
+#define NCM_GET_NET_ADDRESS		0x81
+#define NCM_SET_NET_ADDRESS		0x82
+#define NCM_GET_NTB_FORMAT	    0x83	/* Current format returned as uWord */
+#define NCM_SET_NTB_FORMAT	    0x84	/* Desired format is in wValue */
+#define NCM_GET_NTB_INPUT_SIZE		0x85
+#define NCM_SET_NTB_INPUT_SIZE		0x86
+#define NCM_GET_MAX_DATAGRAM_SIZE	0x87
+#define NCM_SET_MAX_DATAGRAM_SIZE	0x88
+#define NCM_GET_CRC_MODE		0x89
+#define NCM_SET_CRC_MODE		0x8a
+
+#define NCM_NDP16_SIG_NOCRC	0x304d434e
+#define NCM_NDP16_SIG_CRC	0x314d434e
+#define NCM_NDP32_SIG_NOCRC	0x306d636e
+#define NCM_NDP32_SIG_CRC	0x316d636e
+
+#define NCM_FORMAT_NTB16	0x00
+#define NCM_FORMAT_NTB32	0x01
+
+struct ncm_ntb_parameters {
+	uWord	wLength;
+	uWord	bmNtbFormatsSupported;
+#define NCM_FORMAT_NTB16_MASK	(1U << NCM_FORMAT_NTB16)
+#define NCM_FORMAT_NTB32_MASK	(1U << NCM_FORMAT_NTB32)
+	uDWord	dwNtbInMaxSize;
+	uWord	wNdpInDivisor;
+	uWord	wNdpInPayloadRemainder;
+	uWord	wNdpInAlignment;
+	uWord	wReserved1;
+	uDWord	dwNtbOutMaxSize;
+	uWord	wNdpOutDivisor;
+	uWord	wNdpOutPayloadRemainder;
+	uWord	wNdpOutAlignment;
+	uWord	wNtbOutMaxDatagrams;
+} __packed;
+
+struct ncm_header16 {
+#define NCM_HDR16_SIG		0x484d434e
+	uDWord	dwSignature;
+	uWord	wHeaderLength;
+	uWord	wSequence;
+	uWord	wBlockLength;
+	uWord	wNdpIndex;
+} __packed;
+
+struct ncm_header32 {
+#define NCM_HDR32_SIG		0x686d636e
+	uDWord	dwSignature;
+	uWord	wHeaderLength;
+	uWord	wSequence;
+	uDWord	dwBlockLength;
+	uDWord	dwNdpIndex;
+} __packed;
+
+
+#define MBIM_NCM_NTH_SIDSHIFT	24
+#define MBIM_NCM_NTH_GETSID(s)	(((s) > MBIM_NCM_NTH_SIDSHIFT) & 0xff)
+
+struct ncm_pointer16_dgram {
+	uWord	wDatagramIndex;
+	uWord	wDatagramLen;
+} __packed;
+
+struct ncm_pointer16 {
+#define MBIM_NCM_NTH16_IPS	 0x00535049
+#define MBIM_NCM_NTH16_ISISG(s) (((s) & 0x00ffffff) == MBIM_NCM_NTH16_IPS)
+#define MBIM_NCM_NTH16_SIG(s)	\
+		((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH16_IPS)
+	uDWord	dwSignature;
+	uWord	wLength;
+	uWord	wNextNdpIndex;
+
+	/* Minimum is two datagrams, but can be more */
+	struct ncm_pointer16_dgram dgram[1];
+} __packed;
+
+struct ncm_pointer32_dgram {
+	uDWord	dwDatagramIndex;
+	uDWord	dwDatagramLen;
+} __packed;
+
+struct ncm_pointer32 {
+#define MBIM_NCM_NTH32_IPS	0x00737069
+#define MBIM_NCM_NTH32_ISISG(s)	\
+		(((s) & 0x00ffffff) == MBIM_NCM_NTH32_IPS)
+#define MBIM_NCM_NTH32_SIG(s)		\
+		((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH32_IPS)
+	uDWord	dwSignature;
+	uWord	wLength;
+	uWord	wReserved6;
+	uDWord	dwNextNdpIndex;
+	uDWord	dwReserved12;
+
+	/* Minimum is two datagrams, but can be more */
+	struct ncm_pointer32_dgram dgram[1];
+} __packed;
 #endif /* _USBCDC_H_ */