tech-net archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Porting umb(4) from OpenBSD



			Hi tech-net@,

I am currently trying to port umb(4) from OpenBSD:

> The umb driver provides support for USB MBIM devices.
> MBIM devices establish connections via cellular networks such as
> GPRS, UMTS, and LTE. They appear as a regular point-to-point network
> interface, transporting raw IP frames.

This is apparently required to access Internet with USB modems since
4G/LTE (at least on my own Sierra Wireless EM7345).

It does not build yet (therefore also not tested) but I am getting
there. If anyone feels like reviewing it already, it could save me some
trouble and help getting it to work faster.

This is probably also missing changes to ifconfig(8), if only to set the
PIN code; this is the user interface that OpenBSD developers have chosen
for this. Alternative suggestions are welcome! (sysctl...)

Cheers,
-- 
khorben
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 45ae1977943b..d3cf3c571c69 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -982,6 +982,7 @@ cue*	at uhub? port ?		# CATC USB-EL1201A based adapters
 kue*	at uhub? port ?		# Kawasaki LSI KL5KUSB101B based adapters
 #mos*	at uhub? port ?		# Moschip MCS7730/MCS7830/MCS7832 based adapters
 udav*	at uhub? port ?		# Davicom DM9601 based adapters
+#umb*	at uhub? port ?		# Mobile Broadband Interface Model
 url*	at uhub? port ?		# Realtek RTL8150L based adapters
 urndis* at uhub? port ? 	# Microsoft RNDIS specification
 
diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb
index 8a981f08909e..69a6c0bb5a5b 100644
--- a/sys/dev/usb/files.usb
+++ b/sys/dev/usb/files.usb
@@ -378,6 +378,11 @@ device	otus: arp, ether, firmload, ifnet, wlan
 attach	otus at usbdevif
 file	dev/usb/if_otus.c		otus
 
+# Mobile Broadband Interface Model
+device	umb: ifnet, mii
+attach	umb at usbdevif
+file	dev/usb/if_umb.c		umb
+
 # Serial drivers
 # Modems
 define	umodem_common
diff --git a/sys/dev/usb/if_umb.c b/sys/dev/usb/if_umb.c
new file mode 100644
index 000000000000..731032e767a8
--- /dev/null
+++ b/sys/dev/usb/if_umb.c
@@ -0,0 +1,2569 @@
+/*	$NetBSD$ */
+/*	$OpenBSD: if_umb.c,v 1.18 2018/02/19 08:59:52 mpi Exp $ */
+
+/*
+ * Copyright (c) 2016 genua mbH
+ * 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.
+ */
+
+/*
+ * Mobile Broadband Interface Model specification:
+ * http://www.usb.org/developers/docs/devclass_docs/MBIM10Errata1_073013.zip
+ * Compliance testing guide
+ * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: if_cdce.c,v 1.44.8.1 2018/01/31 18:01:54 martin Exp $");
+
+#ifdef _KERNEL_OPT
+#include "opt_inet.h"
+#endif
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/systm.h>
+#include <sys/syslog.h>
+
+#include <net/if.h>
+#include <net/if_types.h>
+
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdivar.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbdevs.h>
+#include <dev/usb/usbcdc.h>
+
+#include <dev/usb/mbim.h>
+#include <dev/usb/if_umbreg.h>
+
+#ifdef UMB_DEBUG
+#define DPRINTF(x...)							\
+		do { if (umb_debug) log(LOG_DEBUG, x); } while (0)
+
+#define DPRINTFN(n, x...)						\
+		do { if (umb_debug >= (n)) log(LOG_DEBUG, x); } while (0)
+
+#define DDUMPN(n, b, l)							\
+		do {							\
+			if (umb_debug >= (n))				\
+				umb_dump((b), (l));			\
+		} while (0)
+
+int	 umb_debug = 0;
+char	*umb_uuid2str(uint8_t [MBIM_UUID_LEN]);
+void	 umb_dump(void *, int);
+
+#else
+#define DPRINTF(x...)		do { } while (0)
+#define DPRINTFN(n, x...)	do { } while (0)
+#define DDUMPN(n, b, l)		do { } while (0)
+#endif
+
+#define DEVNAM(sc)		device_xname((sc)->sc_dev)
+
+/*
+ * State change timeout
+ */
+#define UMB_STATE_CHANGE_TIMEOUT	30
+
+/*
+ * State change flags
+ */
+#define UMB_NS_DONT_DROP	0x0001	/* do not drop below current state */
+#define UMB_NS_DONT_RAISE	0x0002	/* do not raise below current state */
+
+/*
+ * Diagnostic macros
+ */
+const struct umb_valdescr umb_regstates[] = MBIM_REGSTATE_DESCRIPTIONS;
+const struct umb_valdescr umb_dataclasses[] = MBIM_DATACLASS_DESCRIPTIONS;
+const struct umb_valdescr umb_simstate[] = MBIM_SIMSTATE_DESCRIPTIONS;
+const struct umb_valdescr umb_messages[] = MBIM_MESSAGES_DESCRIPTIONS;
+const struct umb_valdescr umb_status[] = MBIM_STATUS_DESCRIPTIONS;
+const struct umb_valdescr umb_cids[] = MBIM_CID_DESCRIPTIONS;
+const struct umb_valdescr umb_pktstate[] = MBIM_PKTSRV_STATE_DESCRIPTIONS;
+const struct umb_valdescr umb_actstate[] = MBIM_ACTIVATION_STATE_DESCRIPTIONS;
+const struct umb_valdescr umb_error[] = MBIM_ERROR_DESCRIPTIONS;
+const struct umb_valdescr umb_pintype[] = MBIM_PINTYPE_DESCRIPTIONS;
+const struct umb_valdescr umb_istate[] = UMB_INTERNAL_STATE_DESCRIPTIONS;
+
+#define umb_regstate(c)		umb_val2descr(umb_regstates, (c))
+#define umb_dataclass(c)	umb_val2descr(umb_dataclasses, (c))
+#define umb_simstate(s)		umb_val2descr(umb_simstate, (s))
+#define umb_request2str(m)	umb_val2descr(umb_messages, (m))
+#define umb_status2str(s)	umb_val2descr(umb_status, (s))
+#define umb_cid2str(c)		umb_val2descr(umb_cids, (c))
+#define umb_packet_state(s)	umb_val2descr(umb_pktstate, (s))
+#define umb_activation(s)	umb_val2descr(umb_actstate, (s))
+#define umb_error2str(e)	umb_val2descr(umb_error, (e))
+#define umb_pin_type(t)		umb_val2descr(umb_pintype, (t))
+#define umb_istate(s)		umb_val2descr(umb_istate, (s))
+
+int		 umb_match(device_t, cfdata_t, void *);
+void		 umb_attach(device_t, device_t, void *);
+int		 umb_detach(device_t, int);
+void		 umb_ncm_setup(struct umb_softc *);
+int		 umb_alloc_xfers(struct umb_softc *);
+void		 umb_free_xfers(struct umb_softc *);
+int		 umb_alloc_bulkpipes(struct umb_softc *);
+void		 umb_close_bulkpipes(struct umb_softc *);
+int		 umb_ioctl(struct ifnet *, u_long, void *);
+int		 umb_output(struct ifnet *, struct mbuf *,
+		    const struct sockaddr *, const struct rtentry *);
+void		 umb_input(struct ifnet *, struct mbuf *);
+void		 umb_start(struct ifnet *);
+void		 umb_watchdog(struct ifnet *);
+void		 umb_statechg_timeout(void *);
+
+void		 umb_newstate(struct umb_softc *, enum umb_state, int);
+void		 umb_state_task(void *);
+void		 umb_up(struct umb_softc *);
+void		 umb_down(struct umb_softc *, int);
+
+void		 umb_get_response_task(void *);
+
+void		 umb_decode_response(struct umb_softc *, void *, int);
+void		 umb_handle_indicate_status_msg(struct umb_softc *, void *,
+		    int);
+void		 umb_handle_opendone_msg(struct umb_softc *, void *, int);
+void		 umb_handle_closedone_msg(struct umb_softc *, void *, int);
+int		 umb_decode_register_state(struct umb_softc *, void *, int);
+int		 umb_decode_devices_caps(struct umb_softc *, void *, int);
+int		 umb_decode_subscriber_status(struct umb_softc *, void *, int);
+int		 umb_decode_radio_state(struct umb_softc *, void *, int);
+int		 umb_decode_pin(struct umb_softc *, void *, int);
+int		 umb_decode_packet_service(struct umb_softc *, void *, int);
+int		 umb_decode_signal_state(struct umb_softc *, void *, int);
+int		 umb_decode_connect_info(struct umb_softc *, void *, int);
+int		 umb_decode_ip_configuration(struct umb_softc *, void *, int);
+void		 umb_rx(struct umb_softc *);
+void		 umb_rxeof(struct usbd_xfer *, void *, usbd_status);
+int		 umb_encap(struct umb_softc *, struct mbuf *);
+void		 umb_txeof(struct usbd_xfer *, void *, usbd_status);
+void		 umb_decap(struct umb_softc *, struct usbd_xfer *);
+
+usbd_status	 umb_send_encap_command(struct umb_softc *, void *, int);
+int		 umb_get_encap_response(struct umb_softc *, void *, int *);
+void		 umb_ctrl_msg(struct umb_softc *, uint32_t, void *, int);
+
+void		 umb_open(struct umb_softc *);
+void		 umb_close(struct umb_softc *);
+
+int		 umb_setpin(struct umb_softc *, int, int, void *, int, void *,
+		    int);
+void		 umb_setdataclass(struct umb_softc *);
+void		 umb_radio(struct umb_softc *, int);
+void		 umb_allocate_cid(struct umb_softc *);
+void		 umb_send_fcc_auth(struct umb_softc *);
+void		 umb_packet_service(struct umb_softc *, int);
+void		 umb_connect(struct umb_softc *);
+void		 umb_disconnect(struct umb_softc *);
+void		 umb_send_connect(struct umb_softc *, int);
+
+void		 umb_qry_ipconfig(struct umb_softc *);
+void		 umb_cmd(struct umb_softc *, int, int, void *, int);
+void		 umb_cmd1(struct umb_softc *, int, int, void *, int, uint8_t *);
+void		 umb_command_done(struct umb_softc *, void *, int);
+void		 umb_decode_cid(struct umb_softc *, uint32_t, void *, int);
+void		 umb_decode_qmi(struct umb_softc *, uint8_t *, int);
+
+void		 umb_intr(struct usbd_xfer *, void *, usbd_status);
+
+#if 0
+char		*umb_ntop(struct sockaddr *);
+#endif
+
+int		 umb_xfer_tout = USBD_DEFAULT_TIMEOUT;
+
+uint8_t		 umb_uuid_basic_connect[] = MBIM_UUID_BASIC_CONNECT;
+uint8_t		 umb_uuid_context_internet[] = MBIM_UUID_CONTEXT_INTERNET;
+uint8_t		 umb_uuid_qmi_mbim[] = MBIM_UUID_QMI_MBIM;
+uint32_t	 umb_session_id = 0;
+
+CFATTACH_DECL_NEW(umb, sizeof(struct umb_softc), umb_match, umb_attach,
+    umb_detach, NULL);
+
+const int umb_delay = 4000;
+
+/*
+ * These devices require an "FCC Authentication" command.
+ */
+const struct usb_devno umb_fccauth_devs[] = {
+	{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_EM7455 },
+};
+
+uint8_t umb_qmi_alloc_cid[] = {
+	0x01,
+	0x0f, 0x00,		/* len */
+	0x00,			/* QMUX flags */
+	0x00,			/* service "ctl" */
+	0x00,			/* CID */
+	0x00,			/* QMI flags */
+	0x01,			/* transaction */
+	0x22, 0x00,		/* msg "Allocate CID" */
+	0x04, 0x00,		/* TLV len */
+	0x01, 0x01, 0x00, 0x02	/* TLV */
+};
+
+uint8_t umb_qmi_fcc_auth[] = {
+	0x01,
+	0x0c, 0x00,		/* len */
+	0x00,			/* QMUX flags */
+	0x02,			/* service "dms" */
+#define UMB_QMI_CID_OFFS	5
+	0x00,			/* CID (filled in later) */
+	0x00,			/* QMI flags */
+	0x01, 0x00,		/* transaction */
+	0x5f, 0x55,		/* msg "Send FCC Authentication" */
+	0x00, 0x00		/* TLV len */
+};
+
+int
+umb_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct usbif_attach_arg *uiaa = aux;
+	usb_interface_descriptor_t *id;
+
+	if (!uiaa->uiaa_iface)
+		return UMATCH_NONE;
+	if ((id = usbd_get_interface_descriptor(uiaa->uiaa_iface)) == NULL)
+		return UMATCH_NONE;
+
+	/*
+	 * If this function implements NCM, check if alternate setting
+	 * 1 implements MBIM.
+	 */
+	if (id->bInterfaceClass == UICLASS_CDC &&
+	    id->bInterfaceSubClass ==
+	    UISUBCLASS_NETWORK_CONTROL_MODEL)
+		id = usbd_find_idesc(uiaa->uiaa_device->ud_cdesc, uiaa->uiaa_iface->ui_index, 1);
+	if (id == NULL)
+		return UMATCH_NONE;
+
+	if (id->bInterfaceClass == UICLASS_CDC &&
+	    id->bInterfaceSubClass ==
+	    UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL &&
+	    id->bInterfaceProtocol == 0)
+		return UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO;
+
+	return UMATCH_NONE;
+}
+
+void
+umb_attach(device_t parent, device_t self, void *aux)
+{
+	struct umb_softc *sc = (struct umb_softc *)self;
+	struct usbif_attach_arg *uiaa = aux;
+	usbd_status status;
+	usbd_desc_iter_t iter;
+	const usb_descriptor_t *desc;
+	int	 v;
+	const usb_cdc_union_descriptor_t *ud;
+	const struct mbim_descriptor *md;
+	int	 i;
+	int	 ctrl_ep;
+	const usb_interface_descriptor_t *id;
+	usb_config_descriptor_t	*cd;
+	usb_endpoint_descriptor_t *ed;
+	const usb_interface_assoc_descriptor_t *ad;
+	int	 current_ifaceno = -1;
+	int	 data_ifaceno = -1;
+	int	 altnum;
+	int	 s;
+	struct ifnet *ifp;
+
+	sc->sc_udev = uiaa->uiaa_device;
+	sc->sc_ctrl_ifaceno = uiaa->uiaa_ifaceno;
+
+	/*
+	 * Some MBIM hardware does not provide the mandatory CDC Union
+	 * Descriptor, so we also look at matching Interface
+	 * Association Descriptors to find out the MBIM Data Interface
+	 * number.
+	 */
+	sc->sc_ver_maj = sc->sc_ver_min = -1;
+	sc->sc_maxpktlen = MBIM_MAXSEGSZ_MINVAL;
+	usb_desc_iter_init(sc->sc_udev, &iter);
+	while ((desc = usb_desc_iter_next(&iter))) {
+		if (desc->bDescriptorType == UDESC_INTERFACE_ASSOC) {
+			ad = (const usb_interface_assoc_descriptor_t *)desc;
+			if (ad->bFirstInterface == uiaa->uiaa_ifaceno &&
+			    ad->bInterfaceCount > 1)
+				data_ifaceno = uiaa->uiaa_ifaceno + 1;
+			continue;
+		}
+		if (desc->bDescriptorType == UDESC_INTERFACE) {
+			id = (const usb_interface_descriptor_t *)desc;
+			current_ifaceno = id->bInterfaceNumber;
+			continue;
+		}
+		if (current_ifaceno != uiaa->uiaa_ifaceno)
+			continue;
+		if (desc->bDescriptorType != UDESC_CS_INTERFACE)
+			continue;
+		switch (desc->bDescriptorSubtype) {
+		case UDESCSUB_CDC_UNION:
+			ud = (const usb_cdc_union_descriptor_t *)desc;
+			data_ifaceno = ud->bSlaveInterface[0];
+			break;
+		case UDESCSUB_MBIM:
+			md = (const struct mbim_descriptor *)desc;
+			v = UGETW(md->bcdMBIMVersion);
+			sc->sc_ver_maj = MBIM_VER_MAJOR(v);
+			sc->sc_ver_min = MBIM_VER_MINOR(v);
+			sc->sc_ctrl_len = UGETW(md->wMaxControlMessage);
+			/* Never trust a USB device! Could try to exploit us */
+			if (sc->sc_ctrl_len < MBIM_CTRLMSG_MINLEN ||
+			    sc->sc_ctrl_len > MBIM_CTRLMSG_MAXLEN) {
+				DPRINTF("%s: control message len %d out of "
+				    "bounds [%d .. %d]\n", DEVNAM(sc),
+				    sc->sc_ctrl_len, MBIM_CTRLMSG_MINLEN,
+				    MBIM_CTRLMSG_MAXLEN);
+				/* cont. anyway */
+			}
+			sc->sc_maxpktlen = UGETW(md->wMaxSegmentSize);
+			DPRINTFN(2, "%s: ctrl_len=%d, maxpktlen=%d, cap=0x%x\n",
+			    DEVNAM(sc), sc->sc_ctrl_len, sc->sc_maxpktlen,
+			    md->bmNetworkCapabilities);
+			break;
+		default:
+			break;
+		}
+	}
+	if (sc->sc_ver_maj < 0) {
+		printf("%s: missing MBIM descriptor\n", DEVNAM(sc));
+		goto fail;
+	}
+	if (usb_lookup(umb_fccauth_devs, uiaa->uiaa_vendor, uiaa->uiaa_product)) {
+		sc->sc_flags |= UMBFLG_FCC_AUTH_REQUIRED;
+		sc->sc_cid = -1;
+	}
+
+	for (i = 0; i < uiaa->uiaa_nifaces; i++) {
+		id = usbd_get_interface_descriptor(uiaa->uiaa_ifaces[i]);
+		if (id != NULL && id->bInterfaceNumber == data_ifaceno) {
+			sc->sc_data_iface = uiaa->uiaa_ifaces[i];
+		}
+	}
+	if (sc->sc_data_iface == NULL) {
+		printf("%s: no data interface found\n", DEVNAM(sc));
+		goto fail;
+	}
+
+	/*
+	 * If this is a combined NCM/MBIM function, switch to
+	 * alternate setting one to enable MBIM.
+	 */
+	id = usbd_get_interface_descriptor(uiaa->uiaa_iface);
+	if (id->bInterfaceClass == UICLASS_CDC &&
+	    id->bInterfaceSubClass ==
+	    UISUBCLASS_NETWORK_CONTROL_MODEL)
+		usbd_set_interface(uiaa->uiaa_iface, 1);
+
+	id = usbd_get_interface_descriptor(uiaa->uiaa_iface);
+	ctrl_ep = -1;
+	for (i = 0; i < id->bNumEndpoints && ctrl_ep == -1; i++) {
+		ed = usbd_interface2endpoint_descriptor(uiaa->uiaa_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;
+	}
+
+	/*
+	 * For the MBIM Data Interface, select the appropriate
+	 * alternate setting by looking for a matching descriptor that
+	 * has two endpoints.
+	 */
+	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_iface->ui_index, i);
+		if (id == NULL)
+			continue;
+		if (id->bInterfaceClass == UICLASS_CDC_DATA &&
+		    id->bInterfaceSubClass == UISUBCLASS_DATA &&
+		    id->bInterfaceProtocol == UIPROTO_DATA_MBIM &&
+		    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));
+
+	usb_init_task(&sc->sc_umb_task, umb_state_task, sc,
+	    0);
+	usb_init_task(&sc->sc_get_response_task, umb_get_response_task, sc,
+	    0);
+	callout_setfunc(&sc->sc_statechg_timer, umb_statechg_timeout, sc);
+
+	if (usbd_open_pipe_intr(uiaa->uiaa_iface, ctrl_ep, USBD_SHORT_XFER_OK,
+	    &sc->sc_ctrl_pipe, sc, &sc->sc_intr_msg, sizeof (sc->sc_intr_msg),
+	    umb_intr, USBD_DEFAULT_INTERVAL)) {
+		printf("%s: failed to open control pipe\n", DEVNAM(sc));
+		goto fail;
+	}
+	sc->sc_resp_buf = malloc(sc->sc_ctrl_len, M_DEVBUF, M_NOWAIT);
+	if (sc->sc_resp_buf == NULL) {
+		printf("%s: allocation of resp buffer failed\n", DEVNAM(sc));
+		goto fail;
+	}
+	sc->sc_ctrl_msg = malloc(sc->sc_ctrl_len, M_DEVBUF, M_NOWAIT);
+	if (sc->sc_ctrl_msg == NULL) {
+		printf("%s: allocation of ctrl msg buffer failed\n",
+		    DEVNAM(sc));
+		goto fail;
+	}
+
+	sc->sc_info.regstate = MBIM_REGSTATE_UNKNOWN;
+	sc->sc_info.pin_attempts_left = UMB_VALUE_UNKNOWN;
+	sc->sc_info.rssi = UMB_VALUE_UNKNOWN;
+	sc->sc_info.ber = UMB_VALUE_UNKNOWN;
+
+	umb_ncm_setup(sc);
+	DPRINTFN(2, "%s: rx/tx size %d/%d\n", DEVNAM(sc),
+	    sc->sc_rx_bufsz, sc->sc_tx_bufsz);
+
+	s = splnet();
+	ifp = GET_IFP(sc);
+	ifp->if_flags = IFF_SIMPLEX | IFF_MULTICAST | IFF_POINTOPOINT;
+	ifp->if_ioctl = umb_ioctl;
+	ifp->if_start = umb_start;
+	ifp->if_rtrequest = p2p_rtrequest;
+
+	ifp->if_watchdog = umb_watchdog;
+	strlcpy(ifp->if_xname, DEVNAM(sc), IFNAMSIZ);
+	ifp->if_link_state = LINK_STATE_DOWN;
+
+	ifp->if_type = IFT_MBIM;
+	ifp->if_addrlen = 0;
+	ifp->if_hdrlen = sizeof (struct ncm_header16) +
+	    sizeof (struct ncm_pointer16);
+	ifp->if_mtu = 1500;		/* use a common default */
+	ifp->if_hardmtu = sc->sc_maxpktlen;
+	ifp->if_output = umb_output;
+	ifp->_if_input = umb_input;
+	if_attach(ifp);
+	if_alloc_sadl(ifp);
+	ifp->if_softc = sc;
+	/*
+	 * Open the device now so that we are able to query device information.
+	 * XXX maybe close when done?
+	 */
+	umb_open(sc);
+	splx(s);
+
+	DPRINTF("%s: version %d.%d\n", DEVNAM(sc), sc->sc_ver_maj, sc->sc_ver_min);
+	return;
+
+fail:
+	return;
+}
+
+int
+umb_detach(device_t self, int flags)
+{
+	struct umb_softc *sc = (struct umb_softc *)self;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 s;
+
+	s = splnet();
+	if (ifp->if_flags & IFF_RUNNING)
+		umb_down(sc, 1);
+	umb_close(sc);
+
+	usb_rem_task(sc->sc_udev, &sc->sc_get_response_task);
+	usb_wait_task(sc->sc_udev, &sc->sc_get_response_task);
+	callout_destroy(&sc->sc_statechg_timer);
+	sc->sc_nresp = 0;
+	usb_rem_task(sc->sc_udev, &sc->sc_umb_task);
+	usb_wait_task(sc->sc_udev, &sc->sc_umb_task);
+	if (sc->sc_ctrl_pipe) {
+		usbd_close_pipe(sc->sc_ctrl_pipe);
+		sc->sc_ctrl_pipe = NULL;
+	}
+	if (sc->sc_ctrl_msg) {
+		free(sc->sc_ctrl_msg, sc->sc_ctrl_len);
+		sc->sc_ctrl_msg = NULL;
+	}
+	if (sc->sc_resp_buf) {
+		free(sc->sc_resp_buf, sc->sc_ctrl_len);
+		sc->sc_resp_buf = NULL;
+	}
+	if (ifp->if_softc != NULL) {
+		if_detach(ifp);
+	}
+
+	splx(s);
+	return 0;
+}
+
+void
+umb_ncm_setup(struct umb_softc *sc)
+{
+	usb_device_request_t req;
+	struct ncm_ntb_parameters np;
+
+	/* Query NTB tranfers sizes */
+	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 = UGETDW(np.dwNtbInMaxSize);
+		sc->sc_tx_bufsz = UGETDW(np.dwNtbOutMaxSize);
+	} else
+		sc->sc_rx_bufsz = sc->sc_tx_bufsz = 8 * 1024;
+}
+
+int
+umb_alloc_xfers(struct umb_softc *sc)
+{
+	int err = 0;
+
+	if (!sc->sc_rx_xfer) {
+		err |= usbd_create_xfer(sc->sc_rx_pipe,
+		    sc->sc_rx_bufsz + MBIM_HDR32_LEN,
+		    USBD_FORCE_SHORT_XFER, 0, &sc->sc_rx_xfer);
+	}
+	if (!sc->sc_tx_xfer) {
+		err |= usbd_create_xfer(sc->sc_tx_pipe,
+		    sc->sc_tx_bufsz + MBIM_HDR16_LEN,
+		    USBD_FORCE_SHORT_XFER, 0, &sc->sc_tx_xfer);
+	}
+	return (err != 0) ? 1 : 0;
+}
+
+void
+umb_free_xfers(struct umb_softc *sc)
+{
+	if (sc->sc_rx_xfer) {
+		/* implicit usbd_free_buffer() */
+		usbd_destroy_xfer(sc->sc_rx_xfer);
+		sc->sc_rx_xfer = NULL;
+		sc->sc_rx_buf = NULL;
+	}
+	if (sc->sc_tx_xfer) {
+		usbd_destroy_xfer(sc->sc_tx_xfer);
+		sc->sc_tx_xfer = NULL;
+		sc->sc_tx_buf = NULL;
+	}
+	if (sc->sc_tx_m) {
+		m_freem(sc->sc_tx_m);
+		sc->sc_tx_m = NULL;
+	}
+}
+
+int
+umb_alloc_bulkpipes(struct umb_softc *sc)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+
+	if (!(ifp->if_flags & IFF_RUNNING)) {
+		if (usbd_open_pipe(sc->sc_data_iface, sc->sc_rx_ep,
+		    USBD_EXCLUSIVE_USE, &sc->sc_rx_pipe))
+			return 0;
+		if (usbd_open_pipe(sc->sc_data_iface, sc->sc_tx_ep,
+		    USBD_EXCLUSIVE_USE, &sc->sc_tx_pipe))
+			return 0;
+
+		ifp->if_flags |= IFF_RUNNING;
+		ifq_clr_oactive(&ifp->if_snd);
+		umb_rx(sc);
+	}
+	return 1;
+}
+
+void
+umb_close_bulkpipes(struct umb_softc *sc)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+
+	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;
+	}
+}
+
+int
+umb_ioctl(struct ifnet *ifp, u_long cmd, void *data)
+{
+#if 0
+	struct proc *p = curproc;
+#endif
+	struct umb_softc *sc = ifp->if_softc;
+	struct ifreq *ifr = (struct ifreq *)data;
+	int	 s, error = 0;
+	struct umb_parameter mp;
+
+
+	s = splnet();
+	switch (cmd) {
+	case SIOCSIFFLAGS:
+		usb_add_task(sc->sc_udev, &sc->sc_umb_task, USB_TASKQ_DRIVER);
+		break;
+	case SIOCGUMBINFO:
+		error = copyout(&sc->sc_info, ifr->ifr_data,
+		    sizeof (sc->sc_info));
+		break;
+	case SIOCSUMBPARAM:
+#if 0
+		if ((error = suser(p)) != 0)
+			break;
+#endif
+		if ((error = copyin(ifr->ifr_data, &mp, sizeof (mp))) != 0)
+			break;
+
+		if ((error = umb_setpin(sc, mp.op, mp.is_puk, mp.pin, mp.pinlen,
+		    mp.newpin, mp.newpinlen)) != 0)
+			break;
+
+		if (mp.apnlen < 0 || mp.apnlen > sizeof (sc->sc_info.apn)) {
+			error = EINVAL;
+			break;
+		}
+		sc->sc_roaming = mp.roaming ? 1 : 0;
+		memset(sc->sc_info.apn, 0, sizeof (sc->sc_info.apn));
+		memcpy(sc->sc_info.apn, mp.apn, mp.apnlen);
+		sc->sc_info.apnlen = mp.apnlen;
+		sc->sc_info.preferredclasses = mp.preferredclasses;
+		umb_setdataclass(sc);
+		break;
+	case SIOCGUMBPARAM:
+		memset(&mp, 0, sizeof (mp));
+		memcpy(mp.apn, sc->sc_info.apn, sc->sc_info.apnlen);
+		mp.apnlen = sc->sc_info.apnlen;
+		mp.roaming = sc->sc_roaming;
+		mp.preferredclasses = sc->sc_info.preferredclasses;
+		error = copyout(&mp, ifr->ifr_data, sizeof (mp));
+		break;
+	case SIOCSIFMTU:
+		/* Does this include the NCM headers and tail? */
+		if (ifr->ifr_mtu > ifp->if_hardmtu) {
+			error = EINVAL;
+			break;
+		}
+		ifp->if_mtu = ifr->ifr_mtu;
+		break;
+	case SIOCSIFADDR:
+	case SIOCAIFADDR:
+	case SIOCSIFDSTADDR:
+	case SIOCADDMULTI:
+	case SIOCDELMULTI:
+		break;
+	default:
+		error = ENOTTY;
+		break;
+	}
+	splx(s);
+	return error;
+}
+
+int
+umb_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst,
+    const struct rtentry *rtp)
+{
+	int error;
+
+	DPRINTFN(10,("%s: %s: enter\n",
+		     device_xname(((struct upl_softc *)ifp->if_softc)->sc_dev),
+		     __func__));
+
+	/*
+	 * if the queueing discipline needs packet classification,
+	 * do it now.
+	 */
+	IFQ_CLASSIFY(&ifp->if_snd, m, dst->sa_family);
+
+	/*
+	 * Queue message on interface, and start output if interface
+	 * not yet active.
+	 */
+	error = if_transmit_lock(ifp, m);
+
+	return error;
+}
+
+void
+umb_input(struct ifnet *ifp, struct mbuf *m)
+{
+	size_t pktlen = m->m_len;
+	int s;
+
+	if ((ifp->if_flags & IFF_UP) == 0) {
+		m_freem(m);
+		return;
+	}
+	if (pktlen < sizeof (struct ip)) {
+		ifp->if_ierrors++;
+		DPRINTFN(4, "%s: dropping short packet (len %d)\n", __func__,
+		    pktlen);
+		m_freem(m);
+		return;
+	}
+	s = splnet();
+	if (__predict_false(!pktq_enqueue(ip_pktq, m, 0))) {
+		ifp->if_iqdrops++;
+		m_freem(m);
+	} else {
+		ifp->if_ipackets++;
+		ifp->if_ibytes += pktlen;
+	}
+	splx(s);
+}
+
+void
+umb_start(struct ifnet *ifp)
+{
+	struct umb_softc *sc = ifp->if_softc;
+	struct mbuf *m_head = NULL;
+
+	if (!(ifp->if_flags & IFF_RUNNING) ||
+	    ifq_is_oactive(&ifp->if_snd))
+		return;
+
+	m_head = ifq_deq_begin(&ifp->if_snd);
+	if (m_head == NULL)
+		return;
+
+	if (!umb_encap(sc, m_head)) {
+		ifq_deq_rollback(&ifp->if_snd, m_head);
+		ifq_set_oactive(&ifp->if_snd);
+		return;
+	}
+	ifq_deq_commit(&ifp->if_snd, m_head);
+
+	ifq_set_oactive(&ifp->if_snd);
+	ifp->if_timer = (2 * umb_xfer_tout) / 1000;
+}
+
+void
+umb_watchdog(struct ifnet *ifp)
+{
+	struct umb_softc *sc = ifp->if_softc;
+
+
+	ifp->if_oerrors++;
+	printf("%s: watchdog timeout\n", DEVNAM(sc));
+	usbd_abort_pipe(sc->sc_tx_pipe);
+	return;
+}
+
+void
+umb_statechg_timeout(void *arg)
+{
+	struct umb_softc *sc = arg;
+
+	if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING && !sc->sc_roaming) {
+		/*
+		 * Query the registration state until we're with the home
+		 * network again.
+		 */
+		umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_QRY, NULL, 0);
+	} else
+		printf("%s: state change timeout\n",DEVNAM(sc));
+	usb_add_task(sc->sc_udev, &sc->sc_umb_task, USB_TASKQ_DRIVER);
+}
+
+void
+umb_newstate(struct umb_softc *sc, enum umb_state newstate, int flags)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+
+	if (newstate == sc->sc_state)
+		return;
+	if (((flags & UMB_NS_DONT_DROP) && newstate < sc->sc_state) ||
+	    ((flags & UMB_NS_DONT_RAISE) && newstate > sc->sc_state))
+		return;
+	if (ifp->if_flags & IFF_DEBUG)
+		log(LOG_DEBUG, "%s: state going %s from '%s' to '%s'\n",
+		    DEVNAM(sc), newstate > sc->sc_state ? "up" : "down",
+		    umb_istate(sc->sc_state), umb_istate(newstate));
+	sc->sc_state = newstate;
+	usb_add_task(sc->sc_udev, &sc->sc_umb_task, USB_TASKQ_DRIVER);
+}
+
+void
+umb_state_task(void *arg)
+{
+	struct umb_softc *sc = arg;
+	struct ifnet *ifp = GET_IFP(sc);
+	struct ifreq ifr;
+	struct in_aliasreq ifra;
+	int	 s;
+	int	 state;
+
+	s = splnet();
+	if (ifp->if_flags & IFF_UP)
+		umb_up(sc);
+	else
+		umb_down(sc, 0);
+
+	state = sc->sc_state == UMB_S_UP ? LINK_STATE_UP : LINK_STATE_DOWN;
+	if (ifp->if_link_state != state) {
+		if (ifp->if_flags & IFF_DEBUG)
+			log(LOG_DEBUG, "%s: link state changed from %s to %s\n",
+			    DEVNAM(sc),
+			    (ifp->if_link_state == LINK_STATE_UP)
+			    ? "up" : "down",
+			    (state == LINK_STATE_UP) ? "up" : "down");
+		ifp->if_link_state = state;
+		if (state != LINK_STATE_UP) {
+			/*
+			 * Purge any existing addresses
+			 */
+			memset(sc->sc_info.ipv4dns, 0,
+			    sizeof (sc->sc_info.ipv4dns));
+			if (ifioctl_common(ifp, SIOCGIFADDR, &ifr) == 0 &&
+			    satosin(&ifr.ifr_addr)->sin_addr.s_addr !=
+			    INADDR_ANY) {
+				memset(&ifra, 0, sizeof (ifra));
+				memcpy(&ifra.ifra_addr, &ifr.ifr_addr,
+				    sizeof (ifra.ifra_addr));
+				ifioctl_common(ifp, SIOCDIFADDR, &ifra);
+			}
+		}
+		if_link_state_change(ifp, state);
+	}
+	splx(s);
+}
+
+void
+umb_up(struct umb_softc *sc)
+{
+	switch (sc->sc_state) {
+	case UMB_S_DOWN:
+		DPRINTF("%s: init: opening ...\n", DEVNAM(sc));
+		umb_open(sc);
+		break;
+	case UMB_S_OPEN:
+		if (sc->sc_flags & UMBFLG_FCC_AUTH_REQUIRED) {
+			if (sc->sc_cid == -1) {
+				DPRINTF("%s: init: allocating CID ...\n",
+				    DEVNAM(sc));
+				umb_allocate_cid(sc);
+				break;
+			} else
+				umb_newstate(sc, UMB_S_CID, UMB_NS_DONT_DROP);
+		} else {
+			DPRINTF("%s: init: turning radio on ...\n", DEVNAM(sc));
+			umb_radio(sc, 1);
+			break;
+		}
+		/*FALLTHROUGH*/
+	case UMB_S_CID:
+		DPRINTF("%s: init: sending FCC auth ...\n", DEVNAM(sc));
+		umb_send_fcc_auth(sc);
+		break;
+	case UMB_S_RADIO:
+		DPRINTF("%s: init: checking SIM state ...\n", DEVNAM(sc));
+		umb_cmd(sc, MBIM_CID_SUBSCRIBER_READY_STATUS, MBIM_CMDOP_QRY,
+		    NULL, 0);
+		break;
+	case UMB_S_SIMREADY:
+		DPRINTF("%s: init: attaching ...\n", DEVNAM(sc));
+		umb_packet_service(sc, 1);
+		break;
+	case UMB_S_ATTACHED:
+		sc->sc_tx_seq = 0;
+		if (!umb_alloc_xfers(sc)) {
+			umb_free_xfers(sc);
+			printf("%s: allocation of xfers failed\n", DEVNAM(sc));
+			break;
+		}
+		DPRINTF("%s: init: connecting ...\n", DEVNAM(sc));
+		umb_connect(sc);
+		break;
+	case UMB_S_CONNECTED:
+		DPRINTF("%s: init: getting IP config ...\n", DEVNAM(sc));
+		umb_qry_ipconfig(sc);
+		break;
+	case UMB_S_UP:
+		DPRINTF("%s: init: reached state UP\n", DEVNAM(sc));
+		if (!umb_alloc_bulkpipes(sc)) {
+			printf("%s: opening bulk pipes failed\n", DEVNAM(sc));
+			umb_down(sc, 1);
+		}
+		break;
+	}
+	if (sc->sc_state < UMB_S_UP)
+		callout_schedule(&sc->sc_statechg_timer,
+		    UMB_STATE_CHANGE_TIMEOUT * hz);
+	else
+		callout_stop(&sc->sc_statechg_timer);
+	return;
+}
+
+void
+umb_down(struct umb_softc *sc, int force)
+{
+	umb_close_bulkpipes(sc);
+	if (sc->sc_state < UMB_S_CONNECTED)
+		umb_free_xfers(sc);
+
+	switch (sc->sc_state) {
+	case UMB_S_UP:
+	case UMB_S_CONNECTED:
+		DPRINTF("%s: stop: disconnecting ...\n", DEVNAM(sc));
+		umb_disconnect(sc);
+		if (!force)
+			break;
+		/*FALLTHROUGH*/
+	case UMB_S_ATTACHED:
+		DPRINTF("%s: stop: detaching ...\n", DEVNAM(sc));
+		umb_packet_service(sc, 0);
+		if (!force)
+			break;
+		/*FALLTHROUGH*/
+	case UMB_S_SIMREADY:
+	case UMB_S_RADIO:
+		DPRINTF("%s: stop: turning radio off ...\n", DEVNAM(sc));
+		umb_radio(sc, 0);
+		if (!force)
+			break;
+		/*FALLTHROUGH*/
+	case UMB_S_CID:
+	case UMB_S_OPEN:
+	case UMB_S_DOWN:
+		/* Do not close the device */
+		DPRINTF("%s: stop: reached state DOWN\n", DEVNAM(sc));
+		break;
+	}
+	if (force)
+		sc->sc_state = UMB_S_OPEN;
+
+	if (sc->sc_state > UMB_S_OPEN)
+		callout_schedule(&sc->sc_statechg_timer,
+		    UMB_STATE_CHANGE_TIMEOUT * hz);
+	else
+		callout_stop(&sc->sc_statechg_timer);
+}
+
+void
+umb_get_response_task(void *arg)
+{
+	struct umb_softc *sc = arg;
+	int	 len;
+	int	 s;
+
+	/*
+	 * Function is required to send on RESPONSE_AVAILABLE notification for
+	 * each encapsulated response that is to be processed by the host.
+	 * But of course, we can receive multiple notifications before the
+	 * response task is run.
+	 */
+	s = splusb();
+	while (sc->sc_nresp > 0) {
+		--sc->sc_nresp;
+		len = sc->sc_ctrl_len;
+		if (umb_get_encap_response(sc, sc->sc_resp_buf, &len))
+			umb_decode_response(sc, sc->sc_resp_buf, len);
+	}
+	splx(s);
+}
+
+void
+umb_decode_response(struct umb_softc *sc, void *response, int len)
+{
+	struct mbim_msghdr *hdr = response;
+	struct mbim_fragmented_msg_hdr *fraghdr;
+	uint32_t type;
+
+	DPRINTFN(3, "%s: got response: len %d\n", DEVNAM(sc), len);
+	DDUMPN(4, response, len);
+
+	if (len < sizeof (*hdr) || le32toh(hdr->len) != len) {
+		/*
+		 * We should probably cancel a transaction, but since the
+		 * message is too short, we cannot decode the transaction
+		 * id (tid) and hence don't know, whom to cancel. Must wait
+		 * for the timeout.
+		 */
+		DPRINTF("%s: received short response (len %d)\n",
+		    DEVNAM(sc), len);
+		return;
+	}
+
+	/*
+	 * XXX FIXME: if message is fragmented, store it until last frag
+	 *	is received and then re-assemble all fragments.
+	 */
+	type = le32toh(hdr->type);
+	switch (type) {
+	case MBIM_INDICATE_STATUS_MSG:
+	case MBIM_COMMAND_DONE:
+		fraghdr = response;
+		if (le32toh(fraghdr->frag.nfrag) != 1) {
+			DPRINTF("%s: discarding fragmented messages\n",
+			    DEVNAM(sc));
+			return;
+		}
+		break;
+	default:
+		break;
+	}
+
+	DPRINTF("%s: <- rcv %s (tid %u)\n", DEVNAM(sc), umb_request2str(type),
+	    le32toh(hdr->tid));
+	switch (type) {
+	case MBIM_FUNCTION_ERROR_MSG:
+	case MBIM_HOST_ERROR_MSG:
+	{
+		struct mbim_f2h_hosterr *e;
+		int	 err;
+
+		if (len >= sizeof (*e)) {
+			e = response;
+			err = le32toh(e->err);
+
+			DPRINTF("%s: %s message, error %s (tid %u)\n",
+			    DEVNAM(sc), umb_request2str(type),
+			    umb_error2str(err), le32toh(hdr->tid));
+			if (err == MBIM_ERROR_NOT_OPENED)
+				umb_newstate(sc, UMB_S_DOWN, 0);
+		}
+		break;
+	}
+	case MBIM_INDICATE_STATUS_MSG:
+		umb_handle_indicate_status_msg(sc, response, len);
+		break;
+	case MBIM_OPEN_DONE:
+		umb_handle_opendone_msg(sc, response, len);
+		break;
+	case MBIM_CLOSE_DONE:
+		umb_handle_closedone_msg(sc, response, len);
+		break;
+	case MBIM_COMMAND_DONE:
+		umb_command_done(sc, response, len);
+		break;
+	default:
+		DPRINTF("%s: discard messsage %s\n", DEVNAM(sc),
+		    umb_request2str(type));
+		break;
+	}
+}
+
+void
+umb_handle_indicate_status_msg(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_f2h_indicate_status *m = data;
+	uint32_t infolen;
+	uint32_t cid;
+
+	if (len < sizeof (*m)) {
+		DPRINTF("%s: discard short %s messsage\n", DEVNAM(sc),
+		    umb_request2str(le32toh(m->hdr.type)));
+		return;
+	}
+	if (memcmp(m->devid, umb_uuid_basic_connect, sizeof (m->devid))) {
+		DPRINTF("%s: discard %s messsage for other UUID '%s'\n",
+		    DEVNAM(sc), umb_request2str(le32toh(m->hdr.type)),
+		    umb_uuid2str(m->devid));
+		return;
+	}
+	infolen = le32toh(m->infolen);
+	if (len < sizeof (*m) + infolen) {
+		DPRINTF("%s: discard truncated %s messsage (want %d, got %d)\n",
+		    DEVNAM(sc), umb_request2str(le32toh(m->hdr.type)),
+		    (int)sizeof (*m) + infolen, len);
+		return;
+	}
+
+	cid = le32toh(m->cid);
+	DPRINTF("%s: indicate %s status\n", DEVNAM(sc), umb_cid2str(cid));
+	umb_decode_cid(sc, cid, m->info, infolen);
+}
+
+void
+umb_handle_opendone_msg(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_f2h_openclosedone *resp = data;
+	struct ifnet *ifp = GET_IFP(sc);
+	uint32_t status;
+
+	status = le32toh(resp->status);
+	if (status == MBIM_STATUS_SUCCESS) {
+		if (sc->sc_maxsessions == 0) {
+			umb_cmd(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_QRY, NULL,
+			    0);
+			umb_cmd(sc, MBIM_CID_PIN, MBIM_CMDOP_QRY, NULL, 0);
+			umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_QRY,
+			    NULL, 0);
+		}
+		umb_newstate(sc, UMB_S_OPEN, UMB_NS_DONT_DROP);
+	} else if (ifp->if_flags & IFF_DEBUG)
+		log(LOG_ERR, "%s: open error: %s\n", DEVNAM(sc),
+		    umb_status2str(status));
+	return;
+}
+
+void
+umb_handle_closedone_msg(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_f2h_openclosedone *resp = data;
+	uint32_t status;
+
+	status = le32toh(resp->status);
+	if (status == MBIM_STATUS_SUCCESS)
+		umb_newstate(sc, UMB_S_DOWN, 0);
+	else
+		DPRINTF("%s: close error: %s\n", DEVNAM(sc),
+		    umb_status2str(status));
+	return;
+}
+
+static inline void
+umb_getinfobuf(char *in, int inlen, uint32_t offs, uint32_t sz,
+    void *out, size_t outlen)
+{
+	offs = le32toh(offs);
+	sz = le32toh(sz);
+	if (inlen >= offs + sz) {
+		memset(out, 0, outlen);
+		memcpy(out, in + offs, MIN(sz, outlen));
+	}
+}
+
+static inline int
+umb_padding(void *data, int len, size_t sz)
+{
+	char *p = data;
+	int np = 0;
+
+	while (len < sz && (len % 4) != 0) {
+		*p++ = '\0';
+		len++;
+		np++;
+	}
+	return np;
+}
+
+static inline int
+umb_addstr(void *buf, size_t bufsz, int *offs, void *str, int slen,
+    uint32_t *offsmember, uint32_t *sizemember)
+{
+	if (*offs + slen > bufsz)
+		return 0;
+
+	*sizemember = htole32((uint32_t)slen);
+	if (slen && str) {
+		*offsmember = htole32((uint32_t)*offs);
+		memcpy((char *)buf + *offs, str, slen);
+		*offs += slen;
+		*offs += umb_padding(buf, *offs, bufsz);
+	} else
+		*offsmember = htole32(0);
+	return 1;
+}
+
+int
+umb_decode_register_state(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_cid_registration_state_info *rs = data;
+	struct ifnet *ifp = GET_IFP(sc);
+
+	if (len < sizeof (*rs))
+		return 0;
+	sc->sc_info.nwerror = le32toh(rs->nwerror);
+	sc->sc_info.regstate = le32toh(rs->regstate);
+	sc->sc_info.regmode = le32toh(rs->regmode);
+	sc->sc_info.cellclass = le32toh(rs->curcellclass);
+
+	/* XXX should we remember the provider_id? */
+	umb_getinfobuf(data, len, rs->provname_offs, rs->provname_size,
+	    sc->sc_info.provider, sizeof (sc->sc_info.provider));
+	umb_getinfobuf(data, len, rs->roamingtxt_offs, rs->roamingtxt_size,
+	    sc->sc_info.roamingtxt, sizeof (sc->sc_info.roamingtxt));
+
+	DPRINTFN(2, "%s: %s, availclass 0x%x, class 0x%x, regmode %d\n",
+	    DEVNAM(sc), umb_regstate(sc->sc_info.regstate),
+	    le32toh(rs->availclasses), sc->sc_info.cellclass,
+	    sc->sc_info.regmode);
+
+	if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING &&
+	    !sc->sc_roaming &&
+	    sc->sc_info.activation == MBIM_ACTIVATION_STATE_ACTIVATED) {
+		if (ifp->if_flags & IFF_DEBUG)
+			log(LOG_INFO,
+			    "%s: disconnecting from roaming network\n",
+			    DEVNAM(sc));
+		umb_disconnect(sc);
+	}
+	return 1;
+}
+
+int
+umb_decode_devices_caps(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_cid_device_caps *dc = data;
+
+	if (len < sizeof (*dc))
+		return 0;
+	sc->sc_maxsessions = le32toh(dc->max_sessions);
+	sc->sc_info.supportedclasses = le32toh(dc->dataclass);
+	umb_getinfobuf(data, len, dc->devid_offs, dc->devid_size,
+	    sc->sc_info.devid, sizeof (sc->sc_info.devid));
+	umb_getinfobuf(data, len, dc->fwinfo_offs, dc->fwinfo_size,
+	    sc->sc_info.fwinfo, sizeof (sc->sc_info.fwinfo));
+	umb_getinfobuf(data, len, dc->hwinfo_offs, dc->hwinfo_size,
+	    sc->sc_info.hwinfo, sizeof (sc->sc_info.hwinfo));
+	DPRINTFN(2, "%s: max sessions %d, supported classes 0x%x\n",
+	    DEVNAM(sc), sc->sc_maxsessions, sc->sc_info.supportedclasses);
+	return 1;
+}
+
+int
+umb_decode_subscriber_status(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_cid_subscriber_ready_info *si = data;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	npn;
+
+	if (len < sizeof (*si))
+		return 0;
+	sc->sc_info.sim_state = le32toh(si->ready);
+
+	umb_getinfobuf(data, len, si->sid_offs, si->sid_size,
+	    sc->sc_info.sid, sizeof (sc->sc_info.sid));
+	umb_getinfobuf(data, len, si->icc_offs, si->icc_size,
+	    sc->sc_info.iccid, sizeof (sc->sc_info.iccid));
+
+	npn = le32toh(si->no_pn);
+	if (npn > 0)
+		umb_getinfobuf(data, len, si->pn[0].offs, si->pn[0].size,
+		    sc->sc_info.pn, sizeof (sc->sc_info.pn));
+	else
+		memset(sc->sc_info.pn, 0, sizeof (sc->sc_info.pn));
+
+	if (sc->sc_info.sim_state == MBIM_SIMSTATE_LOCKED)
+		sc->sc_info.pin_state = UMB_PUK_REQUIRED;
+	if (ifp->if_flags & IFF_DEBUG)
+		log(LOG_INFO, "%s: SIM %s\n", DEVNAM(sc),
+		    umb_simstate(sc->sc_info.sim_state));
+	if (sc->sc_info.sim_state == MBIM_SIMSTATE_INITIALIZED)
+		umb_newstate(sc, UMB_S_SIMREADY, UMB_NS_DONT_DROP);
+	return 1;
+}
+
+int
+umb_decode_radio_state(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_cid_radio_state_info *rs = data;
+	struct ifnet *ifp = GET_IFP(sc);
+
+	if (len < sizeof (*rs))
+		return 0;
+
+	sc->sc_info.hw_radio_on =
+	    (le32toh(rs->hw_state) == MBIM_RADIO_STATE_ON) ? 1 : 0;
+	sc->sc_info.sw_radio_on =
+	    (le32toh(rs->sw_state) == MBIM_RADIO_STATE_ON) ? 1 : 0;
+	if (!sc->sc_info.hw_radio_on) {
+		printf("%s: radio is disabled by hardware switch\n",
+		    DEVNAM(sc));
+		/*
+		 * XXX do we need a time to poll the state of the rfkill switch
+		 *	or will the device send an unsolicited notification
+		 *	in case the state changes?
+		 */
+		umb_newstate(sc, UMB_S_OPEN, 0);
+	} else if (!sc->sc_info.sw_radio_on) {
+		if (ifp->if_flags & IFF_DEBUG)
+			log(LOG_INFO, "%s: radio is off\n", DEVNAM(sc));
+		umb_newstate(sc, UMB_S_OPEN, 0);
+	} else
+		umb_newstate(sc, UMB_S_RADIO, UMB_NS_DONT_DROP);
+	return 1;
+}
+
+int
+umb_decode_pin(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_cid_pin_info *pi = data;
+	struct ifnet *ifp = GET_IFP(sc);
+	uint32_t	attempts_left;
+
+	if (len < sizeof (*pi))
+		return 0;
+
+	attempts_left = le32toh(pi->remaining_attempts);
+	if (attempts_left != 0xffffffff)
+		sc->sc_info.pin_attempts_left = attempts_left;
+
+	switch (le32toh(pi->state)) {
+	case MBIM_PIN_STATE_UNLOCKED:
+		sc->sc_info.pin_state = UMB_PIN_UNLOCKED;
+		break;
+	case MBIM_PIN_STATE_LOCKED:
+		switch (le32toh(pi->type)) {
+		case MBIM_PIN_TYPE_PIN1:
+			sc->sc_info.pin_state = UMB_PIN_REQUIRED;
+			break;
+		case MBIM_PIN_TYPE_PUK1:
+			sc->sc_info.pin_state = UMB_PUK_REQUIRED;
+			break;
+		case MBIM_PIN_TYPE_PIN2:
+		case MBIM_PIN_TYPE_PUK2:
+			/* Assume that PIN1 was accepted */
+			sc->sc_info.pin_state = UMB_PIN_UNLOCKED;
+			break;
+		}
+		break;
+	}
+	if (ifp->if_flags & IFF_DEBUG)
+		log(LOG_INFO, "%s: %s state %s (%d attempts left)\n",
+		    DEVNAM(sc), umb_pin_type(le32toh(pi->type)),
+		    (le32toh(pi->state) == MBIM_PIN_STATE_UNLOCKED) ?
+			"unlocked" : "locked",
+		    le32toh(pi->remaining_attempts));
+
+	/*
+	 * In case the PIN was set after IFF_UP, retrigger the state machine
+	 */
+	usb_add_task(sc->sc_udev, &sc->sc_umb_task, USB_TASKQ_DRIVER);
+	return 1;
+}
+
+int
+umb_decode_packet_service(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_cid_packet_service_info *psi = data;
+	int	 state, highestclass;
+	uint64_t up_speed, down_speed;
+	struct ifnet *ifp = GET_IFP(sc);
+
+	if (len < sizeof (*psi))
+		return 0;
+
+	sc->sc_info.nwerror = le32toh(psi->nwerror);
+	state = le32toh(psi->state);
+	highestclass = le32toh(psi->highest_dataclass);
+	up_speed = le64toh(psi->uplink_speed);
+	down_speed = le64toh(psi->downlink_speed);
+	if (sc->sc_info.packetstate  != state ||
+	    sc->sc_info.uplink_speed != up_speed ||
+	    sc->sc_info.downlink_speed != down_speed) {
+		if (ifp->if_flags & IFF_DEBUG) {
+			log(LOG_INFO, "%s: packet service ", DEVNAM(sc));
+			if (sc->sc_info.packetstate  != state)
+				addlog("changed from %s to ",
+				    umb_packet_state(sc->sc_info.packetstate));
+			addlog("%s, class %s, speed: %" PRIu64 " up / %" PRIu64 " down\n",
+			    umb_packet_state(state), 
+			    umb_dataclass(highestclass), up_speed, down_speed);
+		}
+	}
+	sc->sc_info.packetstate = state;
+	sc->sc_info.highestclass = highestclass;
+	sc->sc_info.uplink_speed = up_speed;
+	sc->sc_info.downlink_speed = down_speed;
+
+	if (sc->sc_info.regmode == MBIM_REGMODE_AUTOMATIC) {
+		/*
+		 * For devices using automatic registration mode, just proceed,
+		 * once registration has completed.
+		 */
+		if (ifp->if_flags & IFF_UP) {
+			switch (sc->sc_info.regstate) {
+			case MBIM_REGSTATE_HOME:
+			case MBIM_REGSTATE_ROAMING:
+			case MBIM_REGSTATE_PARTNER:
+				umb_newstate(sc, UMB_S_ATTACHED,
+				    UMB_NS_DONT_DROP);
+				break;
+			default:
+				break;
+			}
+		} else
+			umb_newstate(sc, UMB_S_SIMREADY, UMB_NS_DONT_RAISE);
+	} else switch (sc->sc_info.packetstate) {
+	case MBIM_PKTSERVICE_STATE_ATTACHED:
+		umb_newstate(sc, UMB_S_ATTACHED, UMB_NS_DONT_DROP);
+		break;
+	case MBIM_PKTSERVICE_STATE_DETACHED:
+		umb_newstate(sc, UMB_S_SIMREADY, UMB_NS_DONT_RAISE);
+		break;
+	}
+	return 1;
+}
+
+int
+umb_decode_signal_state(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_cid_signal_state *ss = data;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 rssi;
+
+	if (len < sizeof (*ss))
+		return 0;
+
+	if (le32toh(ss->rssi) == 99)
+		rssi = UMB_VALUE_UNKNOWN;
+	else {
+		rssi = -113 + 2 * le32toh(ss->rssi);
+		if ((ifp->if_flags & IFF_DEBUG) && sc->sc_info.rssi != rssi &&
+		    sc->sc_state >= UMB_S_CONNECTED)
+			log(LOG_INFO, "%s: rssi %d dBm\n", DEVNAM(sc), rssi);
+	}
+	sc->sc_info.rssi = rssi;
+	sc->sc_info.ber = le32toh(ss->err_rate);
+	if (sc->sc_info.ber == -99)
+		sc->sc_info.ber = UMB_VALUE_UNKNOWN;
+	return 1;
+}
+
+int
+umb_decode_connect_info(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_cid_connect_info *ci = data;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 act;
+
+	if (len < sizeof (*ci))
+		return 0;
+
+	if (le32toh(ci->sessionid) != umb_session_id) {
+		DPRINTF("%s: discard connection info for session %u\n",
+		    DEVNAM(sc), le32toh(ci->sessionid));
+		return 1;
+	}
+	if (memcmp(ci->context, umb_uuid_context_internet,
+	    sizeof (ci->context))) {
+		DPRINTF("%s: discard connection info for other context\n",
+		    DEVNAM(sc));
+		return 1;
+	}
+	act = le32toh(ci->activation);
+	if (sc->sc_info.activation != act) {
+		if (ifp->if_flags & IFF_DEBUG)
+			log(LOG_INFO, "%s: connection %s\n", DEVNAM(sc),
+			    umb_activation(act));
+		if ((ifp->if_flags & IFF_DEBUG) &&
+		    le32toh(ci->iptype) != MBIM_CONTEXT_IPTYPE_DEFAULT &&
+		    le32toh(ci->iptype) != MBIM_CONTEXT_IPTYPE_IPV4)
+			log(LOG_DEBUG, "%s: got iptype %d connection\n",
+			    DEVNAM(sc), le32toh(ci->iptype));
+
+		sc->sc_info.activation = act;
+		sc->sc_info.nwerror = le32toh(ci->nwerror);
+
+		if (sc->sc_info.activation == MBIM_ACTIVATION_STATE_ACTIVATED)
+			umb_newstate(sc, UMB_S_CONNECTED, UMB_NS_DONT_DROP);
+		else if (sc->sc_info.activation ==
+		    MBIM_ACTIVATION_STATE_DEACTIVATED)
+			umb_newstate(sc, UMB_S_ATTACHED, 0);
+		/* else: other states are purely transitional */
+	}
+	return 1;
+}
+
+int
+umb_decode_ip_configuration(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_cid_ip_configuration_info *ic = data;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 s;
+	uint32_t avail;
+	uint32_t val;
+	int	 n, i;
+	int	 off;
+	struct mbim_cid_ipv4_element ipv4elem;
+	struct in_aliasreq ifra;
+	struct sockaddr_in *sin;
+	int	 state = -1;
+	int	 rv;
+
+	if (len < sizeof (*ic))
+		return 0;
+	if (le32toh(ic->sessionid) != umb_session_id) {
+		DPRINTF("%s: ignore IP configration for session id %d\n",
+		    DEVNAM(sc), le32toh(ic->sessionid));
+		return 0;
+	}
+	s = splnet();
+
+	/*
+	 * IPv4 configuation
+	 */
+	avail = le32toh(ic->ipv4_available);
+	if (avail & MBIM_IPCONF_HAS_ADDRINFO) {
+		n = le32toh(ic->ipv4_naddr);
+		off = le32toh(ic->ipv4_addroffs);
+
+		if (n == 0 || off + sizeof (ipv4elem) > len)
+			goto done;
+
+		/* Only pick the first one */
+		memcpy(&ipv4elem, (char *)data + off, sizeof (ipv4elem));
+		ipv4elem.prefixlen = le32toh(ipv4elem.prefixlen);
+
+		memset(&ifra, 0, sizeof (ifra));
+		sin = (struct sockaddr_in *)&ifra.ifra_addr;
+		sin->sin_family = AF_INET;
+		sin->sin_len = sizeof (ifra.ifra_addr);
+		sin->sin_addr.s_addr = ipv4elem.addr;
+
+		sin = (struct sockaddr_in *)&ifra.ifra_dstaddr;
+		sin->sin_family = AF_INET;
+		sin->sin_len = sizeof (ifra.ifra_dstaddr);
+		if (avail & MBIM_IPCONF_HAS_GWINFO) {
+			off = le32toh(ic->ipv4_gwoffs);
+			sin->sin_addr.s_addr = *((uint32_t *)((char *)data + off));
+		}
+
+		sin = (struct sockaddr_in *)&ifra.ifra_mask;
+		sin->sin_family = AF_INET;
+		sin->sin_len = sizeof (ifra.ifra_mask);
+		in_len2mask(&sin->sin_addr, ipv4elem.prefixlen);
+
+		rv = ifioctl_common(ifp, SIOCAIFADDR, &ifra);
+		if (rv == 0) {
+#if 0
+			if (ifp->if_flags & IFF_DEBUG)
+				log(LOG_INFO, "%s: IPv4 addr %s, mask %s, "
+				    "gateway %s\n", device_xname(ifp->if_softc),
+				    umb_ntop(sintosa(&ifra.ifra_addr)),
+				    umb_ntop(sintosa(&ifra.ifra_mask)),
+				    umb_ntop(sintosa(&ifra.ifra_dstaddr)));
+#endif
+			state = UMB_S_UP;
+		} else
+			printf("%s: unable to set IPv4 address, error %d\n",
+			    device_xname(ifp->if_softc), rv);
+	}
+
+	memset(sc->sc_info.ipv4dns, 0, sizeof (sc->sc_info.ipv4dns));
+	if (avail & MBIM_IPCONF_HAS_DNSINFO) {
+		n = le32toh(ic->ipv4_ndnssrv);
+		off = le32toh(ic->ipv4_dnssrvoffs);
+		i = 0;
+		while (n-- > 0) {
+			if (off + sizeof (uint32_t) > len)
+				break;
+			val = *((uint32_t *)(data + off));
+			if (i < UMB_MAX_DNSSRV)
+				sc->sc_info.ipv4dns[i++] = val;
+			off += sizeof (uint32_t);
+		}
+	}
+
+	if ((avail & MBIM_IPCONF_HAS_MTUINFO)) {
+		val = le32toh(ic->ipv4_mtu);
+		if (ifp->if_hardmtu != val && val <= sc->sc_maxpktlen) {
+			ifp->if_hardmtu = val;
+			if (ifp->if_mtu > val)
+				ifp->if_mtu = val;
+			if (ifp->if_flags & IFF_DEBUG)
+				log(LOG_INFO, "%s: MTU %d\n", DEVNAM(sc), val);
+		}
+	}
+
+	avail = le32toh(ic->ipv6_available);
+	if ((ifp->if_flags & IFF_DEBUG) && avail & MBIM_IPCONF_HAS_ADDRINFO) {
+		/* XXX FIXME: IPv6 configuation missing */
+		log(LOG_INFO, "%s: ignoring IPv6 configuration\n", DEVNAM(sc));
+	}
+	if (state != -1)
+		umb_newstate(sc, state, 0);
+
+done:
+	splx(s);
+	return 1;
+}
+
+void
+umb_rx(struct umb_softc *sc)
+{
+	usbd_setup_xfer(sc->sc_rx_xfer, sc, sc->sc_rx_buf,
+	    sc->sc_rx_bufsz, USBD_SHORT_XFER_OK,
+	    USBD_NO_TIMEOUT, umb_rxeof);
+	usbd_transfer(sc->sc_rx_xfer);
+}
+
+void
+umb_rxeof(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+	struct umb_softc *sc = priv;
+	struct ifnet *ifp = GET_IFP(sc);
+
+	if (!(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));
+			umb_down(sc, 1);
+		}
+	} else {
+		sc->sc_rx_nerr = 0;
+		umb_decap(sc, xfer);
+	}
+
+	umb_rx(sc);
+	return;
+}
+
+int
+umb_encap(struct umb_softc *sc, struct mbuf *m)
+{
+	struct ncm_header16 *hdr;
+	struct ncm_pointer16 *ptr;
+	usbd_status  err;
+	int len;
+
+	/* All size constraints have been validated by the caller! */
+	hdr = (struct ncm_header16 *)sc->sc_tx_buf;
+	ptr = (struct ncm_pointer16 *)(hdr + 1);
+	USETDW(hdr->dwSignature, NCM_HDR16_SIG);
+	USETW(hdr->wHeaderLength, sizeof (*hdr));
+	USETW(hdr->wSequence, sc->sc_tx_seq);
+	sc->sc_tx_seq++;
+
+	len = m->m_pkthdr.len;
+
+	USETDW(ptr->dwSignature, MBIM_NCM_NTH16_SIG(umb_session_id));
+	USETW(ptr->wLength, sizeof(*ptr));
+	USETW(ptr->wNextNdpIndex, 0);
+	USETW(ptr->dgram[0].wDatagramIndex, MBIM_HDR16_LEN);
+	USETW(ptr->dgram[0].wDatagramLen, len);
+	USETW(ptr->dgram[1].wDatagramIndex, 0);
+	USETW(ptr->dgram[1].wDatagramLen, 0);
+
+	m_copydata(m, 0, len, ptr + 1);
+	sc->sc_tx_m = m;
+	len += MBIM_HDR16_LEN;
+	USETW(hdr->wBlockLength, len);
+
+	DPRINTFN(3, "%s: encap %d bytes\n", DEVNAM(sc), len);
+	DDUMPN(5, sc->sc_tx_buf, len);
+	usbd_setup_xfer(sc->sc_tx_xfer, sc, sc->sc_tx_buf, len,
+	    USBD_FORCE_SHORT_XFER, umb_xfer_tout, umb_txeof);
+	err = usbd_transfer(sc->sc_tx_xfer);
+	if (err != USBD_IN_PROGRESS) {
+		DPRINTF("%s: start tx error: %s\n", DEVNAM(sc),
+		    usbd_errstr(err));
+		return 0;
+	}
+	return 1;
+}
+
+void
+umb_txeof(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+	struct umb_softc *sc = priv;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 s;
+
+	s = splnet();
+	ifq_clr_oactive(&ifp->if_snd);
+	ifp->if_timer = 0;
+
+	m_freem(sc->sc_tx_m);
+	sc->sc_tx_m = NULL;
+
+	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_IS_EMPTY(&ifp->if_snd) == 0)
+		umb_start(ifp);
+
+	splx(s);
+}
+
+void
+umb_decap(struct umb_softc *sc, struct usbd_xfer *xfer)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 s;
+	char	*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;
+	int	 hlen, blen;
+	int	 ptrlen, ptroff, dgentryoff;
+	uint32_t doff, dlen;
+	struct mbuf *m;
+
+	usbd_get_xfer_status(xfer, NULL, (void **)&buf, &len, NULL);
+	DPRINTFN(4, "%s: recv %d bytes\n", DEVNAM(sc), len);
+	DDUMPN(5, buf, len);
+	s = splnet();
+	if (len < sizeof (*hdr16))
+		goto toosmall;
+
+	hdr16 = (struct ncm_header16 *)buf;
+	hsig = UGETDW(hdr16->dwSignature);
+	hlen = UGETW(hdr16->wHeaderLength);
+	if (len < hlen)
+		goto toosmall;
+	if (len > sc->sc_rx_bufsz) {
+		DPRINTF("%s: packet too large (%d)\n", DEVNAM(sc), len);
+		goto fail;
+	}
+	switch (hsig) {
+	case NCM_HDR16_SIG:
+		blen = UGETW(hdr16->wBlockLength);
+		ptroff = UGETW(hdr16->wNdpIndex);
+		if (hlen != sizeof (*hdr16)) {
+			DPRINTF("%s: bad header len %d for NTH16 (exp %zu)\n",
+			    DEVNAM(sc), hlen, sizeof (*hdr16));
+			goto fail;
+		}
+		break;
+	case NCM_HDR32_SIG:
+		hdr32 = (struct ncm_header32 *)hdr16;
+		blen = UGETDW(hdr32->dwBlockLength);
+		ptroff = UGETDW(hdr32->dwNdpIndex);
+		if (hlen != sizeof (*hdr32)) {
+			DPRINTF("%s: bad header len %d for NTH32 (exp %zu)\n",
+			    DEVNAM(sc), hlen, sizeof (*hdr32));
+			goto fail;
+		}
+		break;
+	default:
+		DPRINTF("%s: unsupported NCM header signature (0x%08x)\n",
+		    DEVNAM(sc), hsig);
+		goto fail;
+	}
+	if (len < blen) {
+		DPRINTF("%s: bad NTB len (%d) for %d bytes of data\n",
+		    DEVNAM(sc), blen, len);
+		goto fail;
+	}
+
+	ptr16 = (struct ncm_pointer16 *)(buf + ptroff);
+	psig = UGETDW(ptr16->dwSignature);
+	ptrlen = UGETW(ptr16->wLength);
+	if (len < ptrlen + ptroff)
+		goto toosmall;
+	if (!MBIM_NCM_NTH16_ISISG(psig) && !MBIM_NCM_NTH32_ISISG(psig)) {
+		DPRINTF("%s: unsupported NCM pointer signature (0x%08x)\n",
+		    DEVNAM(sc), psig);
+		goto fail;
+	}
+
+	switch (hsig) {
+	case NCM_HDR16_SIG:
+		dgentryoff = offsetof(struct ncm_pointer16, dgram);
+		break;
+	case NCM_HDR32_SIG:
+		dgentryoff = offsetof(struct ncm_pointer32, dgram);
+		break;
+	default:
+		goto fail;
+	}
+
+	while (dgentryoff < ptrlen) {
+		switch (hsig) {
+		case NCM_HDR16_SIG:
+			if (ptroff + 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 (ptroff + 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;
+		}
+
+		/* Terminating zero entry */
+		if (dlen == 0 || doff == 0)
+			break;
+		if (len < dlen + doff) {
+			/* Skip giant datagram but continue processing */
+			DPRINTF("%s: datagram too large (%d @ off %d)\n",
+			    DEVNAM(sc), dlen, doff);
+			continue;
+		}
+
+		dp = buf + doff;
+		DPRINTFN(3, "%s: decap %d bytes\n", DEVNAM(sc), dlen);
+#if 0
+		m = m_devget(dp, dlen, 0);
+#else
+		m = m_devget(dp, dlen, 0, ifp, NULL);
+#endif
+		if (m == NULL) {
+			ifp->if_iqdrops++;
+			continue;
+		}
+
+		if_percpuq_enqueue((ifp)->if_percpuq, (m));
+	}
+done:
+	splx(s);
+	return;
+toosmall:
+	DPRINTF("%s: packet too small (%d)\n", DEVNAM(sc), len);
+fail:
+	ifp->if_ierrors++;
+	splx(s);
+}
+
+usbd_status
+umb_send_encap_command(struct umb_softc *sc, void *data, int len)
+{
+	struct usbd_xfer *xfer;
+	usb_device_request_t req;
+	char *buf;
+
+	if (len > sc->sc_ctrl_len)
+		return USBD_INVAL;
+
+	if (usbd_create_xfer(sc->sc_udev->ud_pipe0, len, 0, 0, &xfer) != 0)
+		return USBD_NOMEM;
+	memcpy(buf, data, len);
+
+	/* XXX FIXME: if (total len > sc->sc_ctrl_len) => must fragment */
+	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+	req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND;
+	USETW(req.wValue, 0);
+	USETW(req.wIndex, sc->sc_ctrl_ifaceno);
+	USETW(req.wLength, len);
+	DELAY(umb_delay);
+	return usbd_request_async(xfer, &req, NULL, NULL);
+}
+
+int
+umb_get_encap_response(struct umb_softc *sc, void *buf, int *len)
+{
+	usb_device_request_t req;
+	usbd_status err;
+
+	req.bmRequestType = UT_READ_CLASS_INTERFACE;
+	req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE;
+	USETW(req.wValue, 0);
+	USETW(req.wIndex, sc->sc_ctrl_ifaceno);
+	USETW(req.wLength, *len);
+	/* XXX FIXME: re-assemble fragments */
+
+	DELAY(umb_delay);
+	err = usbd_do_request_flags(sc->sc_udev, &req, buf, USBD_SHORT_XFER_OK,
+	    len, umb_xfer_tout);
+	if (err == USBD_NORMAL_COMPLETION)
+		return 1;
+	DPRINTF("%s: ctrl recv: %s\n", DEVNAM(sc), usbd_errstr(err));
+	return 0;
+}
+
+void
+umb_ctrl_msg(struct umb_softc *sc, uint32_t req, void *data, int len)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+	uint32_t tid;
+	struct mbim_msghdr *hdr = data;
+	usbd_status err;
+	int	 s;
+
+#if 0
+	assertwaitok();
+#endif
+	if (len < sizeof (*hdr))
+		return;
+	tid = ++sc->sc_tid;
+
+	hdr->type = htole32(req);
+	hdr->len = htole32(len);
+	hdr->tid = htole32(tid);
+
+#ifdef UMB_DEBUG
+	if (umb_debug) {
+		const char *op, *str;
+		if (req == MBIM_COMMAND_MSG) {
+			struct mbim_h2f_cmd *c = data;
+			if (le32toh(c->op) == MBIM_CMDOP_SET)
+				op = "set";
+			else
+				op = "qry";
+			str = umb_cid2str(le32toh(c->cid));
+		} else {
+			op = "snd";
+			str = umb_request2str(req);
+		}
+		DPRINTF("%s: -> %s %s (tid %u)\n", DEVNAM(sc), op, str, tid);
+	}
+#endif
+	s = splusb();
+	err = umb_send_encap_command(sc, data, len);
+	splx(s);
+	if (err != USBD_NORMAL_COMPLETION) {
+		if (ifp->if_flags & IFF_DEBUG)
+			log(LOG_ERR, "%s: send %s msg (tid %u) failed: %s\n",
+			    DEVNAM(sc), umb_request2str(req), tid,
+			    usbd_errstr(err));
+
+		/* will affect other transactions, too */
+		usbd_abort_pipe(sc->sc_udev->ud_pipe0);
+	} else {
+		DPRINTFN(2, "%s: sent %s (tid %u)\n", DEVNAM(sc),
+		    umb_request2str(req), tid);
+		DDUMPN(3, data, len);
+	}
+	return;
+}
+
+void
+umb_open(struct umb_softc *sc)
+{
+	struct mbim_h2f_openmsg msg;
+
+	memset(&msg, 0, sizeof (msg));
+	msg.maxlen = htole32(sc->sc_ctrl_len);
+	umb_ctrl_msg(sc, MBIM_OPEN_MSG, &msg, sizeof (msg));
+	return;
+}
+
+void
+umb_close(struct umb_softc *sc)
+{
+	struct mbim_h2f_closemsg msg;
+
+	memset(&msg, 0, sizeof (msg));
+	umb_ctrl_msg(sc, MBIM_CLOSE_MSG, &msg, sizeof (msg));
+}
+
+int
+umb_setpin(struct umb_softc *sc, int op, int is_puk, void *pin, int pinlen,
+    void *newpin, int newpinlen)
+{
+	struct mbim_cid_pin cp;
+	int	 off;
+
+	if (pinlen == 0)
+		return 0;
+	if (pinlen < 0 || pinlen > MBIM_PIN_MAXLEN ||
+	    newpinlen < 0 || newpinlen > MBIM_PIN_MAXLEN ||
+	    op < 0 || op > MBIM_PIN_OP_CHANGE ||
+	    (is_puk && op != MBIM_PIN_OP_ENTER))
+		return EINVAL;
+
+	memset(&cp, 0, sizeof (cp));
+	cp.type = htole32(is_puk ? MBIM_PIN_TYPE_PUK1 : MBIM_PIN_TYPE_PIN1);
+
+	off = offsetof(struct mbim_cid_pin, data);
+	if (!umb_addstr(&cp, sizeof (cp), &off, pin, pinlen,
+	    &cp.pin_offs, &cp.pin_size))
+		return EINVAL;
+
+	cp.op  = htole32(op);
+	if (newpinlen) {
+		if (!umb_addstr(&cp, sizeof (cp), &off, newpin, newpinlen,
+		    &cp.newpin_offs, &cp.newpin_size))
+			return EINVAL;
+	} else {
+		if ((op == MBIM_PIN_OP_CHANGE) || is_puk)
+			return EINVAL;
+		if (!umb_addstr(&cp, sizeof (cp), &off, NULL, 0,
+		    &cp.newpin_offs, &cp.newpin_size))
+			return EINVAL;
+	}
+	umb_cmd(sc, MBIM_CID_PIN, MBIM_CMDOP_SET, &cp, off);
+	return 0;
+}
+
+void
+umb_setdataclass(struct umb_softc *sc)
+{
+	struct mbim_cid_registration_state rs;
+	uint32_t	 classes;
+
+	if (sc->sc_info.supportedclasses == MBIM_DATACLASS_NONE)
+		return;
+
+	memset(&rs, 0, sizeof (rs));
+	rs.regaction = htole32(MBIM_REGACTION_AUTOMATIC);
+	classes = sc->sc_info.supportedclasses;
+	if (sc->sc_info.preferredclasses != MBIM_DATACLASS_NONE)
+		classes &= sc->sc_info.preferredclasses;
+	rs.data_class = htole32(classes);
+	umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_SET, &rs, sizeof (rs));
+}
+
+void
+umb_radio(struct umb_softc *sc, int on)
+{
+	struct mbim_cid_radio_state s;
+
+	DPRINTF("%s: set radio %s\n", DEVNAM(sc), on ? "on" : "off");
+	memset(&s, 0, sizeof (s));
+	s.state = htole32(on ? MBIM_RADIO_STATE_ON : MBIM_RADIO_STATE_OFF);
+	umb_cmd(sc, MBIM_CID_RADIO_STATE, MBIM_CMDOP_SET, &s, sizeof (s));
+}
+
+void
+umb_allocate_cid(struct umb_softc *sc)
+{
+	umb_cmd1(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_SET,
+	    umb_qmi_alloc_cid, sizeof (umb_qmi_alloc_cid), umb_uuid_qmi_mbim);
+}
+
+void
+umb_send_fcc_auth(struct umb_softc *sc)
+{
+	uint8_t	 fccauth[sizeof (umb_qmi_fcc_auth)];
+
+	if (sc->sc_cid == -1) {
+		DPRINTF("%s: missing CID, cannot send FCC auth\n", DEVNAM(sc));
+		umb_allocate_cid(sc);
+		return;
+	}
+	memcpy(fccauth, umb_qmi_fcc_auth, sizeof (fccauth));
+	fccauth[UMB_QMI_CID_OFFS] = sc->sc_cid;
+	umb_cmd1(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_SET,
+	    fccauth, sizeof (fccauth), umb_uuid_qmi_mbim);
+}
+
+void
+umb_packet_service(struct umb_softc *sc, int attach)
+{
+	struct mbim_cid_packet_service	s;
+
+	DPRINTF("%s: %s packet service\n", DEVNAM(sc),
+	    attach ? "attach" : "detach");
+	memset(&s, 0, sizeof (s));
+	s.action = htole32(attach ?
+	    MBIM_PKTSERVICE_ACTION_ATTACH : MBIM_PKTSERVICE_ACTION_DETACH);
+	umb_cmd(sc, MBIM_CID_PACKET_SERVICE, MBIM_CMDOP_SET, &s, sizeof (s));
+}
+
+void
+umb_connect(struct umb_softc *sc)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+
+	if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING && !sc->sc_roaming) {
+		log(LOG_INFO, "%s: connection disabled in roaming network\n",
+		    DEVNAM(sc));
+		return;
+	}
+	if (ifp->if_flags & IFF_DEBUG)
+		log(LOG_DEBUG, "%s: connecting ...\n", DEVNAM(sc));
+	umb_send_connect(sc, MBIM_CONNECT_ACTIVATE);
+}
+
+void
+umb_disconnect(struct umb_softc *sc)
+{
+	struct ifnet *ifp = GET_IFP(sc);
+
+	if (ifp->if_flags & IFF_DEBUG)
+		log(LOG_DEBUG, "%s: disconnecting ...\n", DEVNAM(sc));
+	umb_send_connect(sc, MBIM_CONNECT_DEACTIVATE);
+}
+
+void
+umb_send_connect(struct umb_softc *sc, int command)
+{
+	struct mbim_cid_connect *c;
+	int	 off;
+
+	/* Too large or the stack */
+	c = malloc(sizeof (*c), M_DEVBUF, M_WAIT|M_ZERO);
+	c->sessionid = htole32(umb_session_id);
+	c->command = htole32(command);
+	off = offsetof(struct mbim_cid_connect, data);
+	if (!umb_addstr(c, sizeof (*c), &off, sc->sc_info.apn,
+	    sc->sc_info.apnlen, &c->access_offs, &c->access_size))
+		goto done;
+	/* XXX FIXME: support user name and passphrase */
+	c->user_offs = htole32(0);
+	c->user_size = htole32(0);
+	c->passwd_offs = htole32(0);
+	c->passwd_size = htole32(0);
+	c->authprot = htole32(MBIM_AUTHPROT_NONE);
+	c->compression = htole32(MBIM_COMPRESSION_NONE);
+	c->iptype = htole32(MBIM_CONTEXT_IPTYPE_IPV4);
+	memcpy(c->context, umb_uuid_context_internet, sizeof (c->context));
+	umb_cmd(sc, MBIM_CID_CONNECT, MBIM_CMDOP_SET, c, off);
+done:
+	free(c, sizeof (*c));
+	return;
+}
+
+void
+umb_qry_ipconfig(struct umb_softc *sc)
+{
+	struct mbim_cid_ip_configuration_info ipc;
+
+	memset(&ipc, 0, sizeof (ipc));
+	ipc.sessionid = htole32(umb_session_id);
+	umb_cmd(sc, MBIM_CID_IP_CONFIGURATION, MBIM_CMDOP_QRY,
+	    &ipc, sizeof (ipc));
+}
+
+void
+umb_cmd(struct umb_softc *sc, int cid, int op, void *data, int len)
+{
+	umb_cmd1(sc, cid, op, data, len, umb_uuid_basic_connect);
+}
+
+void
+umb_cmd1(struct umb_softc *sc, int cid, int op, void *data, int len,
+    uint8_t *uuid)
+{
+	struct mbim_h2f_cmd *cmd;
+	int	totlen;
+
+	/* XXX FIXME support sending fragments */
+	if (sizeof (*cmd) + len > sc->sc_ctrl_len) {
+		DPRINTF("%s: set %s msg too long: cannot send\n",
+		    DEVNAM(sc), umb_cid2str(cid));
+		return;
+	}
+	cmd = sc->sc_ctrl_msg;
+	memset(cmd, 0, sizeof (*cmd));
+	cmd->frag.nfrag = htole32(1);
+	memcpy(cmd->devid, uuid, sizeof (cmd->devid));
+	cmd->cid = htole32(cid);
+	cmd->op = htole32(op);
+	cmd->infolen = htole32(len);
+	totlen = sizeof (*cmd);
+	if (len > 0) {
+		memcpy(cmd + 1, data, len);
+		totlen += len;
+	}
+	umb_ctrl_msg(sc, MBIM_COMMAND_MSG, cmd, totlen);
+}
+
+void
+umb_command_done(struct umb_softc *sc, void *data, int len)
+{
+	struct mbim_f2h_cmddone *cmd = data;
+	struct ifnet *ifp = GET_IFP(sc);
+	uint32_t status;
+	uint32_t cid;
+	uint32_t infolen;
+	int	 qmimsg = 0;
+
+	if (len < sizeof (*cmd)) {
+		DPRINTF("%s: discard short %s messsage\n", DEVNAM(sc),
+		    umb_request2str(le32toh(cmd->hdr.type)));
+		return;
+	}
+	cid = le32toh(cmd->cid);
+	if (memcmp(cmd->devid, umb_uuid_basic_connect, sizeof (cmd->devid))) {
+		if (memcmp(cmd->devid, umb_uuid_qmi_mbim,
+		    sizeof (cmd->devid))) {
+			DPRINTF("%s: discard %s messsage for other UUID '%s'\n",
+			    DEVNAM(sc), umb_request2str(le32toh(cmd->hdr.type)),
+			    umb_uuid2str(cmd->devid));
+			return;
+		} else
+			qmimsg = 1;
+	}
+
+	status = le32toh(cmd->status);
+	switch (status) {
+	case MBIM_STATUS_SUCCESS:
+		break;
+	case MBIM_STATUS_NOT_INITIALIZED:
+		if (ifp->if_flags & IFF_DEBUG)
+			log(LOG_ERR, "%s: SIM not initialized (PIN missing)\n",
+			    DEVNAM(sc));
+		return;
+	case MBIM_STATUS_PIN_REQUIRED:
+		sc->sc_info.pin_state = UMB_PIN_REQUIRED;
+		/*FALLTHROUGH*/
+	default:
+		if (ifp->if_flags & IFF_DEBUG)
+			log(LOG_ERR, "%s: set/qry %s failed: %s\n", DEVNAM(sc),
+			    umb_cid2str(cid), umb_status2str(status));
+		return;
+	}
+
+	infolen = le32toh(cmd->infolen);
+	if (len < sizeof (*cmd) + infolen) {
+		DPRINTF("%s: discard truncated %s messsage (want %d, got %d)\n",
+		    DEVNAM(sc), umb_cid2str(cid),
+		    (int)sizeof (*cmd) + infolen, len);
+		return;
+	}
+	if (qmimsg) {
+		if (sc->sc_flags & UMBFLG_FCC_AUTH_REQUIRED)
+			umb_decode_qmi(sc, cmd->info, infolen);
+	} else {
+		DPRINTFN(2, "%s: set/qry %s done\n", DEVNAM(sc),
+		    umb_cid2str(cid));
+		umb_decode_cid(sc, cid, cmd->info, infolen);
+	}
+}
+
+void
+umb_decode_cid(struct umb_softc *sc, uint32_t cid, void *data, int len)
+{
+	int	 ok = 1;
+
+	switch (cid) {
+	case MBIM_CID_DEVICE_CAPS:
+		ok = umb_decode_devices_caps(sc, data, len);
+		break;
+	case MBIM_CID_SUBSCRIBER_READY_STATUS:
+		ok = umb_decode_subscriber_status(sc, data, len);
+		break;
+	case MBIM_CID_RADIO_STATE:
+		ok = umb_decode_radio_state(sc, data, len);
+		break;
+	case MBIM_CID_PIN:
+		ok = umb_decode_pin(sc, data, len);
+		break;
+	case MBIM_CID_REGISTER_STATE:
+		ok = umb_decode_register_state(sc, data, len);
+		break;
+	case MBIM_CID_PACKET_SERVICE:
+		ok = umb_decode_packet_service(sc, data, len);
+		break;
+	case MBIM_CID_SIGNAL_STATE:
+		ok = umb_decode_signal_state(sc, data, len);
+		break;
+	case MBIM_CID_CONNECT:
+		ok = umb_decode_connect_info(sc, data, len);
+		break;
+	case MBIM_CID_IP_CONFIGURATION:
+		ok = umb_decode_ip_configuration(sc, data, len);
+		break;
+	default:
+		/*
+		 * Note: the above list is incomplete and only contains
+		 *	mandatory CIDs from the BASIC_CONNECT set.
+		 *	So alternate values are not unusual.
+		 */
+		DPRINTFN(4, "%s: ignore %s\n", DEVNAM(sc), umb_cid2str(cid));
+		break;
+	}
+	if (!ok)
+		DPRINTF("%s: discard %s with bad info length %d\n",
+		    DEVNAM(sc), umb_cid2str(cid), len);
+	return;
+}
+
+void
+umb_decode_qmi(struct umb_softc *sc, uint8_t *data, int len)
+{
+	uint8_t	srv;
+	uint16_t msg, tlvlen;
+	uint32_t val;
+
+#define UMB_QMI_QMUXLEN		6
+	if (len < UMB_QMI_QMUXLEN)
+		goto tooshort;
+
+	srv = data[4];
+	data += UMB_QMI_QMUXLEN;
+	len -= UMB_QMI_QMUXLEN;
+
+#define UMB_GET16(p)	((uint16_t)*p | (uint16_t)*(p + 1) << 8)
+#define UMB_GET32(p)	((uint32_t)*p | (uint32_t)*(p + 1) << 8 | \
+			    (uint32_t)*(p + 2) << 16 |(uint32_t)*(p + 3) << 24)
+	switch (srv) {
+	case 0:	/* ctl */
+#define UMB_QMI_CTLLEN		6
+		if (len < UMB_QMI_CTLLEN)
+			goto tooshort;
+		msg = UMB_GET16(&data[2]);
+		tlvlen = UMB_GET16(&data[4]);
+		data += UMB_QMI_CTLLEN;
+		len -= UMB_QMI_CTLLEN;
+		break;
+	case 2:	/* dms  */
+#define UMB_QMI_DMSLEN		7
+		if (len < UMB_QMI_DMSLEN)
+			goto tooshort;
+		msg = UMB_GET16(&data[3]);
+		tlvlen = UMB_GET16(&data[5]);
+		data += UMB_QMI_DMSLEN;
+		len -= UMB_QMI_DMSLEN;
+		break;
+	default:
+		DPRINTF("%s: discard QMI message for unknown service type %d\n",
+		    DEVNAM(sc), srv);
+		return;
+	}
+
+	if (len < tlvlen)
+		goto tooshort;
+
+#define UMB_QMI_TLVLEN		3
+	while (len > 0) {
+		if (len < UMB_QMI_TLVLEN)
+			goto tooshort;
+		tlvlen = UMB_GET16(&data[1]);
+		if (len < UMB_QMI_TLVLEN + tlvlen)
+			goto tooshort;
+		switch (data[0]) {
+		case 1:	/* allocation info */
+			if (msg == 0x0022) {	/* Allocate CID */
+				if (tlvlen != 2 || data[3] != 2) /* dms */
+					break;
+				sc->sc_cid = data[4];
+				DPRINTF("%s: QMI CID %d allocated\n",
+				    DEVNAM(sc), sc->sc_cid);
+				umb_newstate(sc, UMB_S_CID, UMB_NS_DONT_DROP);
+			}
+			break;
+		case 2:	/* response */
+			if (tlvlen != sizeof (val))
+				break;
+			val = UMB_GET32(&data[3]);
+			switch (msg) {
+			case 0x0022:	/* Allocate CID */
+				if (val != 0) {
+					log(LOG_ERR, "%s: allocation of QMI CID"
+					    " failed, error 0x%x\n", DEVNAM(sc),
+					    val);
+					/* XXX how to proceed? */
+					return;
+				}
+				break;
+			case 0x555f:	/* Send FCC Authentication */
+				if (val == 0)
+					log(LOG_INFO, "%s: send FCC "
+					    "Authentication succeeded\n",
+					    DEVNAM(sc));
+				else if (val == 0x001a0001)
+					log(LOG_INFO, "%s: FCC Authentication "
+					    "not required\n", DEVNAM(sc));
+				else
+					log(LOG_INFO, "%s: send FCC "
+					    "Authentication failed, "
+					    "error 0x%x\n", DEVNAM(sc), val);
+
+				/* FCC Auth is needed only once after power-on*/
+				sc->sc_flags &= ~UMBFLG_FCC_AUTH_REQUIRED;
+
+				/* Try to proceed anyway */
+				DPRINTF("%s: init: turning radio on ...\n",
+				    DEVNAM(sc));
+				umb_radio(sc, 1);
+				break;
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		}
+		data += UMB_QMI_TLVLEN + tlvlen;
+		len -= UMB_QMI_TLVLEN + tlvlen;
+	}
+	return;
+
+tooshort:
+	DPRINTF("%s: discard short QMI message\n", DEVNAM(sc));
+	return;
+}
+
+void
+umb_intr(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+	struct umb_softc *sc = priv;
+	struct ifnet *ifp = GET_IFP(sc);
+	int	 total_len;
+
+	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");
+		break;
+	case UCDC_N_RESPONSE_AVAILABLE:
+		DPRINTFN(2, "%s: umb_intr: response available\n", DEVNAM(sc));
+		++sc->sc_nresp;
+		usb_add_task(sc->sc_udev, &sc->sc_get_response_task, USB_TASKQ_DRIVER);
+		break;
+	case UCDC_N_CONNECTION_SPEED_CHANGE:
+		DPRINTFN(2, "%s: umb_intr: connection speed changed\n",
+		    DEVNAM(sc));
+		break;
+	default:
+		DPRINTF("%s: unexpected notifiation (0x%02x)\n",
+		    DEVNAM(sc), sc->sc_intr_msg.bNotification);
+		break;
+	}
+}
+
+/*
+ * Diagnostic routines
+ */
+#if 0
+char *
+umb_ntop(struct sockaddr *sa)
+{
+#define NUMBUFS		4
+	static char astr[NUMBUFS][INET_ADDRSTRLEN];
+	static unsigned nbuf = 0;
+	char	*s;
+
+	s = astr[nbuf++];
+	if (nbuf >= NUMBUFS)
+		nbuf = 0;
+
+	switch (sa->sa_family) {
+	case AF_INET:
+	default:
+		inet_ntop(AF_INET, &satosin(sa)->sin_addr, s, sizeof (astr[0]));
+		break;
+	case AF_INET6:
+		inet_ntop(AF_INET6, &satosin6(sa)->sin6_addr, s,
+		    sizeof (astr[0]));
+		break;
+	}
+	return s;
+}
+#endif
+
+#ifdef UMB_DEBUG
+char *
+umb_uuid2str(uint8_t uuid[MBIM_UUID_LEN])
+{
+	static char uuidstr[2 * MBIM_UUID_LEN + 5];
+
+#define UUID_BFMT	"%02X"
+#define UUID_SEP	"-"
+	snprintf(uuidstr, sizeof (uuidstr),
+	    UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_SEP
+	    UUID_BFMT UUID_BFMT UUID_SEP
+	    UUID_BFMT UUID_BFMT UUID_SEP
+	    UUID_BFMT UUID_BFMT UUID_SEP
+	    UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT,
+	    uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5],
+	    uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11],
+	    uuid[12], uuid[13], uuid[14], uuid[15]);
+	return uuidstr;
+}
+
+void
+umb_dump(void *buf, int len)
+{
+	int	 i = 0;
+	uint8_t	*c = buf;
+
+	if (len == 0)
+		return;
+	while (i < len) {
+		if ((i % 16) == 0) {
+			if (i > 0)
+				addlog("\n");
+			log(LOG_DEBUG, "%4d:  ", i);
+		}
+		addlog(" %02x", *c);
+		c++;
+		i++;
+	}
+	addlog("\n");
+}
+#endif /* UMB_DEBUG */
diff --git a/sys/dev/usb/if_umbreg.h b/sys/dev/usb/if_umbreg.h
new file mode 100644
index 000000000000..5404eb530e5e
--- /dev/null
+++ b/sys/dev/usb/if_umbreg.h
@@ -0,0 +1,388 @@
+/*	$OpenBSD: if_umb.h,v 1.4 2017/04/18 13:27:55 gerhard Exp $ */
+
+/*
+ * Copyright (c) 2016 genua mbH
+ * 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.
+ */
+
+/*
+ * Mobile Broadband Interface Model
+ * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf
+ */
+
+struct umb_valdescr {
+	int		 val;
+	char const	*descr;
+};
+
+static const char *
+umb_val2descr(const struct umb_valdescr *vdp, int val)
+{
+	static char sval[32];
+
+	while (vdp->descr != NULL) {
+		if (vdp->val == val)
+			return vdp->descr;
+		vdp++;
+	}
+	snprintf(sval, sizeof (sval), "#%d", val);
+	return sval;
+}
+
+#define MBIM_REGSTATE_DESCRIPTIONS {				\
+	{ MBIM_REGSTATE_UNKNOWN,	"unknown" },		\
+	{ MBIM_REGSTATE_DEREGISTERED,	"not registered" },	\
+	{ MBIM_REGSTATE_SEARCHING,	"searching" },		\
+	{ MBIM_REGSTATE_HOME,		"home network" },	\
+	{ MBIM_REGSTATE_ROAMING,	"roaming network" },	\
+	{ MBIM_REGSTATE_PARTNER,	"partner network" },	\
+	{ MBIM_REGSTATE_DENIED,		"access denied" },	\
+	{ 0, NULL } }
+
+#define MBIM_DATACLASS_DESCRIPTIONS {					\
+	{ MBIM_DATACLASS_NONE,				"none" },	\
+	{ MBIM_DATACLASS_GPRS,				"GPRS" },	\
+	{ MBIM_DATACLASS_EDGE,				"EDGE" },	\
+	{ MBIM_DATACLASS_UMTS,				"UMTS" },	\
+	{ MBIM_DATACLASS_HSDPA,				"HSDPA" },	\
+	{ MBIM_DATACLASS_HSUPA,				"HSUPA" },	\
+	{ MBIM_DATACLASS_HSDPA|MBIM_DATACLASS_HSUPA,	"HSPA" },	\
+	{ MBIM_DATACLASS_LTE,				"LTE" },	\
+	{ MBIM_DATACLASS_1XRTT,				"CDMA2000" },	\
+	{ MBIM_DATACLASS_1XEVDO,			"CDMA2000" },	\
+	{ MBIM_DATACLASS_1XEVDO_REV_A,			"CDMA2000" },	\
+	{ MBIM_DATACLASS_1XEVDV,			"CDMA2000" },	\
+	{ MBIM_DATACLASS_3XRTT,				"CDMA2000" },	\
+	{ MBIM_DATACLASS_1XEVDO_REV_B,			"CDMA2000" },	\
+	{ MBIM_DATACLASS_UMB,				"CDMA2000" },	\
+	{ MBIM_DATACLASS_CUSTOM,			"custom" },	\
+	{ 0, NULL } }
+
+#define MBIM_1TO1_DESCRIPTION(m)	{ (m), #m }
+#define MBIM_MESSAGES_DESCRIPTIONS {				\
+	MBIM_1TO1_DESCRIPTION(MBIM_OPEN_MSG),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CLOSE_MSG),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_COMMAND_MSG),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_HOST_ERROR_MSG),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_OPEN_DONE),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CLOSE_DONE),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_COMMAND_DONE),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_FUNCTION_ERROR_MSG),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_INDICATE_STATUS_MSG),	\
+	{ 0, NULL } }
+
+#define MBIM_STATUS_DESCRIPTION(m)	{ MBIM_STATUS_ ## m, #m }
+#define MBIM_STATUS_DESCRIPTIONS {					\
+	MBIM_STATUS_DESCRIPTION(SUCCESS),				\
+	MBIM_STATUS_DESCRIPTION(BUSY),					\
+	MBIM_STATUS_DESCRIPTION(FAILURE),				\
+	MBIM_STATUS_DESCRIPTION(SIM_NOT_INSERTED),			\
+	MBIM_STATUS_DESCRIPTION(BAD_SIM),				\
+	MBIM_STATUS_DESCRIPTION(PIN_REQUIRED),				\
+	MBIM_STATUS_DESCRIPTION(PIN_DISABLED),				\
+	MBIM_STATUS_DESCRIPTION(NOT_REGISTERED),			\
+	MBIM_STATUS_DESCRIPTION(PROVIDERS_NOT_FOUND),			\
+	MBIM_STATUS_DESCRIPTION(NO_DEVICE_SUPPORT),			\
+	MBIM_STATUS_DESCRIPTION(PROVIDER_NOT_VISIBLE),			\
+	MBIM_STATUS_DESCRIPTION(DATA_CLASS_NOT_AVAILABLE),		\
+	MBIM_STATUS_DESCRIPTION(PACKET_SERVICE_DETACHED),		\
+	MBIM_STATUS_DESCRIPTION(MAX_ACTIVATED_CONTEXTS),		\
+	MBIM_STATUS_DESCRIPTION(NOT_INITIALIZED),			\
+	MBIM_STATUS_DESCRIPTION(VOICE_CALL_IN_PROGRESS),		\
+	MBIM_STATUS_DESCRIPTION(CONTEXT_NOT_ACTIVATED),			\
+	MBIM_STATUS_DESCRIPTION(SERVICE_NOT_ACTIVATED),			\
+	MBIM_STATUS_DESCRIPTION(INVALID_ACCESS_STRING),			\
+	MBIM_STATUS_DESCRIPTION(INVALID_USER_NAME_PWD),			\
+	MBIM_STATUS_DESCRIPTION(RADIO_POWER_OFF),			\
+	MBIM_STATUS_DESCRIPTION(INVALID_PARAMETERS),			\
+	MBIM_STATUS_DESCRIPTION(READ_FAILURE),				\
+	MBIM_STATUS_DESCRIPTION(WRITE_FAILURE),				\
+	MBIM_STATUS_DESCRIPTION(NO_PHONEBOOK),				\
+	MBIM_STATUS_DESCRIPTION(PARAMETER_TOO_LONG),			\
+	MBIM_STATUS_DESCRIPTION(STK_BUSY),				\
+	MBIM_STATUS_DESCRIPTION(OPERATION_NOT_ALLOWED),			\
+	MBIM_STATUS_DESCRIPTION(MEMORY_FAILURE),			\
+	MBIM_STATUS_DESCRIPTION(INVALID_MEMORY_INDEX),			\
+	MBIM_STATUS_DESCRIPTION(MEMORY_FULL),				\
+	MBIM_STATUS_DESCRIPTION(FILTER_NOT_SUPPORTED),			\
+	MBIM_STATUS_DESCRIPTION(DSS_INSTANCE_LIMIT),			\
+	MBIM_STATUS_DESCRIPTION(INVALID_DEVICE_SERVICE_OPERATION),	\
+	MBIM_STATUS_DESCRIPTION(AUTH_INCORRECT_AUTN),			\
+	MBIM_STATUS_DESCRIPTION(AUTH_SYNC_FAILURE),			\
+	MBIM_STATUS_DESCRIPTION(AUTH_AMF_NOT_SET),			\
+	MBIM_STATUS_DESCRIPTION(CONTEXT_NOT_SUPPORTED),			\
+	MBIM_STATUS_DESCRIPTION(SMS_UNKNOWN_SMSC_ADDRESS),		\
+	MBIM_STATUS_DESCRIPTION(SMS_NETWORK_TIMEOUT),			\
+	MBIM_STATUS_DESCRIPTION(SMS_LANG_NOT_SUPPORTED),		\
+	MBIM_STATUS_DESCRIPTION(SMS_ENCODING_NOT_SUPPORTED),		\
+	MBIM_STATUS_DESCRIPTION(SMS_FORMAT_NOT_SUPPORTED),		\
+	{ 0, NULL } }
+
+#define MBIM_ERROR_DESCRIPTION(m)	{ MBIM_ERROR_ ## m, #m }
+#define MBIM_ERROR_DESCRIPTIONS {					\
+	MBIM_ERROR_DESCRIPTION(TIMEOUT_FRAGMENT),			\
+	MBIM_ERROR_DESCRIPTION(FRAGMENT_OUT_OF_SEQUENCE),		\
+	MBIM_ERROR_DESCRIPTION(LENGTH_MISMATCH),			\
+	MBIM_ERROR_DESCRIPTION(DUPLICATED_TID),				\
+	MBIM_ERROR_DESCRIPTION(NOT_OPENED),				\
+	MBIM_ERROR_DESCRIPTION(UNKNOWN),				\
+	MBIM_ERROR_DESCRIPTION(CANCEL),					\
+	MBIM_ERROR_DESCRIPTION(MAX_TRANSFER),				\
+	{ 0, NULL } }
+
+#define MBIM_CID_DESCRIPTIONS {						\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_DEVICE_CAPS),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_SUBSCRIBER_READY_STATUS),	\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_RADIO_STATE),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_PIN),				\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_PIN_LIST),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_HOME_PROVIDER),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_PREFERRED_PROVIDERS),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_VISIBLE_PROVIDERS),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_REGISTER_STATE),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_PACKET_SERVICE),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_SIGNAL_STATE),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_CONNECT),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_PROVISIONED_CONTEXTS),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_SERVICE_ACTIVATION),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_IP_CONFIGURATION),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_DEVICE_SERVICES),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST),	\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_PACKET_STATISTICS),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_NETWORK_IDLE_HINT),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_EMERGENCY_MODE),			\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_IP_PACKET_FILTERS),		\
+	MBIM_1TO1_DESCRIPTION(MBIM_CID_MULTICARRIER_PROVIDERS),		\
+	{ 0, NULL } }
+
+#define MBIM_SIMSTATE_DESCRIPTIONS {					\
+	{ MBIM_SIMSTATE_NOTINITIALIZED, "not initialized" },		\
+	{ MBIM_SIMSTATE_INITIALIZED, "initialized" },			\
+	{ MBIM_SIMSTATE_NOTINSERTED, "not inserted" },			\
+	{ MBIM_SIMSTATE_BADSIM, "bad type" },				\
+	{ MBIM_SIMSTATE_FAILURE, "failed" },				\
+	{ MBIM_SIMSTATE_NOTACTIVATED, "not activated" },		\
+	{ MBIM_SIMSTATE_LOCKED, "locked" },				\
+	{ 0, NULL } }
+
+#define MBIM_PINTYPE_DESCRIPTIONS {					\
+	{ MBIM_PIN_TYPE_NONE, "none" },					\
+	{ MBIM_PIN_TYPE_CUSTOM, "custom" },				\
+	{ MBIM_PIN_TYPE_PIN1, "PIN1" },					\
+	{ MBIM_PIN_TYPE_PIN2, "PIN2" },					\
+	{ MBIM_PIN_TYPE_DEV_SIM_PIN, "device PIN" },			\
+	{ MBIM_PIN_TYPE_DEV_FIRST_SIM_PIN, "device 1st PIN" },		\
+	{ MBIM_PIN_TYPE_NETWORK_PIN, "network PIN" },			\
+	{ MBIM_PIN_TYPE_NETWORK_SUBSET_PIN, "network subset PIN" },	\
+	{ MBIM_PIN_TYPE_SERVICE_PROVIDER_PIN, "provider PIN" },		\
+	{ MBIM_PIN_TYPE_CORPORATE_PIN, "corporate PIN" },		\
+	{ MBIM_PIN_TYPE_SUBSIDY_LOCK, "subsidy lock" },			\
+	{ MBIM_PIN_TYPE_PUK1, "PUK" },					\
+	{ MBIM_PIN_TYPE_PUK2, "PUK2" },					\
+	{ MBIM_PIN_TYPE_DEV_FIRST_SIM_PUK, "device 1st PUK" },		\
+	{ MBIM_PIN_TYPE_NETWORK_PUK, "network PUK" },			\
+	{ MBIM_PIN_TYPE_NETWORK_SUBSET_PUK, "network subset PUK" },	\
+	{ MBIM_PIN_TYPE_SERVICE_PROVIDER_PUK, "provider PUK" },		\
+	{ MBIM_PIN_TYPE_CORPORATE_PUK, "corporate PUK" },		\
+	{ 0, NULL } }
+
+#define MBIM_PKTSRV_STATE_DESCRIPTIONS {				\
+	{ MBIM_PKTSERVICE_STATE_UNKNOWN, "unknown" },			\
+	{ MBIM_PKTSERVICE_STATE_ATTACHING, "attaching" },		\
+	{ MBIM_PKTSERVICE_STATE_ATTACHED, "attached" },			\
+	{ MBIM_PKTSERVICE_STATE_DETACHING, "detaching" },		\
+	{ MBIM_PKTSERVICE_STATE_DETACHED, "detached" },			\
+	{ 0, NULL } }
+
+#define MBIM_ACTIVATION_STATE_DESCRIPTIONS {				\
+	{ MBIM_ACTIVATION_STATE_UNKNOWN, "unknown" },			\
+	{ MBIM_ACTIVATION_STATE_ACTIVATED, "activated" },		\
+	{ MBIM_ACTIVATION_STATE_ACTIVATING, "activating" },		\
+	{ MBIM_ACTIVATION_STATE_DEACTIVATED, "deactivated" },		\
+	{ MBIM_ACTIVATION_STATE_DEACTIVATING, "deactivating" },		\
+	{ 0, NULL } }
+
+/*
+ * Driver internal state
+ */
+enum umb_state {
+	UMB_S_DOWN = 0,		/* interface down */
+	UMB_S_OPEN,		/* MBIM device has been opened */
+	UMB_S_CID,		/* QMI client id allocated */
+	UMB_S_RADIO,		/* radio is on */
+	UMB_S_SIMREADY,		/* SIM is ready */
+	UMB_S_ATTACHED,		/* packet service is attached */
+	UMB_S_CONNECTED,	/* connected to provider */
+	UMB_S_UP,		/* have IP configuration */
+};
+
+#define UMB_INTERNAL_STATE_DESCRIPTIONS {	\
+	{ UMB_S_DOWN, "down" },			\
+	{ UMB_S_OPEN, "open" },			\
+	{ UMB_S_CID, "CID allocated" },		\
+	{ UMB_S_RADIO, "radio on" },		\
+	{ UMB_S_SIMREADY, "SIM is ready" },	\
+	{ UMB_S_ATTACHED, "attached" },		\
+	{ UMB_S_CONNECTED, "connected" },	\
+	{ UMB_S_UP, "up" },			\
+	{ 0, NULL } }
+
+/*
+ * UMB parameters (SIOC[GS]UMBPARAM ioctls)
+ */
+struct umb_parameter {
+	int			op;
+	int			is_puk;
+	char			pin[MBIM_PIN_MAXLEN];
+	int			pinlen;
+
+	char			newpin[MBIM_PIN_MAXLEN];
+	int			newpinlen;
+
+#define UMB_APN_MAXLEN		100
+	uint16_t		apn[UMB_APN_MAXLEN];
+	int			apnlen;
+
+	int			roaming;
+	uint32_t		preferredclasses;
+};
+
+/*
+ * UMB device status info (SIOCGUMBINFO ioctl)
+ */
+struct umb_info {
+	enum umb_state		state;
+	int			enable_roaming;
+#define UMB_PIN_REQUIRED	0
+#define UMB_PIN_UNLOCKED	1
+#define UMB_PUK_REQUIRED	2
+	int			pin_state;
+	int			pin_attempts_left;
+	int			activation;
+	int			sim_state;
+	int			regstate;
+	int			regmode;
+	int			nwerror;
+	int			packetstate;
+	uint32_t		supportedclasses; /* what the hw supports */
+	uint32_t		preferredclasses; /* what the user prefers */
+	uint32_t		highestclass;	/* what the network offers */
+	uint32_t		cellclass;
+#define UMB_PROVIDERNAME_MAXLEN		20
+	uint16_t		provider[UMB_PROVIDERNAME_MAXLEN];
+#define UMB_PHONENR_MAXLEN		22
+	uint16_t		pn[UMB_PHONENR_MAXLEN];
+#define UMB_SUBSCRIBERID_MAXLEN		15
+	uint16_t		sid[UMB_SUBSCRIBERID_MAXLEN];
+#define UMB_ICCID_MAXLEN		20
+	uint16_t		iccid[UMB_ICCID_MAXLEN];
+#define UMB_ROAMINGTEXT_MAXLEN		63
+	uint16_t		roamingtxt[UMB_ROAMINGTEXT_MAXLEN];
+
+#define UMB_DEVID_MAXLEN		18
+	uint16_t		devid[UMB_DEVID_MAXLEN];
+#define UMB_FWINFO_MAXLEN		30
+	uint16_t		fwinfo[UMB_FWINFO_MAXLEN];
+#define UMB_HWINFO_MAXLEN		30
+	uint16_t		hwinfo[UMB_HWINFO_MAXLEN];
+
+	uint16_t		apn[UMB_APN_MAXLEN];
+	int			apnlen;
+
+#define UMB_VALUE_UNKNOWN	-999
+	int			rssi;
+#define UMB_BER_EXCELLENT	0
+#define UMB_BER_VERYGOOD	1
+#define UMB_BER_GOOD		2
+#define UMB_BER_OK		3
+#define UMB_BER_MEDIUM		4
+#define UMB_BER_BAD		5
+#define UMB_BER_VERYBAD		6
+#define UMB_BER_EXTREMELYBAD	7
+	int			ber;
+
+	int			hw_radio_on;
+	int			sw_radio_on;
+
+	uint64_t		uplink_speed;
+	uint64_t		downlink_speed;
+
+#define UMB_MAX_DNSSRV			2
+	u_int32_t		ipv4dns[UMB_MAX_DNSSRV];
+};
+
+#ifdef _KERNEL
+/*
+ * UMB device
+ */
+struct umb_softc {
+#if 0
+	struct device		 sc_dev;
+#else
+	device_t		 sc_dev;
+#endif
+	struct ifnet		 sc_if;
+#define GET_IFP(sc)	(&(sc)->sc_if)
+	struct usbd_device	*sc_udev;
+
+	int			 sc_ver_maj;
+	int			 sc_ver_min;
+	int			 sc_ctrl_len;
+	int			 sc_maxpktlen;
+	int			 sc_maxsessions;
+
+#define UMBFLG_FCC_AUTH_REQUIRED	0x0001
+	uint32_t		 sc_flags;
+	int			 sc_cid;
+
+	struct usb_task		 sc_umb_task;
+	struct usb_task		 sc_get_response_task;
+	int			 sc_nresp;
+#if 0
+	struct timeout		 sc_statechg_timer;
+#else
+	callout_t		 sc_statechg_timer;
+#endif
+
+	uint8_t			 sc_ctrl_ifaceno;
+	struct usbd_pipe	*sc_ctrl_pipe;
+	usb_cdc_notification_t	 sc_intr_msg;
+	struct usbd_interface	*sc_data_iface;
+
+	void			*sc_resp_buf;
+	void			*sc_ctrl_msg;
+
+	int			 sc_rx_ep;
+	struct usbd_xfer	*sc_rx_xfer;
+	char			*sc_rx_buf;
+	int			 sc_rx_bufsz;
+	struct usbd_pipe	*sc_rx_pipe;
+	unsigned		 sc_rx_nerr;
+
+	int			 sc_tx_ep;
+	struct usbd_xfer	*sc_tx_xfer;
+	char			*sc_tx_buf;
+	int			 sc_tx_bufsz;
+	struct usbd_pipe	*sc_tx_pipe;
+	struct mbuf		*sc_tx_m;
+	uint32_t		 sc_tx_seq;
+
+	uint32_t		 sc_tid;
+
+#define sc_state		sc_info.state
+#define sc_roaming		sc_info.enable_roaming
+	struct umb_info		sc_info;
+};
+#endif /* _KERNEL */
diff --git a/sys/dev/usb/mbim.h b/sys/dev/usb/mbim.h
new file mode 100644
index 000000000000..d3deffcfc128
--- /dev/null
+++ b/sys/dev/usb/mbim.h
@@ -0,0 +1,697 @@
+/*	$OpenBSD: mbim.h,v 1.4 2017/04/18 13:27:55 gerhard Exp $ */
+
+/*
+ * Copyright (c) 2016 genua mbH
+ * 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.
+ */
+
+/*
+ * Mobile Broadband Interface Model
+ * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf
+ */
+#ifndef _MBIM_H_
+#define _MBIM_H_
+
+#define UDESCSUB_MBIM			27
+#define MBIM_INTERFACE_ALTSETTING	1
+
+#define MBIM_RESET_FUNCTION		0x05
+
+/*
+ * Registration state (MBIM_REGISTER_STATE)
+ */
+#define MBIM_REGSTATE_UNKNOWN			0
+#define MBIM_REGSTATE_DEREGISTERED		1
+#define MBIM_REGSTATE_SEARCHING			2
+#define MBIM_REGSTATE_HOME			3
+#define MBIM_REGSTATE_ROAMING			4
+#define MBIM_REGSTATE_PARTNER			5
+#define MBIM_REGSTATE_DENIED			6
+
+/*
+ * Data classes mask (MBIM_DATA_CLASS)
+ */
+#define MBIM_DATACLASS_NONE			0x00000000
+#define MBIM_DATACLASS_GPRS			0x00000001
+#define MBIM_DATACLASS_EDGE			0x00000002
+#define MBIM_DATACLASS_UMTS			0x00000004
+#define MBIM_DATACLASS_HSDPA			0x00000008
+#define MBIM_DATACLASS_HSUPA			0x00000010
+#define MBIM_DATACLASS_LTE			0x00000020
+#define MBIM_DATACLASS_1XRTT			0x00010000
+#define MBIM_DATACLASS_1XEVDO			0x00020000
+#define MBIM_DATACLASS_1XEVDO_REV_A		0x00040000
+#define MBIM_DATACLASS_1XEVDV			0x00080000
+#define MBIM_DATACLASS_3XRTT			0x00100000
+#define MBIM_DATACLASS_1XEVDO_REV_B		0x00200000
+#define MBIM_DATACLASS_UMB			0x00400000
+#define MBIM_DATACLASS_CUSTOM			0x80000000
+
+/*
+ * Cell classes mask (MBIM_CELLULAR_CLASS)
+ */
+#define MBIM_CELLCLASS_GSM			0x00000001
+#define MBIM_CELLCLASS_CDMA			0x00000002
+
+/*
+ * UUIDs
+ */
+#define MBIM_UUID_LEN		16
+
+#define MBIM_UUID_BASIC_CONNECT {				\
+		0xa2, 0x89, 0xcc, 0x33, 0xbc, 0xbb, 0x8b, 0x4f,	\
+		0xb6, 0xb0, 0x13, 0x3e, 0xc2, 0xaa, 0xe6, 0xdf	\
+	}
+
+#define MBIM_UUID_CONTEXT_INTERNET {				\
+		0x7e, 0x5e, 0x2a, 0x7e, 0x4e, 0x6f, 0x72, 0x72,	\
+		0x73, 0x6b, 0x65, 0x6e, 0x7e, 0x5e, 0x2a, 0x7e	\
+	}
+
+#define MBIM_UUID_CONTEXT_VPN {				\
+		0x9b, 0x9f, 0x7b, 0xbe, 0x89, 0x52, 0x44, 0xb7,	\
+		0x83, 0xac, 0xca, 0x41, 0x31, 0x8d, 0xf7, 0xa0	\
+	}
+
+#define MBIM_UUID_QMI_MBIM {				\
+		0xd1, 0xa3, 0x0b, 0xc2, 0xf9, 0x7a, 0x6e, 0x43,	\
+		0xbf, 0x65, 0xc7, 0xe2, 0x4f, 0xb0, 0xf0, 0xd3	\
+	}
+
+#define MBIM_CTRLMSG_MINLEN		64
+#define MBIM_CTRLMSG_MAXLEN		(4 * 1204)
+
+#define MBIM_MAXSEGSZ_MINVAL		(2 * 1024)
+
+/*
+ * Control messages (host to function)
+ */
+#define MBIM_OPEN_MSG			1U
+#define MBIM_CLOSE_MSG			2U
+#define MBIM_COMMAND_MSG		3U
+#define MBIM_HOST_ERROR_MSG		4U
+
+/*
+ * Control messages (function to host)
+ */
+#define MBIM_OPEN_DONE			0x80000001U
+#define MBIM_CLOSE_DONE			0x80000002U
+#define MBIM_COMMAND_DONE		0x80000003U
+#define MBIM_FUNCTION_ERROR_MSG		0x80000004U
+#define MBIM_INDICATE_STATUS_MSG	0x80000007U
+
+/*
+ * Generic status codes
+ */
+#define MBIM_STATUS_SUCCESS			0
+#define MBIM_STATUS_BUSY			1
+#define MBIM_STATUS_FAILURE			2
+#define MBIM_STATUS_SIM_NOT_INSERTED		3
+#define MBIM_STATUS_BAD_SIM			4
+#define MBIM_STATUS_PIN_REQUIRED		5
+#define MBIM_STATUS_PIN_DISABLED		6
+#define MBIM_STATUS_NOT_REGISTERED		7
+#define MBIM_STATUS_PROVIDERS_NOT_FOUND		8
+#define MBIM_STATUS_NO_DEVICE_SUPPORT		9
+#define MBIM_STATUS_PROVIDER_NOT_VISIBLE	10
+#define MBIM_STATUS_DATA_CLASS_NOT_AVAILABLE	11
+#define MBIM_STATUS_PACKET_SERVICE_DETACHED	12
+#define MBIM_STATUS_MAX_ACTIVATED_CONTEXTS	13
+#define MBIM_STATUS_NOT_INITIALIZED		14
+#define MBIM_STATUS_VOICE_CALL_IN_PROGRESS	15
+#define MBIM_STATUS_CONTEXT_NOT_ACTIVATED	16
+#define MBIM_STATUS_SERVICE_NOT_ACTIVATED	17
+#define MBIM_STATUS_INVALID_ACCESS_STRING	18
+#define MBIM_STATUS_INVALID_USER_NAME_PWD	19
+#define MBIM_STATUS_RADIO_POWER_OFF		20
+#define MBIM_STATUS_INVALID_PARAMETERS		21
+#define MBIM_STATUS_READ_FAILURE		22
+#define MBIM_STATUS_WRITE_FAILURE		23
+#define MBIM_STATUS_NO_PHONEBOOK		25
+#define MBIM_STATUS_PARAMETER_TOO_LONG		26
+#define MBIM_STATUS_STK_BUSY			27
+#define MBIM_STATUS_OPERATION_NOT_ALLOWED	28
+#define MBIM_STATUS_MEMORY_FAILURE		29
+#define MBIM_STATUS_INVALID_MEMORY_INDEX	30
+#define MBIM_STATUS_MEMORY_FULL			31
+#define MBIM_STATUS_FILTER_NOT_SUPPORTED	32
+#define MBIM_STATUS_DSS_INSTANCE_LIMIT		33
+#define MBIM_STATUS_INVALID_DEVICE_SERVICE_OPERATION	34
+#define MBIM_STATUS_AUTH_INCORRECT_AUTN		35
+#define MBIM_STATUS_AUTH_SYNC_FAILURE		36
+#define MBIM_STATUS_AUTH_AMF_NOT_SET		37
+#define MBIM_STATUS_CONTEXT_NOT_SUPPORTED	38
+#define MBIM_STATUS_SMS_UNKNOWN_SMSC_ADDRESS	100
+#define MBIM_STATUS_SMS_NETWORK_TIMEOUT		101
+#define MBIM_STATUS_SMS_LANG_NOT_SUPPORTED	102
+#define MBIM_STATUS_SMS_ENCODING_NOT_SUPPORTED	103
+#define MBIM_STATUS_SMS_FORMAT_NOT_SUPPORTED	104
+
+/*
+ * Message formats
+ */
+struct mbim_msghdr {
+	/* Msg header */
+	uint32_t	type;		/* message type */
+	uint32_t	len;		/* message length */
+	uint32_t	tid;		/* transaction id */
+} __packed;
+
+struct mbim_fraghdr {
+	uint32_t	nfrag;		/* total # of fragments */
+	uint32_t	currfrag;	/* current fragment */
+} __packed;
+
+struct mbim_fragmented_msg_hdr {
+	struct mbim_msghdr	hdr;
+	struct mbim_fraghdr	frag;
+} __packed;
+
+struct mbim_h2f_openmsg {
+	struct mbim_msghdr	hdr;
+	uint32_t		maxlen;
+} __packed;
+
+struct mbim_h2f_closemsg {
+	struct mbim_msghdr	hdr;
+} __packed;
+
+struct mbim_h2f_cmd {
+	struct mbim_msghdr	hdr;
+	struct mbim_fraghdr	frag;
+	uint8_t			devid[MBIM_UUID_LEN];
+	uint32_t		cid;		/* command id */
+#define MBIM_CMDOP_QRY		0
+#define MBIM_CMDOP_SET		1
+	uint32_t		op;
+	uint32_t		infolen;
+	uint8_t			info[];
+} __packed;
+
+struct mbim_f2h_indicate_status {
+	struct mbim_msghdr	hdr;
+	struct mbim_fraghdr	frag;
+	uint8_t			devid[MBIM_UUID_LEN];
+	uint32_t		cid;		/* command id */
+	uint32_t		infolen;
+	uint8_t			info[];
+} __packed;
+
+struct mbim_f2h_hosterr {
+	struct mbim_msghdr	hdr;
+
+#define MBIM_ERROR_TIMEOUT_FRAGMENT		1
+#define MBIM_ERROR_FRAGMENT_OUT_OF_SEQUENCE	2
+#define MBIM_ERROR_LENGTH_MISMATCH		3
+#define MBIM_ERROR_DUPLICATED_TID		4
+#define MBIM_ERROR_NOT_OPENED			5
+#define MBIM_ERROR_UNKNOWN			6
+#define MBIM_ERROR_CANCEL			7
+#define MBIM_ERROR_MAX_TRANSFER			8
+	uint32_t		err;
+} __packed;
+
+struct mbim_f2h_openclosedone {
+	struct mbim_msghdr	hdr;
+	int32_t			status;
+} __packed;
+
+struct mbim_f2h_cmddone {
+	struct mbim_msghdr	hdr;
+	struct mbim_fraghdr	frag;
+	uint8_t			devid[MBIM_UUID_LEN];
+	uint32_t		cid;		/* command id */
+	int32_t			status;
+	uint32_t		infolen;
+	uint8_t			info[];
+} __packed;
+
+/*
+ * Messages and commands for MBIM_UUID_BASIC_CONNECT
+ */
+#define MBIM_CID_DEVICE_CAPS				1
+#define MBIM_CID_SUBSCRIBER_READY_STATUS		2
+#define MBIM_CID_RADIO_STATE				3
+#define MBIM_CID_PIN					4
+#define MBIM_CID_PIN_LIST				5
+#define MBIM_CID_HOME_PROVIDER				6
+#define MBIM_CID_PREFERRED_PROVIDERS			7
+#define MBIM_CID_VISIBLE_PROVIDERS			8
+#define MBIM_CID_REGISTER_STATE				9
+#define MBIM_CID_PACKET_SERVICE				10
+#define MBIM_CID_SIGNAL_STATE				11
+#define MBIM_CID_CONNECT				12
+#define MBIM_CID_PROVISIONED_CONTEXTS			13
+#define MBIM_CID_SERVICE_ACTIVATION			14
+#define MBIM_CID_IP_CONFIGURATION			15
+#define MBIM_CID_DEVICE_SERVICES			16
+#define MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST		19
+#define MBIM_CID_PACKET_STATISTICS			20
+#define MBIM_CID_NETWORK_IDLE_HINT			21
+#define MBIM_CID_EMERGENCY_MODE				22
+#define MBIM_CID_IP_PACKET_FILTERS			23
+#define MBIM_CID_MULTICARRIER_PROVIDERS			24
+
+struct mbim_cid_subscriber_ready_info {
+#define MBIM_SIMSTATE_NOTINITIALIZED		0
+#define MBIM_SIMSTATE_INITIALIZED		1
+#define MBIM_SIMSTATE_NOTINSERTED		2
+#define MBIM_SIMSTATE_BADSIM			3
+#define MBIM_SIMSTATE_FAILURE			4
+#define MBIM_SIMSTATE_NOTACTIVATED		5
+#define MBIM_SIMSTATE_LOCKED			6
+	uint32_t	ready;
+
+	uint32_t	sid_offs;
+	uint32_t	sid_size;
+
+	uint32_t	icc_offs;
+	uint32_t	icc_size;
+
+#define MBIM_SIMUNIQEID_NONE			0
+#define MBIM_SIMUNIQEID_PROTECT			1
+	uint32_t	info;
+
+	uint32_t	no_pn;
+	struct {
+		uint32_t	offs;
+		uint32_t	size;
+	}
+			pn[];
+} __packed;
+
+struct mbim_cid_radio_state {
+#define MBIM_RADIO_STATE_OFF			0
+#define MBIM_RADIO_STATE_ON			1
+	uint32_t	state;
+} __packed;
+
+struct mbim_cid_radio_state_info {
+	uint32_t	hw_state;
+	uint32_t	sw_state;
+} __packed;
+
+struct mbim_cid_pin {
+#define MBIM_PIN_TYPE_NONE			0
+#define MBIM_PIN_TYPE_CUSTOM			1
+#define MBIM_PIN_TYPE_PIN1			2
+#define MBIM_PIN_TYPE_PIN2			3
+#define MBIM_PIN_TYPE_DEV_SIM_PIN		4
+#define MBIM_PIN_TYPE_DEV_FIRST_SIM_PIN		5
+#define MBIM_PIN_TYPE_NETWORK_PIN		6
+#define MBIM_PIN_TYPE_NETWORK_SUBSET_PIN	7
+#define MBIM_PIN_TYPE_SERVICE_PROVIDER_PIN	8
+#define MBIM_PIN_TYPE_CORPORATE_PIN		9
+#define MBIM_PIN_TYPE_SUBSIDY_LOCK		10
+#define MBIM_PIN_TYPE_PUK1			11
+#define MBIM_PIN_TYPE_PUK2			12
+#define MBIM_PIN_TYPE_DEV_FIRST_SIM_PUK		13
+#define MBIM_PIN_TYPE_NETWORK_PUK		14
+#define MBIM_PIN_TYPE_NETWORK_SUBSET_PUK	15
+#define MBIM_PIN_TYPE_SERVICE_PROVIDER_PUK	16
+#define MBIM_PIN_TYPE_CORPORATE_PUK		17
+	uint32_t	type;
+
+#define MBIM_PIN_OP_ENTER			0
+#define MBIM_PIN_OP_ENABLE			1
+#define MBIM_PIN_OP_DISABLE			2
+#define MBIM_PIN_OP_CHANGE			3
+	uint32_t	op;
+	uint32_t	pin_offs;
+	uint32_t	pin_size;
+	uint32_t	newpin_offs;
+	uint32_t	newpin_size;
+#define MBIM_PIN_MAXLEN	32
+	uint8_t		data[2 * MBIM_PIN_MAXLEN];
+} __packed;
+
+struct mbim_cid_pin_info {
+	uint32_t	type;
+
+#define MBIM_PIN_STATE_UNLOCKED			0
+#define MBIM_PIN_STATE_LOCKED			1
+	uint32_t	state;
+	uint32_t	remaining_attempts;
+} __packed;
+
+struct mbim_cid_pin_list_info {
+	struct mbim_pin_desc {
+
+#define MBIM_PINMODE_NOTSUPPORTED		0
+#define MBIM_PINMODE_ENABLED			1
+#define MBIM_PINMODE_DISABLED			2
+		uint32_t	mode;
+
+#define MBIM_PINFORMAT_UNKNOWN			0
+#define MBIM_PINFORMAT_NUMERIC			1
+#define MBIM_PINFORMAT_ALPHANUMERIC		2
+		uint32_t	format;
+
+		uint32_t	minlen;
+		uint32_t	maxlen;
+	}
+		pin1,
+		pin2,
+		dev_sim_pin,
+		first_dev_sim_pin,
+		net_pin,
+		net_sub_pin,
+		svp_pin,
+		corp_pin,
+		subsidy_lock,
+		custom;
+} __packed;
+
+struct mbim_cid_device_caps {
+#define MBIM_DEVTYPE_UNKNOWN			0
+#define MBIM_DEVTYPE_EMBEDDED			1
+#define MBIM_DEVTYPE_REMOVABLE			2
+#define MBIM_DEVTYPE_REMOTE			3
+	uint32_t	devtype;
+
+	uint32_t	cellclass;	/* values: MBIM_CELLULAR_CLASS */
+	uint32_t	voiceclass;
+	uint32_t	simclass;
+	uint32_t	dataclass;	/* values: MBIM_DATA_CLASS */
+	uint32_t	smscaps;
+	uint32_t	cntrlcaps;
+	uint32_t	max_sessions;
+
+	uint32_t	custdataclass_offs;
+	uint32_t	custdataclass_size;
+
+	uint32_t	devid_offs;
+	uint32_t	devid_size;
+
+	uint32_t	fwinfo_offs;
+	uint32_t	fwinfo_size;
+
+	uint32_t	hwinfo_offs;
+	uint32_t	hwinfo_size;
+
+	uint32_t	data[];
+} __packed;
+
+struct mbim_cid_registration_state {
+	uint32_t	provid_offs;
+	uint32_t	provid_size;
+
+#define MBIM_REGACTION_AUTOMATIC		0
+#define MBIM_REGACTION_MANUAL			1
+	uint32_t	regaction;
+	uint32_t	data_class;
+
+	uint32_t	data[];
+} __packed;
+
+struct mbim_cid_registration_state_info {
+	uint32_t	nwerror;
+
+	uint32_t	regstate;	/* values: MBIM_REGISTER_STATE */
+
+#define MBIM_REGMODE_UNKNOWN			0
+#define MBIM_REGMODE_AUTOMATIC			1
+#define MBIM_REGMODE_MANUAL			2
+	uint32_t	regmode;
+
+	uint32_t	availclasses;	/* values: MBIM_DATA_CLASS */
+	uint32_t	curcellclass;	/* values: MBIM_CELLULAR_CLASS */
+
+	uint32_t	provid_offs;
+	uint32_t	provid_size;
+
+	uint32_t	provname_offs;
+	uint32_t	provname_size;
+
+	uint32_t	roamingtxt_offs;
+	uint32_t	roamingtxt_size;
+
+#define MBIM_REGFLAGS_NONE			0
+#define MBIM_REGFLAGS_MANUAL_NOT_AVAILABLE	1
+#define MBIM_REGFLAGS_PACKETSERVICE_AUTOATTACH	2
+	uint32_t	regflag;
+
+	uint32_t	data[];
+} __packed;
+
+struct mbim_cid_packet_service {
+#define MBIM_PKTSERVICE_ACTION_ATTACH		0
+#define MBIM_PKTSERVICE_ACTION_DETACH		1
+	uint32_t	action;
+} __packed;
+
+struct mbim_cid_packet_service_info {
+	uint32_t	nwerror;
+
+#define MBIM_PKTSERVICE_STATE_UNKNOWN		0
+#define MBIM_PKTSERVICE_STATE_ATTACHING		1
+#define MBIM_PKTSERVICE_STATE_ATTACHED		2
+#define MBIM_PKTSERVICE_STATE_DETACHING		3
+#define MBIM_PKTSERVICE_STATE_DETACHED		4
+	uint32_t	state;
+
+	uint32_t	highest_dataclass;
+	uint64_t	uplink_speed;
+	uint64_t	downlink_speed;
+} __packed;
+
+struct mbim_cid_signal_state {
+	uint32_t	rssi;
+	uint32_t	err_rate;
+	uint32_t	ss_intvl;
+	uint32_t	rssi_thr;
+	uint32_t	err_thr;
+} __packed;
+
+struct mbim_cid_connect {
+	uint32_t	sessionid;
+
+#define MBIM_CONNECT_DEACTIVATE		0
+#define MBIM_CONNECT_ACTIVATE		1
+	uint32_t	command;
+
+#define MBIM_ACCESS_MAXLEN		200
+	uint32_t	access_offs;
+	uint32_t	access_size;
+
+#define MBIM_USER_MAXLEN		510
+	uint32_t	user_offs;
+	uint32_t	user_size;
+
+#define MBIM_PASSWD_MAXLEN		510
+	uint32_t	passwd_offs;
+	uint32_t	passwd_size;
+
+#define MBIM_COMPRESSION_NONE		0
+#define MBIM_COMPRESSION_ENABLE		1
+	uint32_t	compression;
+
+#define MBIM_AUTHPROT_NONE		0
+#define MBIM_AUTHPROT_PAP		1
+#define MBIM_AUTHPROT_CHAP		2
+#define MBIM_AUTHPROT_MSCHAP		3
+	uint32_t	authprot;
+
+#define MBIM_CONTEXT_IPTYPE_DEFAULT	0
+#define MBIM_CONTEXT_IPTYPE_IPV4	1
+#define MBIM_CONTEXT_IPTYPE_IPV6	2
+#define MBIM_CONTEXT_IPTYPE_IPV4V6	3
+#define MBIM_CONTEXT_IPTYPE_IPV4ANDV6	4
+	uint32_t	iptype;
+
+	uint8_t		context[MBIM_UUID_LEN];
+
+	uint8_t		data[MBIM_ACCESS_MAXLEN + MBIM_USER_MAXLEN +
+			     MBIM_PASSWD_MAXLEN];
+
+} __packed;
+
+struct mbim_cid_connect_info {
+	uint32_t	sessionid;
+
+#define MBIM_ACTIVATION_STATE_UNKNOWN		0
+#define MBIM_ACTIVATION_STATE_ACTIVATED		1
+#define MBIM_ACTIVATION_STATE_ACTIVATING	2
+#define MBIM_ACTIVATION_STATE_DEACTIVATED	3
+#define MBIM_ACTIVATION_STATE_DEACTIVATING	4
+	uint32_t	activation;
+
+	uint32_t	voice;
+	uint32_t	iptype;
+	uint8_t		context[MBIM_UUID_LEN];
+	uint32_t	nwerror;
+} __packed;
+
+struct mbim_cid_ipv4_element {
+	uint32_t	prefixlen;
+	uint32_t	addr;
+} __packed;
+
+struct mbim_cid_ipv6_element {
+	uint32_t	prefixlen;
+	uint8_t		addr[16];
+} __packed;
+
+struct mbim_cid_ip_configuration_info {
+	uint32_t	sessionid;
+
+#define MBIM_IPCONF_HAS_ADDRINFO	0x0001
+#define MBIM_IPCONF_HAS_GWINFO		0x0002
+#define MBIM_IPCONF_HAS_DNSINFO		0x0004
+#define MBIM_IPCONF_HAS_MTUINFO		0x0008
+	uint32_t	ipv4_available;
+	uint32_t	ipv6_available;
+
+	uint32_t	ipv4_naddr;
+	uint32_t	ipv4_addroffs;
+	uint32_t	ipv6_naddr;
+	uint32_t	ipv6_addroffs;
+
+	uint32_t	ipv4_gwoffs;
+	uint32_t	ipv6_gwoffs;
+
+	uint32_t	ipv4_ndnssrv;
+	uint32_t	ipv4_dnssrvoffs;
+	uint32_t	ipv6_ndnssrv;
+	uint32_t	ipv6_dnssrvoffs;
+
+	uint32_t	ipv4_mtu;
+	uint32_t	ipv6_mtu;
+
+	uint32_t	data[];
+} __packed;
+
+struct mbim_cid_packet_statistics_info {
+	uint32_t	in_discards;
+	uint32_t	in_errors;
+	uint64_t	in_octets;
+	uint64_t	in_packets;
+	uint64_t	out_octets;
+	uint64_t	out_packets;
+	uint32_t	out_errors;
+	uint32_t	out_discards;
+} __packed;
+
+
+#ifdef _KERNEL
+
+struct mbim_descriptor {
+	uByte	bLength;
+	uByte	bDescriptorType;
+	uByte	bDescriptorSubtype;
+#define MBIM_VER_MAJOR(v)	(((v) >> 8) & 0x0f)
+#define MBIM_VER_MINOR(v)	((v) & 0x0f)
+	uWord	bcdMBIMVersion;
+	uWord	wMaxControlMessage;
+	uByte	bNumberFilters;
+	uByte	bMaxFilterSize;
+	uWord	wMaxSegmentSize;
+	uByte	bmNetworkCapabilities;
+} __packed;
+
+/*
+ * NCM Parameters
+ */
+#define NCM_GET_NTB_PARAMETERS	0x80
+
+struct ncm_ntb_parameters {
+	uWord	wLength;
+	uWord	bmNtbFormatsSupported;
+#define NCM_FORMAT_NTB16	0x0001
+#define NCM_FORMAT_NTB32	0x0002
+	uDWord	dwNtbInMaxSize;
+	uWord	wNtbInDivisor;
+	uWord	wNtbInPayloadRemainder;
+	uWord	wNtbInAlignment;
+	uWord	wReserved1;
+	uDWord	dwNtbOutMaxSize;
+	uWord	wNtbOutDivisor;
+	uWord	wNtbOutPayloadRemainder;
+	uWord	wNtbOutAlignment;
+	uWord	wNtbOutMaxDatagrams;
+} __packed;
+
+/*
+ * NCM Encoding
+ */
+#define MBIM_HDR16_LEN	\
+	(sizeof(struct ncm_header16) + sizeof(struct ncm_pointer16))
+#define MBIM_HDR32_LEN	\
+	(sizeof(struct ncm_header32) + sizeof(struct ncm_pointer32))
+
+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[2];
+} __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[2];
+} __packed;
+
+#endif /* _KERNEL */
+
+#endif /* _MBIM_H_ */
diff --git a/sys/dev/usb/usb.c b/sys/dev/usb/usb.c
index 04fd5198e5fc..84cc0e195e72 100644
--- a/sys/dev/usb/usb.c
+++ b/sys/dev/usb/usb.c
@@ -445,6 +445,20 @@ usb_rem_task(struct usbd_device *dev, struct usb_task *task)
 	}
 }
 
+void
+usb_wait_task(struct usbd_device *dev, struct usb_task *task)
+{
+#if 0
+	int s;
+
+	s = splusb();
+	while (task->state != USB_TASK_STATE_NONE) {
+		tsleep(task, PWAIT, "endtask", 0);
+	}
+	splx(s);
+#endif
+}
+
 void
 usb_event_thread(void *arg)
 {
diff --git a/sys/dev/usb/usb.h b/sys/dev/usb/usb.h
index f56b2ef86d7a..2ecb1a425cb0 100644
--- a/sys/dev/usb/usb.h
+++ b/sys/dev/usb/usb.h
@@ -205,6 +205,7 @@ typedef struct {
 typedef struct {
 	uByte		bLength;
 	uByte		bDescriptorType;
+	uByte		bDescriptorSubtype;
 } UPACKED usb_descriptor_t;
 
 typedef struct {
@@ -666,6 +667,9 @@ typedef struct {
 #define	 UISUBCLASS_CAPI_CONTROLMODEL		5
 #define	 UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL 6
 #define	 UISUBCLASS_ATM_NETWORKING_CONTROL_MODEL 7
+#define	 UISUBCLASS_MOBILE_DIRECT_LINE_MODEL	10
+#define	 UISUBCLASS_NETWORK_CONTROL_MODEL	13
+#define	 UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL	14
 #define	  UIPROTO_CDC_NOCLASS			0 /* no class specific
 						     protocol required */
 #define   UIPROTO_CDC_AT			1
@@ -706,6 +710,7 @@ typedef struct {
 
 #define UICLASS_CDC_DATA	0x0a
 #define  UISUBCLASS_DATA		0
+#define   UIPROTO_DATA_MBIM		0x02    /* MBIM */
 #define   UIPROTO_DATA_ISDNBRI		0x30    /* Physical iface */
 #define   UIPROTO_DATA_HDLC		0x31    /* HDLC */
 #define   UIPROTO_DATA_TRANSPARENT	0x32    /* Transparent */
diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs
index 519830829ed6..5301d71a4350 100644
--- a/sys/dev/usb/usbdevs
+++ b/sys/dev/usb/usbdevs
@@ -2965,6 +2965,7 @@ product SIERRA AC880U		0x6855	Sierra Wireless AirCard 880U
 product SIERRA AC881U		0x6856	Sierra Wireless AirCard 881U
 product SIERRA AC885U		0x6880	Sierra Wireless AirCard 885U
 product SIERRA USB305		0x68a3	Sierra Wireless AirCard USB 305
+product SIERRA EM7455		0x9079	EM7455
 
 /* Sigmatel products */
 product SIGMATEL SIR4116	0x4116	StIR4116 SIR
diff --git a/sys/dev/usb/usbdi.h b/sys/dev/usb/usbdi.h
index 911d3f96a2c5..b3d32588cd7a 100644
--- a/sys/dev/usb/usbdi.h
+++ b/sys/dev/usb/usbdi.h
@@ -219,6 +219,7 @@ struct usb_task {
 
 void usb_add_task(struct usbd_device *, struct usb_task *, int);
 void usb_rem_task(struct usbd_device *, struct usb_task *);
+void usb_wait_task(struct usbd_device *, struct usb_task *);
 #define usb_init_task(t, f, a, fl) ((t)->fun = (f), (t)->arg = (a), (t)->queue = USB_NUM_TASKQS, (t)->flags = (fl))
 
 struct usb_devno {
diff --git a/sys/net/if_types.h b/sys/net/if_types.h
index 191a5b1aeced..d660cda0d28d 100644
--- a/sys/net/if_types.h
+++ b/sys/net/if_types.h
@@ -266,5 +266,6 @@
 #define IFT_L2TP	0xf7		/* L2TPv3 I/F */
 #define IFT_CARP	0xf8		/* Common Address Redundancy Protocol */
 #define IFT_IPSEC	0xf9		/* IPsec I/F */
+#define IFT_MBIM	0xfa		/* Mobile Broadband Interface Model */
 
 #endif /* !_NET_IF_TYPES_H_ */
diff --git a/sys/sys/sockio.h b/sys/sys/sockio.h
index 85ad8dbaf5b6..169a67958618 100644
--- a/sys/sys/sockio.h
+++ b/sys/sys/sockio.h
@@ -139,6 +139,10 @@
 #define	SIOCGETHERCAP	_IOWR('i', 139, struct eccapreq) /* get ethercap */
 #define SIOCGIFINDEX  _IOWR('i', 140, struct ifreq)   /* get ifnet index */
 
+#define SIOCGUMBINFO	_IOWR('i', 190, struct ifreq)	/* get MBIM info */
+#define SIOCSUMBPARAM	_IOW('i', 191, struct ifreq)	/* set MBIM param */
+#define SIOCGUMBPARAM	_IOWR('i', 192, struct ifreq)	/* get MBIM param */
+
 #define	SIOCSETPFSYNC	_IOW('i', 247, struct ifreq)	
 #define	SIOCGETPFSYNC	_IOWR('i', 248, struct ifreq)
 


Home | Main Index | Thread Index | Old Index