diff options
Diffstat (limited to 'sys/dev')
| -rw-r--r-- | sys/dev/usb/Makefile | 4 | ||||
| -rw-r--r-- | sys/dev/usb/files.usb | 7 | ||||
| -rw-r--r-- | sys/dev/usb/if_umb.c | 2889 | ||||
| -rw-r--r-- | sys/dev/usb/if_umbreg.h | 407 | ||||
| -rw-r--r-- | sys/dev/usb/mbim.h | 697 | ||||
| -rw-r--r-- | sys/dev/usb/usb.h | 23 | ||||
| -rw-r--r-- | sys/dev/usb/usbdi.c | 35 | ||||
| -rw-r--r-- | sys/dev/usb/usbdi.h | 4 |
8 files changed, 4051 insertions, 15 deletions
diff --git a/sys/dev/usb/Makefile b/sys/dev/usb/Makefile index 673302874bc..1281c84214e 100644 --- a/sys/dev/usb/Makefile +++ b/sys/dev/usb/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.9 2006/05/14 21:47:00 elad Exp $ +# $NetBSD: Makefile,v 1.10 2018/07/31 16:44:29 khorben Exp $ # use 'make -f Makefile.usbdevs' to make usbdevs.h and usbdevs_data.h # _after_ you committed usbdevs. See comment in Makefile.usbdevs @@ -6,6 +6,6 @@ INCSDIR= /usr/include/dev/usb # Only install includes which are used by userland -INCS= ukyopon.h urio.h usb.h usbhid.h utoppy.h +INCS= if_umbreg.h mbim.h ukyopon.h urio.h usb.h usbhid.h utoppy.h .include <bsd.kinc.mk> diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb index 0073b0a3123..7b9ba3b4ed9 100644 --- a/sys/dev/usb/files.usb +++ b/sys/dev/usb/files.usb @@ -1,4 +1,4 @@ -# $NetBSD: files.usb,v 1.150 2018/03/05 09:35:01 ws Exp $ +# $NetBSD: files.usb,v 1.151 2018/07/31 16:44:29 khorben Exp $ # # Config file and device description for machine-independent USB code. # Included by ports that need it. Ports that use it must provide @@ -379,6 +379,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 +attach umb at usbifif +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 00000000000..b6a9cfe55d4 --- /dev/null +++ b/sys/dev/usb/if_umb.c @@ -0,0 +1,2889 @@ +/* $NetBSD: if_umb.c,v 1.1 2018/07/31 16:44:29 khorben Exp $ */ +/* $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_umb.c,v 1.1 2018/07/31 16:44:29 khorben Exp $"); + +#ifdef _KERNEL_OPT +#include "opt_inet.h" +#endif + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/endian.h> +#include <sys/kauth.h> +#include <sys/kernel.h> +#include <sys/kmem.h> +#include <sys/mbuf.h> +#include <sys/rndsource.h> +#include <sys/socket.h> +#include <sys/syslog.h> +#include <sys/systm.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/if_inarp.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#endif + +#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; +Static char *umb_uuid2str(uint8_t [MBIM_UUID_LEN]); +Static 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) + +#ifndef notyet +#define usb_wait_task(dev, task) +#endif + +/* + * 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)) + +Static int umb_match(device_t, cfdata_t, void *); +Static void umb_attach(device_t, device_t, void *); +Static int umb_detach(device_t, int); +Static int umb_activate(device_t, enum devact); +Static void umb_ncm_setup(struct umb_softc *); +Static int umb_alloc_xfers(struct umb_softc *); +Static void umb_free_xfers(struct umb_softc *); +Static int umb_alloc_bulkpipes(struct umb_softc *); +Static void umb_close_bulkpipes(struct umb_softc *); +Static int umb_ioctl(struct ifnet *, u_long, void *); +Static int umb_output(struct ifnet *, struct mbuf *, + const struct sockaddr *, const struct rtentry *); +Static void umb_input(struct ifnet *, struct mbuf *); +Static void umb_start(struct ifnet *); +Static void umb_watchdog(struct ifnet *); +Static void umb_statechg_timeout(void *); + +Static int umb_mediachange(struct ifnet *); +Static void umb_mediastatus(struct ifnet *, struct ifmediareq *); + +Static void umb_newstate(struct umb_softc *, enum umb_state, int); +Static void umb_state_task(void *); +Static void umb_up(struct umb_softc *); +Static void umb_down(struct umb_softc *, int); + +Static void umb_get_response_task(void *); + +Static void umb_decode_response(struct umb_softc *, void *, int); +Static void umb_handle_indicate_status_msg(struct umb_softc *, void *, + int); +Static void umb_handle_opendone_msg(struct umb_softc *, void *, int); +Static void umb_handle_closedone_msg(struct umb_softc *, void *, int); +Static int umb_decode_register_state(struct umb_softc *, void *, int); +Static int umb_decode_devices_caps(struct umb_softc *, void *, int); +Static int umb_decode_subscriber_status(struct umb_softc *, void *, int); +Static int umb_decode_radio_state(struct umb_softc *, void *, int); +Static int umb_decode_pin(struct umb_softc *, void *, int); +Static int umb_decode_packet_service(struct umb_softc *, void *, int); +Static int umb_decode_signal_state(struct umb_softc *, void *, int); +Static int umb_decode_connect_info(struct umb_softc *, void *, int); +Static int umb_decode_ip_configuration(struct umb_softc *, void *, int); +Static void umb_rx(struct umb_softc *); +Static void umb_rxeof(struct usbd_xfer *, void *, usbd_status); +Static int umb_encap(struct umb_softc *, struct mbuf *); +Static void umb_txeof(struct usbd_xfer *, void *, usbd_status); +Static void umb_decap(struct umb_softc *, struct usbd_xfer *); + +Static usbd_status umb_send_encap_command(struct umb_softc *, void *, int); +Static int umb_get_encap_response(struct umb_softc *, void *, int *); +Static void umb_ctrl_msg(struct umb_softc *, uint32_t, void *, int); + +Static void umb_open(struct umb_softc *); +Static void umb_close(struct umb_softc *); + +Static int umb_setpin(struct umb_softc *, int, int, void *, int, void *, + int); +Static void umb_setdataclass(struct umb_softc *); +Static void umb_radio(struct umb_softc *, int); +Static void umb_allocate_cid(struct umb_softc *); +Static void umb_send_fcc_auth(struct umb_softc *); +Static void umb_packet_service(struct umb_softc *, int); +Static void umb_connect(struct umb_softc *); +Static void umb_disconnect(struct umb_softc *); +Static void umb_send_connect(struct umb_softc *, int); + +Static void umb_qry_ipconfig(struct umb_softc *); +Static void umb_cmd(struct umb_softc *, int, int, const void *, int); +Static void umb_cmd1(struct umb_softc *, int, int, const void *, int, uint8_t *); +Static void umb_command_done(struct umb_softc *, void *, int); +Static void umb_decode_cid(struct umb_softc *, uint32_t, void *, int); +Static void umb_decode_qmi(struct umb_softc *, uint8_t *, int); + +Static void umb_intr(struct usbd_xfer *, void *, usbd_status); + +Static char *umb_ntop(struct sockaddr *); + +Static const char * +inet_ntop(int af, const void *src, char *dst, socklen_t size); +static const char *inet_ntop4(const u_char *src, char *dst, size_t size); +#ifdef INET6 +static const char *inet_ntop6(const u_char *src, char *dst, size_t size); +#endif /* INET6 */ + +Static int umb_xfer_tout = USBD_DEFAULT_TIMEOUT; + +Static uint8_t umb_uuid_basic_connect[] = MBIM_UUID_BASIC_CONNECT; +Static uint8_t umb_uuid_context_internet[] = MBIM_UUID_CONTEXT_INTERNET; +Static uint8_t umb_uuid_qmi_mbim[] = MBIM_UUID_QMI_MBIM; +Static uint32_t umb_session_id = 0; + +CFATTACH_DECL_NEW(umb, sizeof(struct umb_softc), umb_match, umb_attach, + umb_detach, umb_activate); + +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 }, +}; + +Static const 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 */ +}; + +Static const 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 */ +}; + +Static 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; +} + +Static void +umb_attach(device_t parent, device_t self, void *aux) +{ + struct umb_softc *sc = device_private(self); + struct usbif_attach_arg *uiaa = aux; + char *devinfop; + 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; + int rv; + + sc->sc_dev = self; + sc->sc_udev = uiaa->uiaa_device; + + aprint_naive("\n"); + aprint_normal("\n"); + + devinfop = usbd_devinfo_alloc(sc->sc_udev, 0); + aprint_normal_dev(self, "%s\n", devinfop); + usbd_devinfo_free(devinfop); + + 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) { + aprint_error_dev(self, "missing MBIM descriptor\n"); + goto fail; + } + + aprint_normal_dev(self, "version %d.%d\n", sc->sc_ver_maj, + sc->sc_ver_min); + + 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) { + aprint_error_dev(self, "no data interface found\n"); + 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) { + aprint_error_dev(self, "missing interrupt endpoint\n"); + 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) { + aprint_error_dev(self, "missing alt setting for interface #%d\n", + data_ifaceno); + goto fail; + } + status = usbd_set_interface(sc->sc_data_iface, i); + if (status) { + aprint_error_dev(self, "select alt setting %d for interface #%d " + "failed: %s\n", 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) { + aprint_error_dev(self, "missing bulk endpoints\n"); + 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_init(&sc->sc_statechg_timer, 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)) { + aprint_error_dev(self, "failed to open control pipe\n"); + goto fail; + } + sc->sc_resp_buf = kmem_alloc(sc->sc_ctrl_len, KM_NOSLEEP); + if (sc->sc_resp_buf == NULL) { + aprint_error_dev(self, "allocation of resp buffer failed\n"); + goto fail; + } + sc->sc_ctrl_msg = kmem_alloc(sc->sc_ctrl_len, KM_NOSLEEP); + if (sc->sc_ctrl_msg == NULL) { + aprint_error_dev(self, "allocation of ctrl msg buffer failed\n"); + 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(); + + /* initialize the interface */ + ifp = GET_IFP(sc); + ifp->if_softc = sc; + ifp->if_flags = IFF_SIMPLEX | IFF_MULTICAST | IFF_POINTOPOINT; + ifp->if_ioctl = umb_ioctl; + ifp->if_start = umb_start; + + ifp->if_watchdog = umb_watchdog; + strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ); + ifp->if_link_state = LINK_STATE_DOWN; + ifmedia_init(&sc->sc_im, 0, umb_mediachange, umb_mediastatus); + + 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_mtu = sc->sc_maxpktlen; + ifp->if_output = umb_output; + ifp->_if_input = umb_input; + IFQ_SET_READY(&ifp->if_snd); + + /* attach the interface */ + rv = if_initialize(ifp); + if (rv != 0) { + aprint_error_dev(self, "if_initialize failed(%d)\n", rv); + splx(s); + return; + } + if_register(ifp); + if_alloc_sadl(ifp); + + bpf_attach(ifp, DLT_RAW, 0); + rnd_attach_source(&sc->sc_rnd_source, device_xname(sc->sc_dev), + RND_TYPE_NET, RND_FLAG_DEFAULT); + + /* + * Open the device now so that we are able to query device information. + * XXX maybe close when done? + */ + umb_open(sc); + + sc->sc_attached = 1; + splx(s); + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); + + if (!pmf_device_register(self, NULL, NULL)) + aprint_error_dev(self, "couldn't establish power handler\n"); + + return; + +fail: + umb_activate(sc->sc_dev, DVACT_DEACTIVATE); + return; +} + +Static int +umb_detach(device_t self, int flags) +{ + struct umb_softc *sc = (struct umb_softc *)self; + struct ifnet *ifp = GET_IFP(sc); + int s; + + pmf_device_deregister(self); + + 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); + sc->sc_nresp = 0; + if (sc->sc_rx_ep != -1 && sc->sc_tx_ep != -1) { + callout_destroy(&sc->sc_statechg_timer); + 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) { + kmem_free(sc->sc_ctrl_msg, sc->sc_ctrl_len); + sc->sc_ctrl_msg = NULL; + } + if (sc->sc_resp_buf) { + kmem_free(sc->sc_resp_buf, sc->sc_ctrl_len); + sc->sc_resp_buf = NULL; + } + if (ifp->if_softc) { + ifmedia_delete_instance(&sc->sc_im, IFM_INST_ANY); + } + if (sc->sc_attached) { + rnd_detach_source(&sc->sc_rnd_source); + bpf_detach(ifp); + if_detach(ifp); + } + + sc->sc_attached = 0; + splx(s); + return 0; +} + +Static int +umb_activate(device_t self, enum devact act) +{ + struct umb_softc *sc = device_private(self); + + switch (act) { + case DVACT_DEACTIVATE: + if_deactivate(GET_IFP(sc)); + sc->sc_dying = 1; + return 0; + default: + return EOPNOTSUPP; + } +} + +Static 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; +} + +Static 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, + 0, 0, &sc->sc_rx_xfer); + } + if (!sc->sc_tx_xfer) { + err |= usbd_create_xfer(sc->sc_tx_pipe, + sc->sc_tx_bufsz, + 0, 0, &sc->sc_tx_xfer); + } + if (err) + return err; + + sc->sc_rx_buf = usbd_get_buffer(sc->sc_rx_xfer); + sc->sc_tx_buf = usbd_get_buffer(sc->sc_tx_xfer); + + return 0; +} + +Static 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; + } +} + +Static int +umb_alloc_bulkpipes(struct umb_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + int rv; + + if (!(ifp->if_flags & IFF_RUNNING)) { + if ((rv = usbd_open_pipe(sc->sc_data_iface, sc->sc_rx_ep, + USBD_EXCLUSIVE_USE, &sc->sc_rx_pipe))) { + DPRINTFN(4, "usbd_open_pipe() failed (RX) %d\n", rv); + return 0; + } + if ((rv = usbd_open_pipe(sc->sc_data_iface, sc->sc_tx_ep, + USBD_EXCLUSIVE_USE, &sc->sc_tx_pipe))) { + DPRINTFN(4, "usbd_open_pipe() failed (TX) %d\n", rv); + return 0; + } + + if ((rv = umb_alloc_xfers(sc)) != 0) { + DPRINTFN(4, "umb_alloc_xfers() failed %d\n", rv); + return 0; + } + + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; + umb_rx(sc); + } + return 1; +} + +Static void +umb_close_bulkpipes(struct umb_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + + ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); + 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; + } +} + +Static int +umb_ioctl(struct ifnet *ifp, u_long cmd, void *data) +{ + struct umb_softc *sc = ifp->if_softc; + struct ifaddr *ifa = (struct ifaddr *)data; + struct ifreq *ifr = (struct ifreq *)data; + int s, error = 0; + struct umb_parameter mp; + + if (sc->sc_dying) + return EIO; + + s = splnet(); + switch (cmd) { + case SIOCINITIFADDR: + ifp->if_flags |= IFF_UP; + usb_add_task(sc->sc_udev, &sc->sc_umb_task, USB_TASKQ_DRIVER); + switch (ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + break; +#endif /* INET6 */ + default: + error = EAFNOSUPPORT; + break; + } + ifa->ifa_rtrequest = p2p_rtrequest; + break; + case SIOCSIFFLAGS: + error = ifioctl_common(ifp, cmd, data); + if (error) + break; + 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: + error = kauth_authorize_network(curlwp->l_cred, + KAUTH_NETWORK_INTERFACE, + KAUTH_REQ_NETWORK_INTERFACE_SETPRIV, ifp, KAUTH_ARG(cmd), + NULL); + if (error) + break; + + 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; + memset(sc->sc_info.username, 0, sizeof(sc->sc_info.username)); + memcpy(sc->sc_info.username, mp.username, mp.usernamelen); + sc->sc_info.usernamelen = mp.usernamelen; + memset(sc->sc_info.password, 0, sizeof(sc->sc_info.password)); + memcpy(sc->sc_info.password, mp.password, mp.passwordlen); + sc->sc_info.passwordlen = mp.passwordlen; + 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_mtu) { + error = EINVAL; + break; + } + ifp->if_mtu = ifr->ifr_mtu; + break; + case SIOCSIFADDR: + case SIOCAIFADDR: + case SIOCSIFDSTADDR: + case SIOCADDMULTI: + case SIOCDELMULTI: + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &sc->sc_im, cmd); + break; + default: + error = ifioctl_common(ifp, cmd, data); + break; + } + splx(s); + return error; +} + +Static 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 umb_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; +} + +Static 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 %zd)\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); +} + +Static void +umb_start(struct ifnet *ifp) +{ + struct umb_softc *sc = ifp->if_softc; + struct mbuf *m_head = NULL; + + if (sc->sc_dying || (ifp->if_flags & IFF_OACTIVE)) + return; + + IFQ_POLL(&ifp->if_snd, m_head); + if (m_head == NULL) + return; + + if (!umb_encap(sc, m_head)) { + ifp->if_flags |= IFF_OACTIVE; + return; + } + IFQ_DEQUEUE(&ifp->if_snd, m_head); + + bpf_mtap(ifp, m_head); + + ifp->if_flags |= IFF_OACTIVE; + ifp->if_timer = (2 * umb_xfer_tout) / 1000; +} + +Static void +umb_watchdog(struct ifnet *ifp) +{ + struct umb_softc *sc = ifp->if_softc; + + if (sc->sc_dying) + return; + + ifp->if_oerrors++; + printf("%s: watchdog timeout\n", DEVNAM(sc)); + usbd_abort_pipe(sc->sc_tx_pipe); + return; +} + +Static 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); +} + +Static int +umb_mediachange(struct ifnet * ifp) +{ + return 0; +} + +Static void +umb_mediastatus(struct ifnet * ifp, struct ifmediareq * imr) +{ + switch (ifp->if_link_state) { + case LINK_STATE_UP: + imr->ifm_status = IFM_AVALID | IFM_ACTIVE; + break; + case LINK_STATE_DOWN: + imr->ifm_status = IFM_AVALID; + break; + default: + imr->ifm_status = 0; + break; + } +} + +Static 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); +} + +Static void +umb_state_task(void *arg) +{ + struct umb_softc *sc = arg; + struct ifnet *ifp = GET_IFP(sc); + struct ifreq ifr; + 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 (in_control(NULL, SIOCGIFADDR, &ifr, ifp) == 0 && + satosin(&ifr.ifr_addr)->sin_addr.s_addr != + INADDR_ANY) { + in_control(NULL, SIOCDIFADDR, &ifr, ifp); + } + } + if_link_state_change(ifp, state); + } + splx(s); +} + +Static 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; + 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; +} + +Static 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); +} + +Static 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); +} + +Static 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; + } +} + +Static 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); +} + +Static 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; +} + +Static 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; +} + +static void +umb_in_len2mask(struct in_addr *mask, int len) +{ + int i; + u_char *p; + + p = (u_char *)mask; + memset(mask, 0, sizeof(*mask)); + for (i = 0; i < len / 8; i++) + p[i] = 0xff; + if (len % 8) + p[i] = (0xff00 >> (len % 8)) & 0xff; +} + +Static 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; +} + +Static 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; +} + +Static 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; +} + +Static 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; +} + +Static 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; +} + +Static 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; +} + +Static 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; +} + +Static 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; +} + +Static 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 configuration 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); + umb_in_len2mask(&sin->sin_addr, ipv4elem.prefixlen); + + rv = in_control(NULL, SIOCAIFADDR, &ifra, ifp); + if (rv == 0) { + if (ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: IPv4 addr %s, mask %s, " + "gateway %s\n", device_xname(sc->sc_dev), + umb_ntop(sintosa(&ifra.ifra_addr)), + umb_ntop(sintosa(&ifra.ifra_mask)), + umb_ntop(sintosa(&ifra.ifra_dstaddr))); + state = UMB_S_UP; + } else + printf("%s: unable to set IPv4 address, error %d\n", + device_xname(sc->sc_dev), 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 *)((char *)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_mtu != val && val <= sc->sc_maxpktlen) { + ifp->if_mtu = 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 configuration missing */ + log(LOG_INFO, "%s: ignoring IPv6 configuration\n", DEVNAM(sc)); + } + if (state != -1) + umb_newstate(sc, state, 0); + +done: + splx(s); + return 1; +} + +Static 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); +} + +Static void +umb_rxeof(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + struct umb_softc *sc = priv; + struct ifnet *ifp = GET_IFP(sc); + + if (sc->sc_dying || !(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_activate(sc->sc_dev, DVACT_DEACTIVATE); + } + } else { + sc->sc_rx_nerr = 0; + umb_decap(sc, xfer); + } + + umb_rx(sc); + return; +} + +Static 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); + + KASSERT(len <= sc->sc_tx_bufsz - sizeof(*hdr) - sizeof(*ptr)); + 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; +} + +Static 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(); + ifp->if_flags &= ~IFF_OACTIVE; + 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); +} + +Static 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); + m = m_devget(dp, dlen, 0, ifp, NULL); + 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); +} + +Static 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; + buf = usbd_get_buffer(xfer); + 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(sc->sc_udev, xfer, &req, NULL, NULL); +} + +Static 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; +} + +Static 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 (sc->sc_dying) + return; + 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; +} + +Static 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; +} + +Static 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)); +} + +Static 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; +} + +Static 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)); +} + +Static 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)); +} + +Static 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); +} + +Static 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); +} + +Static 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)); +} + +Static 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); +} + +Static 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); +} + +Static void +umb_send_connect(struct umb_softc *sc, int command) +{ + struct mbim_cid_connect *c; + int off; + + /* Too large or the stack */ + c = kmem_zalloc(sizeof(*c), KM_SLEEP); + 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; + if (!umb_addstr(c, sizeof(*c), &off, sc->sc_info.username, + sc->sc_info.usernamelen, &c->user_offs, &c->user_size)) + goto done; + if (!umb_addstr(c, sizeof(*c), &off, sc->sc_info.password, + sc->sc_info.passwordlen, &c->passwd_offs, &c->passwd_size)) + goto done; + 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: + kmem_free(c, sizeof(*c)); + return; +} + +Static 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)); +} + +Static void +umb_cmd(struct umb_softc *sc, int cid, int op, const void *data, int len) +{ + umb_cmd1(sc, cid, op, data, len, umb_uuid_basic_connect); +} + +Static void +umb_cmd1(struct umb_softc *sc, int cid, int op, const 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); +} + +Static 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); + } +} + +Static 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; +} + +Static 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; +} + +Static 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 + */ +Static 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; +} + +#ifdef UMB_DEBUG +Static 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; +} + +Static 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 */ + +/* char * + * inet_ntop(af, src, dst, size) + * convert a network format address to presentation format. + * return: + * pointer to presentation format address (`dst'), or NULL (see errno). + * author: + * Paul Vixie, 1996. + */ +Static const char * +inet_ntop(int af, const void *src, char *dst, socklen_t size) +{ + switch (af) { + case AF_INET: + return (inet_ntop4(src, dst, (size_t)size)); +#ifdef INET6 + case AF_INET6: + return (inet_ntop6(src, dst, (size_t)size)); +#endif /* INET6 */ + default: + return (NULL); + } + /* NOTREACHED */ +} + +/* const char * + * inet_ntop4(src, dst, size) + * format an IPv4 address, more or less like inet_ntoa() + * return: + * `dst' (as a const) + * notes: + * (1) uses no statics + * (2) takes a u_char* not an in_addr as input + * author: + * Paul Vixie, 1996. + */ +Static const char * +inet_ntop4(const u_char *src, char *dst, size_t size) +{ + char tmp[sizeof "255.255.255.255"]; + int l; + + l = snprintf(tmp, sizeof(tmp), "%u.%u.%u.%u", + src[0], src[1], src[2], src[3]); + if (l <= 0 || l >= size) { + return (NULL); + } + strlcpy(dst, tmp, size); + return (dst); +} + +#ifdef INET6 +/* const char * + * inet_ntop6(src, dst, size) + * convert IPv6 binary address into presentation (printable) format + * author: + * Paul Vixie, 1996. + */ +Static const char * +inet_ntop6(const u_char *src, char *dst, size_t size) +{ + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"]; + char *tp, *ep; + struct { int base, len; } best, cur; +#define IN6ADDRSZ 16 +#define INT16SZ 2 + u_int words[IN6ADDRSZ / INT16SZ]; + int i; + int advance; + + /* + * Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, '\0', sizeof words); + for (i = 0; i < IN6ADDRSZ; i++) + words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); + best.base = -1; + best.len = 0; + cur.base = -1; + cur.len = 0; + for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { + if (words[i] == 0) { + if (cur.base == -1) + cur.base = i, cur.len = 1; + else + cur.len++; + } else { + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + } + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + } + if (best.base != -1 && best.len < 2) + best.base = -1; + + /* + * Format the result. + */ + tp = tmp; + ep = tmp + sizeof(tmp); + for (i = 0; i < (IN6ADDRSZ / INT16SZ) && tp < ep; i++) { + /* Are we inside the best run of 0x00's? */ + if (best.base != -1 && i >= best.base && + i < (best.base + best.len)) { + if (i == best.base) { + if (tp + 1 >= ep) + return (NULL); + *tp++ = ':'; + } + continue; + } + /* Are we following an initial run of 0x00s or any real hex? */ + if (i != 0) { + if (tp + 1 >= ep) + return (NULL); + *tp++ = ':'; + } + /* Is this address an encapsulated IPv4? */ + if (i == 6 && best.base == 0 && + (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { + if (!inet_ntop4(src+12, tp, (size_t)(ep - tp))) + return (NULL); + tp += strlen(tp); + break; + } + advance = snprintf(tp, ep - tp, "%x", words[i]); + if (advance <= 0 || advance >= ep - tp) + return (NULL); + tp += advance; + } + /* Was it a trailing run of 0x00's? */ + if (best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ)) { + if (tp + 1 >= ep) + return (NULL); + *tp++ = ':'; + } + if (tp + 1 >= ep) + return (NULL); + *tp++ = '\0'; + + /* + * Check for overflow, copy, and we're done. + */ + if ((size_t)(tp - tmp) > size) { + return (NULL); + } + strlcpy(dst, tmp, size); + return (dst); +} +#endif /* INET6 */ diff --git a/sys/dev/usb/if_umbreg.h b/sys/dev/usb/if_umbreg.h new file mode 100644 index 00000000000..1fe97910b29 --- /dev/null +++ b/sys/dev/usb/if_umbreg.h @@ -0,0 +1,407 @@ +/* $NetBSD: if_umbreg.h,v 1.1 2018/07/31 16:44:29 khorben Exp $ */ +/* $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 + */ +#ifndef _IF_UMBREG_H_ +#define _IF_UMBREG_H_ + +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; + uint16_t 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; + +#define UMB_USERNAME_MAXLEN 205 + uint16_t username[UMB_USERNAME_MAXLEN]; + int usernamelen; + +#define UMB_PASSWORD_MAXLEN 205 + uint16_t password[UMB_PASSWORD_MAXLEN]; + int passwordlen; + + 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; + + uint16_t username[UMB_USERNAME_MAXLEN]; + int usernamelen; + + uint16_t password[UMB_PASSWORD_MAXLEN]; + int passwordlen; + +#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]; +}; + +#if !defined(ifr_mtu) +#define ifr_mtu ifr_ifru.ifru_metric +#endif + +#ifdef _KERNEL +/* + * UMB device + */ +struct umb_softc { + device_t sc_dev; + struct ifnet sc_if; +#define GET_IFP(sc) (&(sc)->sc_if) + struct ifmedia sc_im; + krndsource_t sc_rnd_source; + 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; + callout_t sc_statechg_timer; + char sc_dying; + char sc_attached; + + 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 */ + +#endif /* _IF_UMBREG_H_ */ diff --git a/sys/dev/usb/mbim.h b/sys/dev/usb/mbim.h new file mode 100644 index 00000000000..d3deffcfc12 --- /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.h b/sys/dev/usb/usb.h index f56b2ef86d7..150f01841fe 100644 --- a/sys/dev/usb/usb.h +++ b/sys/dev/usb/usb.h @@ -1,4 +1,4 @@ -/* $NetBSD: usb.h,v 1.115 2017/01/19 16:05:00 skrll Exp $ */ +/* $NetBSD: usb.h,v 1.116 2018/07/31 16:44:30 khorben Exp $ */ /* * Copyright (c) 1998 The NetBSD Foundation, Inc. @@ -205,6 +205,7 @@ typedef struct { typedef struct { uByte bLength; uByte bDescriptorType; + uByte bDescriptorSubtype; } UPACKED usb_descriptor_t; typedef struct { @@ -659,16 +660,19 @@ typedef struct { #define UISUBCLASS_VIDEOCOLLECTION 3 #define UICLASS_CDC 0x02 /* communication */ -#define UISUBCLASS_DIRECT_LINE_CONTROL_MODEL 1 +#define UISUBCLASS_DIRECT_LINE_CONTROL_MODEL 1 #define UISUBCLASS_ABSTRACT_CONTROL_MODEL 2 -#define UISUBCLASS_TELEPHONE_CONTROL_MODEL 3 -#define UISUBCLASS_MULTICHANNEL_CONTROL_MODEL 4 -#define UISUBCLASS_CAPI_CONTROLMODEL 5 -#define UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL 6 -#define UISUBCLASS_ATM_NETWORKING_CONTROL_MODEL 7 -#define UIPROTO_CDC_NOCLASS 0 /* no class specific +#define UISUBCLASS_TELEPHONE_CONTROL_MODEL 3 +#define UISUBCLASS_MULTICHANNEL_CONTROL_MODEL 4 +#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 +#define UIPROTO_CDC_AT 1 #define UICLASS_HID 0x03 #define UISUBCLASS_BOOT 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/usbdi.c b/sys/dev/usb/usbdi.c index ce4ed82e179..03dab2f7e78 100644 --- a/sys/dev/usb/usbdi.c +++ b/sys/dev/usb/usbdi.c @@ -1,4 +1,4 @@ -/* $NetBSD: usbdi.c,v 1.175 2017/10/28 00:37:12 pgoyette Exp $ */ +/* $NetBSD: usbdi.c,v 1.176 2018/07/31 16:44:30 khorben Exp $ */ /* * Copyright (c) 1998, 2012, 2015 The NetBSD Foundation, Inc. @@ -32,7 +32,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: usbdi.c,v 1.175 2017/10/28 00:37:12 pgoyette Exp $"); +__KERNEL_RCSID(0, "$NetBSD: usbdi.c,v 1.176 2018/07/31 16:44:30 khorben Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" @@ -70,6 +70,7 @@ static void *usbd_alloc_buffer(struct usbd_xfer *, uint32_t); static void usbd_free_buffer(struct usbd_xfer *); static struct usbd_xfer *usbd_alloc_xfer(struct usbd_device *, unsigned int); static usbd_status usbd_free_xfer(struct usbd_xfer *); +static void usbd_request_async_cb(struct usbd_xfer *, void *, usbd_status); #if defined(USB_DEBUG) void @@ -1120,6 +1121,36 @@ usbd_do_request_flags(struct usbd_device *dev, usb_device_request_t *req, return err; } +static void +usbd_request_async_cb(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + usbd_free_xfer(xfer); +} + +/* + * Execute a request without waiting for completion. + * Can be used from interrupt context. + */ +usbd_status +usbd_request_async(struct usbd_device *dev, struct usbd_xfer *xfer, + usb_device_request_t *req, void *priv, usbd_callback callback) +{ + usbd_status err; + + if (callback == NULL) + callback = usbd_request_async_cb; + + usbd_setup_default_xfer(xfer, dev, priv, + USBD_DEFAULT_TIMEOUT, req, NULL, UGETW(req->wLength), 0, + callback); + err = usbd_transfer(xfer); + if (err != USBD_IN_PROGRESS) { + usbd_free_xfer(xfer); + return (err); + } + return (USBD_NORMAL_COMPLETION); +} + const struct usbd_quirks * usbd_get_quirks(struct usbd_device *dev) { diff --git a/sys/dev/usb/usbdi.h b/sys/dev/usb/usbdi.h index 0eccd42ab2a..291e87d1226 100644 --- a/sys/dev/usb/usbdi.h +++ b/sys/dev/usb/usbdi.h @@ -1,4 +1,4 @@ -/* $NetBSD: usbdi.h,v 1.93 2018/07/29 01:59:46 riastradh Exp $ */ +/* $NetBSD: usbdi.h,v 1.94 2018/07/31 16:44:30 khorben Exp $ */ /* $FreeBSD: src/sys/dev/usb/usbdi.h,v 1.18 1999/11/17 22:33:49 n_hibma Exp $ */ /* @@ -139,6 +139,8 @@ usbd_status usbd_sync_transfer(struct usbd_xfer *); usbd_status usbd_sync_transfer_sig(struct usbd_xfer *); usbd_status usbd_do_request(struct usbd_device *, usb_device_request_t *, void *); +usbd_status usbd_request_async(struct usbd_device *, struct usbd_xfer *, + usb_device_request_t *, void *, usbd_callback); usbd_status usbd_do_request_flags(struct usbd_device *, usb_device_request_t *, void *, uint16_t, int *, uint32_t); |
