This is part 2 of the MBIM patch. It adds the mbim driver to i386 and amd64 kernels.
Index: sys/arch/amd64/conf/GENERIC =================================================================== RCS file: /cvs/src/sys/arch/amd64/conf/GENERIC,v retrieving revision 1.418 diff -u -p -u -p -r1.418 GENERIC --- sys/arch/amd64/conf/GENERIC 7 May 2016 23:10:50 -0000 1.418 +++ sys/arch/amd64/conf/GENERIC 23 May 2016 09:50:08 -0000 @@ -279,6 +279,7 @@ urtw* at uhub? # Realtek 8187 rsu* at uhub? # Realtek RTL8188SU/RTL8191SU/RTL8192SU urtwn* at uhub? # Realtek RTL8188CU/RTL8192CU udcf* at uhub? # Gude Expert mouseCLOCK +mbim* at uhub? # Mobile Broadband Interface Model uthum* at uhidev? # TEMPerHUM sensor ugold* at uhidev? # gold TEMPer sensor utrh* at uhidev? # USBRH sensor Index: sys/arch/i386/conf/GENERIC =================================================================== RCS file: /cvs/src/sys/arch/i386/conf/GENERIC,v retrieving revision 1.814 diff -u -p -u -p -r1.814 GENERIC --- sys/arch/i386/conf/GENERIC 24 Apr 2016 17:30:31 -0000 1.814 +++ sys/arch/i386/conf/GENERIC 23 May 2016 09:50:08 -0000 @@ -14,6 +14,9 @@ include "../../../conf/GENERIC" maxusers 80 # estimated number of users option USER_PCICONF # user-space PCI configuration +option USB_DEBUG +#option UHUB_DEBUG +option EHCI_DEBUG #option VM86 # Virtual 8086 emulation option KVM86 # Kernel Virtual 8086 emulation @@ -314,6 +317,7 @@ rsu* at uhub? # Realtek RTL8188SU/RTL81 urtwn* at uhub? # Realtek RTL8188CU/RTL8192CU udcf* at uhub? # Gude Expert mouseCLOCK umbg* at uhub? # Meinberg Funkuhren USB5131 +mbim* at uhub? # Mobile Broadband Interface Model uthum* at uhidev? # TEMPerHUM sensor ugold* at uhidev? # gold TEMPer sensor utrh* at uhidev? # USBRH sensor Index: sys/dev/usb/files.usb =================================================================== RCS file: /cvs/src/sys/dev/usb/files.usb,v retrieving revision 1.126 diff -u -p -u -p -r1.126 files.usb --- sys/dev/usb/files.usb 8 Jan 2016 15:54:13 -0000 1.126 +++ sys/dev/usb/files.usb 23 May 2016 09:50:08 -0000 @@ -397,6 +397,11 @@ device otus: ether, ifnet, ifmedia, wlan attach otus at uhub file dev/usb/if_otus.c otus +# Mobile Broadband Interface Model +device mbim: ifnet, ifmedia +attach mbim at uhub +file dev/usb/if_mbim.c mbim + # USB logical device device usbf {} attach usbf at usbdev Index: sys/dev/usb/if_mbim.c =================================================================== RCS file: sys/dev/usb/if_mbim.c diff -N sys/dev/usb/if_mbim.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/dev/usb/if_mbim.c 23 May 2016 09:50:08 -0000 @@ -0,0 +1,2487 @@ +/* $OpenBSD$ */ + +/* + * 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 + */ +#include "bpfilter.h" + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/socket.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/time.h> +#include <sys/timeout.h> +#include <sys/kernel.h> +#include <sys/syslog.h> + +#if NBPFILTER > 0 +#include <net/bpf.h> +#endif +#include <net/if.h> +#include <net/netisr.h> +#include <net/if_arp.h> +#include <net/if_var.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> + +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdevs.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/mbim.h> +#include <dev/usb/if_mbim.h> + +#ifdef MBIM_DEBUG +#define DPRINTF(x...) \ + do { if (mbim_debug) log(LOG_DEBUG, x); } while (0) + +#define DPRINTFN(n, x...) \ + do { if (mbim_debug >= (n)) log(LOG_DEBUG, x); } while (0) + +#define DDUMPN(n, b, l) \ + do { \ + if (mbim_debug >= (n)) \ + mbim_dump((b), (l)); \ + } while (0) + +int mbim_debug = 0; +char *mbim_uuid2str(uint8_t [MBIM_UUID_LEN]); +void mbim_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) (((struct mbim_softc *)(sc))->sc_dev.dv_xname) + +/* + * State change timeout + */ +#define MBIM_STATE_CHANGE_TIMEOUT 30 + +/* + * State change flags + */ +#define MBIM_NS_DONT_DROP 0x0001 /* do not drop below current state */ +#define MBIM_NS_DONT_RAISE 0x0002 /* do not raise below current state */ + +/* + * Diagnostic macros + */ +const struct mbim_valdescr mbim_regstates[] = MBIM_REGSTATE_DESCRIPTIONS; +const struct mbim_valdescr mbim_dataclasses[] = MBIM_DATACLASS_DESCRIPTIONS; +const struct mbim_valdescr mbim_simstate[] = MBIM_SIMSTATE_DESCRIPTIONS; +const struct mbim_valdescr mbim_messages[] = MBIM_MESSAGES_DESCRIPTIONS; +const struct mbim_valdescr mbim_status[] = MBIM_STATUS_DESCRIPTIONS; +const struct mbim_valdescr mbim_cids[] = MBIM_CID_DESCRIPTIONS; +const struct mbim_valdescr mbim_pktstate[] = MBIM_PKTSRV_STATE_DESCRIPTIONS; +const struct mbim_valdescr mbim_actstate[] = MBIM_ACTIVATION_STATE_DESCRIPTIONS; +const struct mbim_valdescr mbim_error[] = MBIM_ERROR_DESCRIPTIONS; +const struct mbim_valdescr mbim_pintype[] = MBIM_PINTYPE_DESCRIPTIONS; +const struct mbim_valdescr mbim_istate[] = MBIM_INTERNAL_STATE_DESCRIPTIONS; + +#define mbim_regstate(c) mbim_val2descr(mbim_regstates, (c)) +#define mbim_dataclass(c) mbim_val2descr(mbim_dataclasses, (c)) +#define mbim_simstate(s) mbim_val2descr(mbim_simstate, (s)) +#define mbim_request2str(m) mbim_val2descr(mbim_messages, (m)) +#define mbim_status2str(s) mbim_val2descr(mbim_status, (s)) +#define mbim_cid2str(c) mbim_val2descr(mbim_cids, (c)) +#define mbim_packet_state(s) mbim_val2descr(mbim_pktstate, (s)) +#define mbim_activation(s) mbim_val2descr(mbim_actstate, (s)) +#define mbim_error2str(e) mbim_val2descr(mbim_error, (e)) +#define mbim_pin_type(t) mbim_val2descr(mbim_pintype, (t)) +#define mbim_istate(s) mbim_val2descr(mbim_istate, (s)) + +/* + * Saved original interface address + * See "struct in_ifaddr". + */ +struct saved_ifaddr { + uint32_t netmask; + struct sockaddr_in addr; + struct sockaddr_in dst; + struct sockaddr_in sockmask; +}; + +int mbim_match(struct device *, void *, void *); +void mbim_attach(struct device *, struct device *, void *); +int mbim_detach(struct device *, int); +int mbim_activate(struct device *, int); +int mbim_alloc_xfers(struct mbim_softc *); +void mbim_free_xfers(struct mbim_softc *); +int mbim_alloc_bulkpipes(struct mbim_softc *); +void mbim_close_bulkpipes(struct mbim_softc *); +int mbim_ioctl(struct ifnet *, u_long, caddr_t); +int mbim_output(struct ifnet *, struct mbuf *, struct sockaddr *, + struct rtentry *); +int mbim_input(struct ifnet *, struct mbuf *, void *); +void mbim_start(struct ifnet *); +void mbim_watchdog(struct ifnet *); +void mbim_statechg_timeout(void *); + +void mbim_newstate(struct mbim_softc *, enum mbim_state, int); +void mbim_state_task(void *); +void mbim_up(struct mbim_softc *); +void mbim_down(struct mbim_softc *, int); +void mbim_linkstate(struct mbim_softc *, int); +void mbim_set_ipv4addr(struct ifnet *, struct in_ifaddr *, int); +void mbim_restore_ipv4addr(struct mbim_softc *); + +void mbim_get_response_task(void *); + +void mbim_decode_response(struct mbim_softc *, void *, int); +void mbim_handle_indicate_status_msg(struct mbim_softc *, void *, + int); +void mbim_handle_opendone_msg(struct mbim_softc *, void *, int); +void mbim_handle_closedone_msg(struct mbim_softc *, void *, int); +int mbim_decode_register_state(struct mbim_softc *, void *, int); +int mbim_decode_devices_caps(struct mbim_softc *, void *, int); +int mbim_decode_subscriber_status(struct mbim_softc *, void *, + int); +int mbim_decode_radio_state(struct mbim_softc *, void *, int); +int mbim_decode_pin(struct mbim_softc *, void *, int); +int mbim_decode_packet_service(struct mbim_softc *, void *, int); +int mbim_decode_signal_state(struct mbim_softc *, void *, int); +int mbim_decode_connect_info(struct mbim_softc *, void *, int); +int mbim_decode_ip_configuration(struct mbim_softc *, void *, int); +void mbim_update_gw(struct ifnet *); +int mbim_update_gw_walker(struct rtentry *, void *, unsigned int); + +void mbim_rx(struct mbim_softc *); +void mbim_rxeof(struct usbd_xfer *, void *, usbd_status); +int mbim_encap(struct mbim_softc *, struct mbuf *); +void mbim_txeof(struct usbd_xfer *, void *, usbd_status); +void mbim_decap(struct mbim_softc *, struct usbd_xfer *); + +usbd_status mbim_send_encap_command(struct mbim_softc *, void *, int); +int mbim_get_encap_response(struct mbim_softc *, void *, int *); +void mbim_ctrl_msg(struct mbim_softc *, uint32_t, void *, int); + +void mbim_open(struct mbim_softc *); +void mbim_close(struct mbim_softc *); + +int mbim_setpin(struct mbim_softc *, int, int, void *, int, + void *, int); +void mbim_setdataclass(struct mbim_softc *); +void mbim_radio(struct mbim_softc *, int); +void mbim_packet_service(struct mbim_softc *, int); +void mbim_connect(struct mbim_softc *); +void mbim_disconnect(struct mbim_softc *); +void mbim_send_connect(struct mbim_softc *, int); + +void mbim_qry_ipconfig(struct mbim_softc *); +void mbim_cmd(struct mbim_softc *, int, int, void *, int); +void mbim_command_done(struct mbim_softc *, void *, int); +void mbim_decode_cid(struct mbim_softc *, uint32_t, void *, int); + +void mbim_intr(struct usbd_xfer *, void *, usbd_status); + +char *mbim_ntop(struct sockaddr *); + +int mbim_xfer_tout = USBD_DEFAULT_TIMEOUT; + +uint8_t mbim_uuid_basic_connect[] = MBIM_UUID_BASIC_CONNECT; +uint8_t mbim_uuid_context_internet[] = MBIM_UUID_CONTEXT_INTERNET; +uint32_t mbim_session_id = 0; + +struct cfdriver mbim_cd = { + NULL, "mbim", DV_DULL +}; + +struct cfattach mbim_ca = { + sizeof (struct mbim_softc), + mbim_match, + mbim_attach, + mbim_detach, + mbim_activate, +}; + +/* + * Some devices are picky about too frequent control messages. + * Query device state not more often than every 0.5 secs. + */ +struct timeval mbim_update_rate = { 0, 500000 }; +int mbim_delay = 4000; + +/* + * Normally, MBIM devices are detected by their interface class and subclass. + * But for some models that have multiple configurations, it is better to + * match by vendor and product id so that we can select the desired + * configuration ourselves. + * + * OTOH, some devices identifiy themself als an MBIM device but fail to speak + * the MBIM protocol. + */ +struct mbim_products { + struct usb_devno dev; + int confno; +}; +const struct mbim_products mbim_devs[] = { + { { USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8305 }, 2 }, + { { USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_EM8805 }, 2 }, +}; +const struct usb_devno mbim_blacklist[] = { + /* Add blacklisted products here */ +}; + +#define mbim_lookup(vid, pid) \ + ((const struct mbim_products *)usb_lookup(mbim_devs, vid, pid)) + +int +mbim_match(struct device *parent, void *match, void *aux) +{ + struct usb_attach_arg *uaa = aux; + usb_interface_descriptor_t *id; + + if (usb_lookup(mbim_blacklist, uaa->vendor, uaa->product) != NULL) + return UMATCH_NONE; + if (mbim_lookup(uaa->vendor, uaa->product) != NULL) + return UMATCH_VENDOR_PRODUCT; + if (!uaa->iface) + return UMATCH_NONE; + if (!uaa->iface || + (id = usbd_get_interface_descriptor(uaa->iface)) == NULL) + return UMATCH_NONE; + if (id->bInterfaceClass != UICLASS_CDC || + id->bInterfaceSubClass != + UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL || + id->bNumEndpoints != 1) + return UMATCH_NONE; + + return UMATCH_DEVCLASS_DEVSUBCLASS; +} + +void +mbim_attach(struct device *parent, struct device *self, void *aux) +{ + struct mbim_softc *sc = (struct mbim_softc *)self; + struct usb_attach_arg *uaa = aux; + usbd_status status; + struct usbd_desc_iter iter; + const usb_descriptor_t *desc; + int v; + struct mbim_descriptor *md; + int i; + struct usbd_interface *ctrl_iface = NULL; + int ctrl_ep; + uint8_t data_ifaceno; + usb_interface_descriptor_t *id; + usb_config_descriptor_t *cd; + usb_endpoint_descriptor_t *ed; + int s; + struct ifnet *ifp; + int hard_mtu; + + sc->sc_udev = uaa->device; + + if (uaa->configno < 0) { + uaa->configno = mbim_lookup(uaa->vendor, uaa->product)->confno; + DPRINTF("%s: switching to config #%d\n", DEVNAM(sc), + uaa->configno); + status = usbd_set_config_no(sc->sc_udev, uaa->configno, 1); + if (status) { + printf("%s: failed to switch to config #%d: %s\n", + DEVNAM(sc), uaa->configno, usbd_errstr(status)); + goto fail; + } + } + + sc->sc_ver_maj = sc->sc_ver_min = -1; + usbd_desc_iter_init(sc->sc_udev, &iter); + hard_mtu = MBIM_MAXSEGSZ_MINVAL; + while ((desc = usbd_desc_iter_next(&iter))) { + if (desc->bDescriptorType != UDESC_CS_INTERFACE) + continue; + switch (desc->bDescriptorSubtype) { + case UDESCSUB_MBIM: + md = (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) { + printf("%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); + if (sc->sc_maxpktlen < MBIM_MAXSEGSZ_MINVAL) { + printf("%s: ignoring invalid segment size %d\n", + DEVNAM(sc), sc->sc_maxpktlen); + /* cont. anyway */ + sc->sc_maxpktlen = 8 * 1024; + } + hard_mtu = sc->sc_maxpktlen; + 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; + } + } + cd = usbd_get_config_descriptor(sc->sc_udev); + if (sc->sc_ver_maj < 0) { + printf("%s: missing MBIM descriptor\n", DEVNAM(sc)); + goto fail; + } + + for (i = 0; i < sc->sc_udev->cdesc->bNumInterface; i++) { + if (usbd_iface_claimed(sc->sc_udev, i)) + continue; + id = usbd_get_interface_descriptor(&sc->sc_udev->ifaces[i]); + if (id == NULL) + continue; + if (id->bInterfaceClass == UICLASS_CDC && + id->bInterfaceSubClass == + UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL) { + ctrl_iface = &sc->sc_udev->ifaces[i]; + sc->sc_ctrl_ifaceno = id->bInterfaceNumber; + usbd_claim_iface(sc->sc_udev, i); + } else if (id->bInterfaceClass == UICLASS_CDC_DATA && + id->bInterfaceSubClass == UISUBCLASS_DATA && + id->bInterfaceProtocol == UIPROTO_DATA_MBIM) { + sc->sc_data_iface = &sc->sc_udev->ifaces[i]; + data_ifaceno = id->bInterfaceNumber; + usbd_claim_iface(sc->sc_udev, i); + } + } + if (ctrl_iface == NULL) { + printf("%s: no control interface found\n", DEVNAM(sc)); + return; + } + if (sc->sc_data_iface == NULL) { + printf("%s: no data interface found\n", DEVNAM(sc)); + goto fail; + } + + id = usbd_get_interface_descriptor(ctrl_iface); + ctrl_ep = -1; + for (i = 0; i < id->bNumEndpoints && ctrl_ep == -1; i++) { + ed = usbd_interface2endpoint_descriptor(ctrl_iface, i); + if (ed == NULL) + break; + if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) + ctrl_ep = ed->bEndpointAddress; + } + if (ctrl_ep == -1) { + printf("%s: missing interrupt endpoint\n", DEVNAM(sc)); + goto fail; + } + + id = usbd_get_interface_descriptor(sc->sc_data_iface); + sc->sc_rx_ep = sc->sc_tx_ep = -1; + if ((status = usbd_set_interface(sc->sc_data_iface, + MBIM_INTERFACE_ALTSETTING))) { + printf("%s: select alt interface %d failed: %s\n", + DEVNAM(sc), MBIM_INTERFACE_ALTSETTING, usbd_errstr(status)); + goto fail; + } + id = usbd_get_interface_descriptor(sc->sc_data_iface); + for (i = 0; i < id->bNumEndpoints; i++) { + if ((ed = usbd_interface2endpoint_descriptor(sc->sc_data_iface, + i)) == NULL) + break; + if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) + sc->sc_rx_ep = ed->bEndpointAddress; + else if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT) + sc->sc_tx_ep = ed->bEndpointAddress; + } + if (sc->sc_rx_ep == -1 || sc->sc_tx_ep == -1) { + printf("%s: missing bulk endpoints\n", DEVNAM(sc)); + goto fail; + } + + DPRINTFN(2, "%s: ctrl-ifno#%d: ep-ctrl=%d, data-ifno#%d: ep-rx=%d, " + "ep-tx=%d\n", DEVNAM(sc), sc->sc_ctrl_ifaceno, + UE_GET_ADDR(ctrl_ep), data_ifaceno, + UE_GET_ADDR(sc->sc_rx_ep), UE_GET_ADDR(sc->sc_tx_ep)); + + usb_init_task(&sc->sc_mbim_task, mbim_state_task, sc, + USB_TASK_TYPE_GENERIC); + usb_init_task(&sc->sc_get_response_task, mbim_get_response_task, sc, + USB_TASK_TYPE_GENERIC); + timeout_set(&sc->sc_statechg_timer, mbim_statechg_timeout, sc); + + if (usbd_open_pipe_intr(ctrl_iface, ctrl_ep, USBD_SHORT_XFER_OK, + &sc->sc_ctrl_pipe, sc, &sc->sc_intr_msg, sizeof (sc->sc_intr_msg), + mbim_intr, USBD_DEFAULT_INTERVAL)) { + printf("%s: failed to open control pipe\n", DEVNAM(sc)); + goto fail; + } + sc->sc_resp_buf = malloc(sc->sc_ctrl_len, M_USBDEV, M_NOWAIT); + if (sc->sc_resp_buf == NULL) { + printf("%s: allocation of resp buffer failed\n", DEVNAM(sc)); + goto fail; + } + sc->sc_ctrl_msg = malloc(sc->sc_ctrl_len, M_USBDEV, M_NOWAIT); + if (sc->sc_ctrl_msg == NULL) { + printf("%s: allocation of ctrl msg buffer failed\n", + DEVNAM(sc)); + goto fail; + } + + sc->sc_info.regstate = MBIM_REGSTATE_UNKNOWN; + sc->sc_info.pin_attempts_left = MBIM_VALUE_UNKNOWN; + sc->sc_info.rssi = MBIM_VALUE_UNKNOWN; + sc->sc_info.ber = MBIM_VALUE_UNKNOWN; + + s = splnet(); + ifp = GET_IFP(sc); + ifp->if_flags = IFF_SIMPLEX | IFF_MULTICAST | IFF_POINTOPOINT; + ifp->if_ioctl = mbim_ioctl; + ifp->if_start = mbim_start; + + ifp->if_watchdog = mbim_watchdog; + strlcpy(ifp->if_xname, DEVNAM(sc), IFNAMSIZ); + ifp->if_link_state = LINK_STATE_DOWN; + + ifp->if_type = IFT_MBIM; + ifp->if_addrlen = 0; + ifp->if_hdrlen = sizeof (struct ncm_header16) + + sizeof (struct ncm_pointer16); + ifp->if_mtu = 1500; /* use a common default */ + ifp->if_hardmtu = hard_mtu; + ifp->if_output = mbim_output; + if_attach(ifp); + if_ih_insert(ifp, mbim_input, NULL); + if_alloc_sadl(ifp); + ifp->if_softc = sc; +#if NBPFILTER > 0 + bpfattach(&ifp->if_bpf, ifp, DLT_RAW, 0); +#endif + /* + * Open the device now so that we are able to query device information. + * XXX maybe close when done? + */ + mbim_open(sc); + splx(s); + + printf("%s: vers %d.%d\n", DEVNAM(sc), sc->sc_ver_maj, sc->sc_ver_min); + return; + +fail: + usbd_deactivate(sc->sc_udev); + return; +} + +int +mbim_detach(struct device *self, int flags) +{ + struct mbim_softc *sc = (struct mbim_softc *)self; + struct ifnet *ifp = GET_IFP(sc); + int s; + + s = splnet(); + if (ifp->if_flags & IFF_RUNNING) + mbim_down(sc, 1); + mbim_close(sc); + + usb_rem_wait_task(sc->sc_udev, &sc->sc_get_response_task); + if (timeout_initialized(&sc->sc_statechg_timer)) + timeout_del(&sc->sc_statechg_timer); + sc->sc_nresp = 0; + usb_rem_wait_task(sc->sc_udev, &sc->sc_mbim_task); + if (sc->sc_ctrl_pipe) { + usbd_close_pipe(sc->sc_ctrl_pipe); + sc->sc_ctrl_pipe = NULL; + } + if (sc->sc_ctrl_msg) { + free(sc->sc_ctrl_msg, M_USBDEV, sc->sc_ctrl_len); + sc->sc_ctrl_msg = NULL; + } + if (sc->sc_resp_buf) { + free(sc->sc_resp_buf, M_USBDEV, sc->sc_ctrl_len); + sc->sc_resp_buf = NULL; + } + if (sc->sc_saved_ifaddr != NULL && + sc->sc_saved_ifaddr != MBIM_IFADDR_NONE) + free(sc->sc_saved_ifaddr, M_USBDEV, + sizeof (struct saved_ifaddr)); + sc->sc_saved_ifaddr = NULL; + + if (ifp->if_softc != NULL) { + if_ih_remove(ifp, mbim_input, NULL); + if_detach(ifp); + } + + splx(s); + return 0; +} + +int +mbim_activate(struct device *self, int act) +{ + struct mbim_softc *sc = (struct mbim_softc *)self; + + switch (act) { + case DVACT_DEACTIVATE: + usbd_deactivate(sc->sc_udev); + break; + } + return 0; +} + +int +mbim_alloc_xfers(struct mbim_softc *sc) +{ + if (!sc->sc_rx_xfer) { + if ((sc->sc_rx_xfer = usbd_alloc_xfer(sc->sc_udev)) != NULL) + sc->sc_rx_buf = usbd_alloc_buffer(sc->sc_rx_xfer, + sc->sc_maxpktlen + MBIM_HDR32_LEN); + } + if (!sc->sc_tx_xfer) { + if ((sc->sc_tx_xfer = usbd_alloc_xfer(sc->sc_udev)) != NULL) + sc->sc_tx_buf = usbd_alloc_buffer(sc->sc_tx_xfer, + sc->sc_maxpktlen + MBIM_HDR16_LEN); + } + return (sc->sc_rx_buf && sc->sc_tx_buf) ? 1 : 0; +} + +void +mbim_free_xfers(struct mbim_softc *sc) +{ + if (sc->sc_rx_xfer) { + /* implicit usbd_free_buffer() */ + usbd_free_xfer(sc->sc_rx_xfer); + sc->sc_rx_xfer = NULL; + sc->sc_rx_buf = NULL; + } + if (sc->sc_tx_xfer) { + usbd_free_xfer(sc->sc_tx_xfer); + sc->sc_tx_xfer = NULL; + sc->sc_tx_buf = NULL; + } + if (sc->sc_tx_m) { + m_freem(sc->sc_tx_m); + sc->sc_tx_m = NULL; + } +} + +int +mbim_alloc_bulkpipes(struct mbim_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + + if (!(ifp->if_flags & IFF_RUNNING)) { + if (usbd_open_pipe(sc->sc_data_iface, sc->sc_rx_ep, + USBD_EXCLUSIVE_USE, &sc->sc_rx_pipe)) + return 0; + if (usbd_open_pipe(sc->sc_data_iface, sc->sc_tx_ep, + USBD_EXCLUSIVE_USE, &sc->sc_tx_pipe)) + return 0; + + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; + mbim_rx(sc); + } + return 1; +} + +void +mbim_close_bulkpipes(struct mbim_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; + } +} + +int +mbim_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct proc *p = curproc; + struct mbim_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + int s, error = 0; + struct mbim_parameter mp; + + if (usbd_is_dying(sc->sc_udev)) + return EIO; + + s = splnet(); + switch (cmd) { + case SIOCSIFADDR: + sc->sc_if.if_rtrequest = p2p_rtrequest; + break; + case SIOCSIFFLAGS: + usb_add_task(sc->sc_udev, &sc->sc_mbim_task); + break; + case SIOCGMBIMINFO: + error = copyout(&sc->sc_info, ifr->ifr_data, + sizeof (sc->sc_info)); + break; + case SIOCSMBIMPARAM: + if ((error = suser(p, 0)) != 0) + break; + if ((error = copyin(ifr->ifr_data, &mp, sizeof (mp))) != 0) + break; + + if ((error = mbim_setpin(sc, mp.op, mp.is_puk, + mp.pin, mp.pinlen, mp.newpin, mp.newpinlen)) != 0) + break; + + if (mp.apnlen < 0 || mp.apnlen > sizeof (sc->sc_info.apn)) { + error = EINVAL; + break; + } + sc->sc_roaming = mp.roaming ? 1 : 0; + memset(sc->sc_info.apn, 0, sizeof (sc->sc_info.apn)); + memcpy(sc->sc_info.apn, mp.apn, mp.apnlen); + sc->sc_info.apnlen = mp.apnlen; + sc->sc_info.preferredclasses = mp.preferredclasses; + mbim_setdataclass(sc); + break; + case SIOCGMBIMPARAM: + memset(&mp, 0, sizeof (mp)); + memcpy(mp.apn, sc->sc_info.apn, sc->sc_info.apnlen); + mp.apnlen = sc->sc_info.apnlen; + mp.roaming = sc->sc_roaming; + mp.preferredclasses = sc->sc_info.preferredclasses; + error = copyout(&mp, ifr->ifr_data, sizeof (mp)); + break; + case SIOCSIFMTU: + /* Does this include the NCM headers and tail? */ + if (ifr->ifr_mtu > ifp->if_hardmtu) { + error = EINVAL; + break; + } + ifp->if_mtu = ifr->ifr_mtu; + break; + case SIOCGIFMTU: + ifr->ifr_mtu = ifp->if_mtu; + break; + case SIOCGIFHARDMTU: + ifr->ifr_hardmtu = ifp->if_hardmtu; + break; + case SIOCAIFADDR: + case SIOCSIFDSTADDR: + case SIOCADDMULTI: + case SIOCDELMULTI: + break; + default: + error = ENOTTY; + break; + } + splx(s); + return error; +} + +int +mbim_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, + struct rtentry *rtp) +{ + struct mbim_softc *sc = ifp->if_softc; + + if (usbd_is_dying(sc->sc_udev) || !(ifp->if_flags & IFF_RUNNING)) { + m_freem(m); + return ENETDOWN; + } + return if_enqueue(ifp, m); +} + +int +mbim_input(struct ifnet *ifp, struct mbuf *m, void *cookie) +{ + struct niqueue *inq; + uint8_t ipv; + + if (((ifp->if_flags & IFF_UP) == 0) || (m->m_flags & M_FILDROP) != 0) { + m_freem(m); + return 1; + } + if (m->m_pkthdr.len < sizeof (struct ip)) { + ifp->if_ierrors++; + DPRINTFN(4, "%s: dropping short packet (len %d)\n", __func__, + m->m_pkthdr.len); + m_freem(m); + return 1; + } + m->m_pkthdr.ph_rtableid = ifp->if_rdomain; + m_copydata(m, 0, sizeof (ipv), &ipv); + ipv >>= 4; + + ifp->if_ibytes += m->m_pkthdr.len; + switch (ipv) { + case 4: + inq = &ipintrq; + break; + case 6: + inq = &ip6intrq; + break; + default: + ifp->if_ierrors++; + DPRINTFN(4, "%s: dropping packet with bad IP version (%d)\n", + __func__, ipv); + m_freem(m); + return 1; + } + niq_enqueue(inq, m); + return 1; +} + +void +mbim_start(struct ifnet *ifp) +{ + struct mbim_softc *sc = ifp->if_softc; + struct mbuf *m_head = NULL; + + if (usbd_is_dying(sc->sc_udev) || + !(ifp->if_flags & IFF_RUNNING) || + (ifp->if_flags & IFF_OACTIVE)) + return; + + m_head = ifq_deq_begin(&ifp->if_snd); + if (m_head == NULL) + return; + + if (!mbim_encap(sc, m_head)) { + ifq_deq_rollback(&ifp->if_snd, m_head); + ifp->if_flags |= IFF_OACTIVE; + return; + } + ifq_deq_commit(&ifp->if_snd, m_head); + +#if NBPFILTER > 0 + if (ifp->if_bpf) + bpf_mtap(ifp->if_bpf, m_head, BPF_DIRECTION_OUT); +#endif + + ifp->if_flags |= IFF_OACTIVE; + ifp->if_timer = (2 * mbim_xfer_tout) / 1000; +} + +void +mbim_watchdog(struct ifnet *ifp) +{ + struct mbim_softc *sc = ifp->if_softc; + + if (usbd_is_dying(sc->sc_udev)) + return; + + ifp->if_oerrors++; + log(LOG_WARNING, "%s: watchdog timeout\n", DEVNAM(sc)); + return; +} + +void +mbim_statechg_timeout(void *arg) +{ + struct mbim_softc *sc = arg; + + log(LOG_INFO, "%s: state change time out\n",DEVNAM(sc)); + usb_add_task(sc->sc_udev, &sc->sc_mbim_task); +} + +void +mbim_newstate(struct mbim_softc *sc, enum mbim_state newstate, int flags) +{ + if (newstate == sc->sc_state) + return; + if (((flags & MBIM_NS_DONT_DROP) && newstate < sc->sc_state) || + ((flags & MBIM_NS_DONT_RAISE) && newstate > sc->sc_state)) + return; + log(LOG_DEBUG, "%s: state going %s from '%s' to '%s'\n", DEVNAM(sc), + newstate > sc->sc_state ? "up" : "down", + mbim_istate(sc->sc_state), mbim_istate(newstate)); + sc->sc_state = newstate; + usb_add_task(sc->sc_udev, &sc->sc_mbim_task); +} + +void +mbim_state_task(void *arg) +{ + struct mbim_softc *sc = arg; + struct ifnet *ifp = GET_IFP(sc); + + int s; + + s = splnet(); + if (ifp->if_flags & IFF_UP) + mbim_up(sc); + else + mbim_down(sc, 0); + mbim_linkstate(sc, + sc->sc_state == MBIM_S_UP ? LINK_STATE_UP : LINK_STATE_DOWN); + splx(s); +} + + +void +mbim_up(struct mbim_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + + splassert(IPL_NET); + + switch (sc->sc_state) { + case MBIM_S_DOWN: + DPRINTF("%s: init: opening ...\n", DEVNAM(sc)); + mbim_open(sc); + break; + case MBIM_S_OPEN: + DPRINTF("%s: init: turning radio on ...\n", DEVNAM(sc)); + mbim_radio(sc, 1); + break; + case MBIM_S_RADIO: + DPRINTF("%s: init: checking SIM state ...\n", DEVNAM(sc)); + mbim_cmd(sc, MBIM_CID_SUBSCRIBER_READY_STATUS, MBIM_CMDOP_QRY, + NULL, 0); + break; + case MBIM_S_SIMREADY: + DPRINTF("%s: init: attaching ...\n", DEVNAM(sc)); + mbim_packet_service(sc, 1); + break; + case MBIM_S_ATTACHED: + sc->sc_tx_seq = 0; + if (!mbim_alloc_xfers(sc)) { + mbim_free_xfers(sc); + log(LOG_ERR, "%s: allocation of xfers failed\n", + DEVNAM(sc)); + break; + } + DPRINTF("%s: init: connecting ...\n", DEVNAM(sc)); + mbim_connect(sc); + break; + case MBIM_S_CONNECTED: + DPRINTF("%s: init: getting IP config ...\n", DEVNAM(sc)); + mbim_qry_ipconfig(sc); + break; + case MBIM_S_UP: + DPRINTF("%s: init: reached state UP\n", DEVNAM(sc)); + if (!mbim_alloc_bulkpipes(sc)) { + log(LOG_ERR, "%s: opening bulk pipes failed\n", + DEVNAM(sc)); + ifp->if_flags &= ~IFF_UP; + mbim_down(sc, 1); + } + break; + } + if (sc->sc_state < MBIM_S_UP) + timeout_add_sec(&sc->sc_statechg_timer, + MBIM_STATE_CHANGE_TIMEOUT); + else + timeout_del(&sc->sc_statechg_timer); + return; +} + +void +mbim_down(struct mbim_softc *sc, int force) +{ + splassert(IPL_NET); + + mbim_close_bulkpipes(sc); + if (sc->sc_state < MBIM_S_CONNECTED) + mbim_free_xfers(sc); + + switch (sc->sc_state) { + case MBIM_S_UP: + case MBIM_S_CONNECTED: + DPRINTF("%s: stop: disconnecting ...\n", DEVNAM(sc)); + mbim_disconnect(sc); + if (!force) + break; + /*FALLTHROUGH*/ + case MBIM_S_ATTACHED: + DPRINTF("%s: stop: detaching ...\n", DEVNAM(sc)); + mbim_packet_service(sc, 0); + if (!force) + break; + /*FALLTHROUGH*/ + case MBIM_S_SIMREADY: + case MBIM_S_RADIO: + DPRINTF("%s: stop: turning radio off ...\n", DEVNAM(sc)); + mbim_radio(sc, 0); + if (!force) + break; + /*FALLTHROUGH*/ + case MBIM_S_OPEN: + case MBIM_S_DOWN: + /* Do not close the device */ + DPRINTF("%s: stop: reached state DOWN\n", DEVNAM(sc)); + break; + } + if (force) + sc->sc_state = MBIM_S_OPEN; + + if (sc->sc_state > MBIM_S_OPEN) + timeout_add_sec(&sc->sc_statechg_timer, + MBIM_STATE_CHANGE_TIMEOUT); + else + timeout_del(&sc->sc_statechg_timer); +} + +void +mbim_linkstate(struct mbim_softc *sc, int state) +{ + struct ifnet *ifp = GET_IFP(sc); + int s; + + s = splnet(); + if (ifp->if_link_state != state) { + log(LOG_INFO, "%s: link state changed from %s to %s\n", + DEVNAM(sc), + LINK_STATE_IS_UP(ifp->if_link_state) ? "up" : "down", + LINK_STATE_IS_UP(state) ? "up" : "down"); + ifp->if_link_state = state; + if_link_state_change(ifp); + if (!LINK_STATE_IS_UP(state)) { + memset(sc->sc_info.ipv4dns, 0, + sizeof (sc->sc_info.ipv4dns)); + mbim_restore_ipv4addr(sc); + } + } + splx(s); +} + +void +mbim_set_ipv4addr(struct ifnet *ifp, struct in_ifaddr *ia, int newifaddr) +{ + struct sockaddr_in ifaddr; + int error; + + splassert(IPL_NET); + memset(&ifaddr, 0, sizeof (ifaddr)); + ifaddr.sin_family = ia->ia_addr.sin_family; + ifaddr.sin_len = sizeof(ifaddr); + ifaddr.sin_addr.s_addr = ia->ia_addr.sin_addr.s_addr; + + log(LOG_INFO, "%s: %s IPv4 addr is %s, mask %s, gateway %s\n", + DEVNAM(ifp->if_softc), newifaddr ? "new" : "changed", + mbim_ntop((struct sockaddr *)&ifaddr), + mbim_ntop((struct sockaddr *)&ia->ia_sockmask), + mbim_ntop((struct sockaddr *)&ia->ia_dstaddr)); + if (!newifaddr) + in_ifscrub(ifp, ia); + if ((error = in_ifinit(ifp, ia, &ifaddr, newifaddr)) == 0) + dohooks(ifp->if_addrhooks, 0); + else + log(LOG_ERR, "%s: unable to set address, error %d\n", + DEVNAM(ifp->if_softc), error); + mbim_update_gw(ifp); +} + +void +mbim_restore_ipv4addr(struct mbim_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + struct in_ifaddr *ia = NULL; + struct ifaddr *ifa; + + splassert(IPL_NET); + if (sc->sc_saved_ifaddr == NULL) + return; + + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { + if (ifa->ifa_addr->sa_family == AF_INET) { + if ((ia = ifatoia(ifa)) != NULL) + break; + } + } + if (ia == NULL) + return; + + if (sc->sc_saved_ifaddr == MBIM_IFADDR_NONE) { + log(LOG_DEBUG, "%s: purge IPv4 addr %s\n", DEVNAM(sc), + mbim_ntop((struct sockaddr *)&ia->ia_addr)); + in_purgeaddr(&ia->ia_ifa); + } else { + struct saved_ifaddr *sif = sc->sc_saved_ifaddr; + + memcpy(&ia->ia_netmask, &sif->netmask, sizeof (ia->ia_netmask)); + memcpy(&ia->ia_addr, &sif->addr, sizeof (ia->ia_addr)); + memcpy(&ia->ia_dstaddr, &sif->dst, sizeof (ia->ia_dstaddr)); + memcpy(&ia->ia_sockmask, &sif->sockmask, + sizeof (ia->ia_sockmask)); + mbim_set_ipv4addr(ifp, ia, 0); + free(sc->sc_saved_ifaddr, M_USBDEV, + sizeof (struct saved_ifaddr)); + } + sc->sc_saved_ifaddr = NULL; +} + +void +mbim_get_response_task(void *arg) +{ + struct mbim_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 (mbim_get_encap_response(sc, sc->sc_resp_buf, &len)) + mbim_decode_response(sc, sc->sc_resp_buf, len); + } + splx(s); +} + +void +mbim_decode_response(struct mbim_softc *sc, void *response, int len) +{ + struct mbim_msghdr *hdr = response; + struct mbim_fragmented_msg_hdr *fraghdr; + uint32_t type; + uint32_t tid; + + DPRINTFN(3, "%s: got response: len %d\n", DEVNAM(sc), len); + DDUMPN(4, response, len); + + if (len < sizeof (*hdr) || letoh32(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 = letoh32(hdr->type); + tid = letoh32(hdr->tid); + switch (type) { + case MBIM_INDICATE_STATUS_MSG: + case MBIM_COMMAND_DONE: + fraghdr = response; + if (letoh32(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), mbim_request2str(type), + 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 = letoh32(e->err); + + DPRINTF("%s: %s message, error %s (tid %u)\n", + DEVNAM(sc), mbim_request2str(type), + mbim_error2str(err), tid); + if (err == MBIM_ERROR_NOT_OPENED) + mbim_newstate(sc, MBIM_S_DOWN, 0); + } + break; + } + case MBIM_INDICATE_STATUS_MSG: + mbim_handle_indicate_status_msg(sc, response, len); + break; + case MBIM_OPEN_DONE: + mbim_handle_opendone_msg(sc, response, len); + break; + case MBIM_CLOSE_DONE: + mbim_handle_closedone_msg(sc, response, len); + break; + case MBIM_COMMAND_DONE: + mbim_command_done(sc, response, len); + break; + default: + DPRINTF("%s: discard messsage %s\n", DEVNAM(sc), + mbim_request2str(type)); + break; + } +} + +void +mbim_handle_indicate_status_msg(struct mbim_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), + mbim_request2str(letoh32(m->hdr.type))); + return; + } + if (memcmp(m->devid, mbim_uuid_basic_connect, sizeof (m->devid))) { + DPRINTF("%s: discard %s messsage for other UUID '%s'\n", + DEVNAM(sc), mbim_request2str(letoh32(m->hdr.type)), + mbim_uuid2str(m->devid)); + return; + } + infolen = letoh32(m->infolen); + if (len < sizeof (*m) + infolen) { + DPRINTF("%s: discard truncated %s messsage (want %d, got %d)\n", + DEVNAM(sc), mbim_request2str(letoh32(m->hdr.type)), + (int)sizeof (*m) + infolen, len); + return; + } + + cid = letoh32(m->cid); + DPRINTF("%s: indicate %s status\n", DEVNAM(sc), mbim_cid2str(cid)); + mbim_decode_cid(sc, cid, m->info, infolen); +} + +void +mbim_handle_opendone_msg(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_f2h_openclosedone *resp = data; + uint32_t status; + + status = letoh32(resp->status); + if (status == MBIM_STATUS_SUCCESS) { + if (sc->sc_maxsessions == 0) { + mbim_cmd(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_QRY, NULL, + 0); + mbim_cmd(sc, MBIM_CID_PIN, MBIM_CMDOP_QRY, NULL, 0); + mbim_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_QRY, + NULL, 0); + } + mbim_newstate(sc, MBIM_S_OPEN, MBIM_NS_DONT_DROP); + } else + log(LOG_ERR, "%s: open error: %s\n", DEVNAM(sc), + mbim_status2str(status)); + return; +} + +void +mbim_handle_closedone_msg(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_f2h_openclosedone *resp = data; + uint32_t status; + + status = letoh32(resp->status); + if (status == MBIM_STATUS_SUCCESS) + mbim_newstate(sc, MBIM_S_DOWN, 0); + else + DPRINTF("%s: close error: %s\n", DEVNAM(sc), + mbim_status2str(status)); + return; +} + +static inline void +mbim_getinfobuf(void *in, int inlen, uint32_t offs, uint32_t sz, + void *out, size_t outlen) +{ + offs = letoh32(offs); + sz = letoh32(sz); + if (inlen >= offs + sz) { + memset(out, 0, outlen); + memcpy(out, in + offs, MIN(sz, outlen)); + } +} + +static inline int +mbim_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 +mbim_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(buf + *offs, str, slen); + *offs += slen; + *offs += mbim_padding(buf, *offs, bufsz); + } else + *offsmember = htole32(0); + return 1; +} + +int +mbim_decode_register_state(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_cid_registration_state_info *rs = data; + + if (len < sizeof (*rs)) + return 0; + sc->sc_info.nwerror = letoh32(rs->nwerror); + sc->sc_info.regstate = letoh32(rs->regstate); + sc->sc_info.regmode = letoh32(rs->regmode); + sc->sc_info.cellclass = letoh32(rs->curcellclass); + + /* XXX should we remember the provider_id? */ + mbim_getinfobuf(data, len, rs->provname_offs, rs->provname_size, + sc->sc_info.provider, sizeof (sc->sc_info.provider)); + mbim_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), mbim_regstate(sc->sc_info.regstate), + letoh32(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) { + log(LOG_INFO, "%s: disconnecting from roaming network\n", + DEVNAM(sc)); + mbim_newstate(sc, MBIM_S_ATTACHED, MBIM_NS_DONT_RAISE); + } + return 1; +} + +int +mbim_decode_devices_caps(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_cid_device_caps *dc = data; + + if (len < sizeof (*dc)) + return 0; + sc->sc_maxsessions = letoh32(dc->max_sessions); + sc->sc_info.supportedclasses = letoh32(dc->dataclass); + mbim_getinfobuf(data, len, dc->devid_offs, dc->devid_size, + sc->sc_info.devid, sizeof (sc->sc_info.devid)); + mbim_getinfobuf(data, len, dc->fwinfo_offs, dc->fwinfo_size, + sc->sc_info.fwinfo, sizeof (sc->sc_info.fwinfo)); + mbim_getinfobuf(data, len, dc->hwinfo_offs, dc->hwinfo_size, + sc->sc_info.hwinfo, sizeof (sc->sc_info.hwinfo)); + DPRINTFN(2, "%s: max sessions %d, supported classes 0x%x\n", + DEVNAM(sc), sc->sc_maxsessions, sc->sc_info.supportedclasses); + return 1; +} + +int +mbim_decode_subscriber_status(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_cid_subscriber_ready_info *si = data; + int npn; + + if (len < sizeof (*si)) + return 0; + sc->sc_info.sim_state = letoh32(si->ready); + + mbim_getinfobuf(data, len, si->sid_offs, si->sid_size, + sc->sc_info.sid, sizeof (sc->sc_info.sid)); + mbim_getinfobuf(data, len, si->icc_offs, si->icc_size, + sc->sc_info.iccid, sizeof (sc->sc_info.iccid)); + + npn = letoh32(si->no_pn); + if (npn > 0) + mbim_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 = MBIM_PUK_REQUIRED; + log(LOG_INFO, "%s: SIM %s\n", DEVNAM(sc), + mbim_simstate(sc->sc_info.sim_state)); + if (sc->sc_info.sim_state == MBIM_SIMSTATE_INITIALIZED) + mbim_newstate(sc, MBIM_S_SIMREADY, MBIM_NS_DONT_DROP); + return 1; +} + +int +mbim_decode_radio_state(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_cid_radio_state_info *rs = data; + + if (len < sizeof (*rs)) + return 0; + + sc->sc_info.hw_radio_on = + (letoh32(rs->hw_state) == MBIM_RADIO_STATE_ON) ? 1 : 0; + sc->sc_info.sw_radio_on = + (letoh32(rs->sw_state) == MBIM_RADIO_STATE_ON) ? 1 : 0; + if (!sc->sc_info.hw_radio_on) { + log(LOG_INFO, "%s: radio is off by rfkill 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? + */ + mbim_newstate(sc, MBIM_S_OPEN, 0); + } else if (!sc->sc_info.sw_radio_on) { + log(LOG_INFO, "%s: radio is off\n", DEVNAM(sc)); + mbim_newstate(sc, MBIM_S_OPEN, 0); + } else + mbim_newstate(sc, MBIM_S_RADIO, MBIM_NS_DONT_DROP); + return 1; +} + +int +mbim_decode_pin(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_cid_pin_info *pi = data; + uint32_t attempts_left; + + if (len < sizeof (*pi)) + return 0; + + attempts_left = letoh32(pi->remaining_attempts); + if (attempts_left != 0xffffffff) + sc->sc_info.pin_attempts_left = attempts_left; + + switch (letoh32(pi->state)) { + case MBIM_PIN_STATE_UNLOCKED: + sc->sc_info.pin_state = MBIM_PIN_UNLOCKED; + break; + case MBIM_PIN_STATE_LOCKED: + switch (letoh32(pi->type)) { + case MBIM_PIN_TYPE_PIN1: + sc->sc_info.pin_state = MBIM_PIN_REQUIRED; + break; + case MBIM_PIN_TYPE_PUK1: + sc->sc_info.pin_state = MBIM_PUK_REQUIRED; + break; + case MBIM_PIN_TYPE_PIN2: + case MBIM_PIN_TYPE_PUK2: + /* Assume that PIN1 was accepted */ + sc->sc_info.pin_state = MBIM_PIN_UNLOCKED; + break; + } + break; + } + log(LOG_INFO, "%s: %s state %s (%d attempts left)\n", + DEVNAM(sc), mbim_pin_type(letoh32(pi->type)), + (letoh32(pi->state) == MBIM_PIN_STATE_UNLOCKED) ? + "unlocked" : "locked", + letoh32(pi->remaining_attempts)); + + /* + * In case the PIN was set after IFF_UP, retrigger the state machine + */ + usb_add_task(sc->sc_udev, &sc->sc_mbim_task); + return 1; +} + +int +mbim_decode_packet_service(struct mbim_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 = letoh32(psi->nwerror); + state = letoh32(psi->state); + highestclass = letoh32(psi->highest_dataclass); + up_speed = letoh64(psi->uplink_speed); + down_speed = letoh64(psi->downlink_speed); + if (sc->sc_info.packetstate != state || + sc->sc_info.uplink_speed != up_speed || + sc->sc_info.downlink_speed != down_speed) { + log(LOG_INFO, "%s: packet service ", DEVNAM(sc)); + if (sc->sc_info.packetstate != state) + addlog("changed from %s to ", + mbim_packet_state(sc->sc_info.packetstate)); + addlog("%s, class %s, speed: %llu up / %llu down\n", + mbim_packet_state(state), mbim_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: + mbim_newstate(sc, MBIM_S_ATTACHED, + MBIM_NS_DONT_DROP); + break; + default: + break; + } + } else + mbim_newstate(sc, MBIM_S_SIMREADY, MBIM_NS_DONT_RAISE); + } else switch (sc->sc_info.packetstate) { + case MBIM_PKTSERVICE_STATE_ATTACHED: + mbim_newstate(sc, MBIM_S_ATTACHED, MBIM_NS_DONT_DROP); + break; + case MBIM_PKTSERVICE_STATE_DETACHED: + mbim_newstate(sc, MBIM_S_SIMREADY, MBIM_NS_DONT_RAISE); + break; + } + return 1; +} + +int +mbim_decode_signal_state(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_cid_signal_state *ss = data; + int rssi; + + if (len < sizeof (*ss)) + return 0; + + if (letoh32(ss->rssi) == 99) + rssi = MBIM_VALUE_UNKNOWN; + else { + rssi = -113 + 2 * letoh32(ss->rssi); + if (sc->sc_info.rssi != rssi) + log(LOG_INFO, "%s: rssi %d dBm\n", DEVNAM(sc), rssi); + } + sc->sc_info.rssi = rssi; + sc->sc_info.ber = letoh32(ss->err_rate); + if (sc->sc_info.ber == -99) + sc->sc_info.ber = MBIM_VALUE_UNKNOWN; + return 1; +} + +int +mbim_decode_connect_info(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_cid_connect_info *ci = data; + int act; + + if (len < sizeof (*ci)) + return 0; + + if (letoh32(ci->sessionid) != mbim_session_id) { + DPRINTF("%s: discard connection info for session %u\n", + DEVNAM(sc), letoh32(ci->sessionid)); + return 1; + } + if (memcmp(ci->context, mbim_uuid_context_internet, + sizeof (ci->context))) { + DPRINTF("%s: discard connection info for other context\n", + DEVNAM(sc)); + return 1; + } + act = letoh32(ci->activation); + if (sc->sc_info.activation != act) { + log(LOG_INFO, "%s: connection %s\n", DEVNAM(sc), + mbim_activation(act)); + if (letoh32(ci->iptype) != MBIM_CONTEXT_IPTYPE_DEFAULT && + letoh32(ci->iptype) != MBIM_CONTEXT_IPTYPE_IPV4) + log(LOG_DEBUG, "%s: got iptype %d connection\n", + DEVNAM(sc), letoh32(ci->iptype)); + + sc->sc_info.activation = act; + sc->sc_info.nwerror = letoh32(ci->nwerror); + + if (sc->sc_info.activation == MBIM_ACTIVATION_STATE_ACTIVATED) + mbim_newstate(sc, MBIM_S_CONNECTED, MBIM_NS_DONT_DROP); + else if (sc->sc_info.activation == + MBIM_ACTIVATION_STATE_DEACTIVATED) + mbim_newstate(sc, MBIM_S_ATTACHED, 0); + /* else: other states are purely transitional */ + } + return 1; +} + +int +mbim_decode_ip_configuration(struct mbim_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_ifaddr *ia = NULL; + struct ifaddr *ifa; + int newifaddr = 0; + int ifaddr_changed = 0; + int state = -1; + + if (len < sizeof (*ic)) + return 0; + if (letoh32(ic->sessionid) != mbim_session_id) { + DPRINTF("%s: ignore IP configration for session id %d\n", + DEVNAM(sc), letoh32(ic->sessionid)); + return 0; + } + s = splnet(); + + /* + * IPv4 configuation + */ + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { + if (ifa->ifa_addr->sa_family == AF_INET) { + if ((ia = ifatoia(ifa)) != NULL) + break; + } + } + + avail = letoh32(ic->ipv4_available); + if (avail & MBIM_IPCONF_HAS_ADDRINFO) { + n = letoh32(ic->ipv4_naddr); + off = letoh32(ic->ipv4_addroffs); + + if (n == 0 || off + sizeof (ipv4elem) > len) + goto done; + + /* Only pick the first one */ + memcpy(&ipv4elem, data + off, sizeof (ipv4elem)); + ipv4elem.addr = letoh32(ipv4elem.addr); + ipv4elem.prefixlen = letoh32(ipv4elem.prefixlen); + if (ia == NULL) { + ia = malloc(sizeof (*ia), M_IFADDR, M_WAITOK | M_ZERO); + ia->ia_ifa.ifa_ifp = ifp; + newifaddr = 1; + /* No previous addr */ + sc->sc_saved_ifaddr = MBIM_IFADDR_NONE; + } else if (memcmp(&ipv4elem.addr, &ia->ia_addr.sin_addr.s_addr, + sizeof (&ipv4elem.addr))) { + ifaddr_changed = 1; + + /* + * First time here, save original settings so we can + * restore them later, when link goes down. + */ + if (sc->sc_saved_ifaddr == NULL) { + struct saved_ifaddr *sif; + + sif = malloc(sizeof (*sif), M_USBDEV, + M_WAITOK | M_ZERO); + memcpy(&sif->netmask, &ia->ia_netmask, + sizeof (sif->netmask)); + memcpy(&sif->addr, &ia->ia_addr, + sizeof (sif->addr)); + memcpy(&sif->dst, &ia->ia_dstaddr, + sizeof (sif->dst)); + memcpy(&sif->sockmask, &ia->ia_sockmask, + sizeof (sif->sockmask)); + sc->sc_saved_ifaddr = sif; + } + } + state = MBIM_S_UP; + } else if (ia != NULL) { + /* IP config has no address, but our interface has one. */ + state = MBIM_S_CONNECTED; + } + if (newifaddr || ifaddr_changed) { + ia->ia_addr.sin_family = AF_INET; + ia->ia_addr.sin_len = sizeof(ia->ia_addr); + ia->ia_addr.sin_addr.s_addr = ipv4elem.addr; + ia->ia_dstaddr.sin_family = AF_INET; + ia->ia_dstaddr.sin_len = sizeof(ia->ia_addr); + if (avail & MBIM_IPCONF_HAS_GWINFO) { + off = letoh32(ic->ipv4_gwoffs); + ia->ia_dstaddr.sin_addr.s_addr = + letoh32(*((uint32_t *)(data + off))); + } else if (newifaddr) + ia->ia_dstaddr.sin_addr.s_addr = INADDR_BROADCAST; + ia->ia_sockmask.sin_family = AF_INET; + ia->ia_sockmask.sin_len = sizeof(ia->ia_addr); + in_len2mask(&ia->ia_sockmask.sin_addr, ipv4elem.prefixlen); + ia->ia_netmask = ia->ia_sockmask.sin_addr.s_addr; + ia->ia_ifa.ifa_addr = sintosa(&ia->ia_addr); + ia->ia_ifa.ifa_dstaddr = sintosa(&ia->ia_dstaddr); + ia->ia_ifa.ifa_netmask = sintosa(&ia->ia_sockmask); + mbim_set_ipv4addr(ifp, ia, newifaddr); + } + + memset(sc->sc_info.ipv4dns, 0, sizeof (sc->sc_info.ipv4dns)); + if ((avail & MBIM_IPCONF_HAS_DNSINFO) && ia != NULL) { + n = letoh32(ic->ipv4_ndnssrv); + off = letoh32(ic->ipv4_dnssrvoffs); + i = 0; + while (n-- > 0) { + if (off + sizeof (uint32_t) > len) + break; + val = letoh32(*((uint32_t *)(data + off))); + if (i < MBIM_MAX_DNSSRV) + sc->sc_info.ipv4dns[i++] = val; + off += sizeof (uint32_t); + } + /* XXX add host routes for DNS servers? */ + } + + if ((avail & MBIM_IPCONF_HAS_MTUINFO)) { + val = letoh32(ic->ipv4_mtu); + if (ifp->if_hardmtu != val && val <= sc->sc_maxpktlen) { + ifp->if_hardmtu = val; + if (ifp->if_mtu > val) + ifp->if_mtu = val; + log(LOG_INFO, "%s: MTU is %d\n", DEVNAM(sc), val); + } + } + + avail = letoh32(ic->ipv6_available); + if (avail & MBIM_IPCONF_HAS_ADDRINFO) { + /* XXX FIXME: IPv6 configuation missing */ + log(LOG_INFO, "%s: ignoring IPv6 configuration\n", DEVNAM(sc)); + } + if (state != -1) + mbim_newstate(sc, state, 0); + +done: + splx(s); + return 1; +} + +/* Code copied from if_spppsubr.c */ +void +mbim_update_gw(struct ifnet *ifp) +{ + unsigned int tid; + + /* update routing table */ + for (tid = 0; tid <= RT_TABLEID_MAX; tid++) { + while (rtable_walk(tid, AF_INET, mbim_update_gw_walker, ifp) == + EAGAIN) + ; /* nothing */ + } +} + +int +mbim_update_gw_walker(struct rtentry *rt, void *arg, unsigned int id) +{ + struct ifnet *ifp = arg; + + if (rt->rt_ifidx == ifp->if_index) { + if (rt->rt_ifa->ifa_dstaddr->sa_family != + rt->rt_gateway->sa_family || + !ISSET(rt->rt_flags, RTF_GATEWAY)) + return 0; /* do not modify non-gateway routes */ + log(LOG_INFO, "%s: update gw %s -> %s\n", DEVNAM(ifp->if_softc), + mbim_ntop(rt->rt_gateway), + mbim_ntop(rt->rt_ifa->ifa_dstaddr)); + rt_setgate(rt, rt->rt_ifa->ifa_dstaddr); + } + return 0; +} + +void +mbim_rx(struct mbim_softc *sc) +{ + usbd_setup_xfer(sc->sc_rx_xfer, sc->sc_rx_pipe, sc, sc->sc_rx_buf, + sc->sc_maxpktlen, USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, mbim_rxeof); + usbd_transfer(sc->sc_rx_xfer); +} + +void +mbim_rxeof(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + struct mbim_softc *sc = priv; + struct ifnet *ifp = GET_IFP(sc); + + if (usbd_is_dying(sc->sc_udev) || !(ifp->if_flags & IFF_RUNNING)) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + if (usbd_ratecheck(&sc->sc_rx_ratechk)) + DPRINTF("%s: rx error: %s\n", DEVNAM(sc), + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_rx_pipe); + if (++sc->sc_rx_nerr > 100) { + log(LOG_ERR, "%s: too many rx errors, disabling\n", + DEVNAM(sc)); + usbd_deactivate(sc->sc_udev); + } + } else { + sc->sc_rx_nerr = 0; + mbim_decap(sc, xfer); + } + + mbim_rx(sc); + return; +} + +int +mbim_encap(struct mbim_softc *sc, struct mbuf *m) +{ + struct ncm_header16 *hdr; + struct ncm_pointer16 *ptr; + usbd_status err; + int len; + + KASSERT(sc->sc_tx_m == NULL); + + hdr = 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++; + USETW(hdr->wNdpIndex, sizeof (*hdr)); + + len = m->m_pkthdr.len; + USETDW(ptr->dwSignature, MBIM_NCM_NTH16_SIG(mbim_session_id)); + USETW(ptr->wLength, sizeof (*ptr)); + USETW(ptr->wNextNdpIndex, 0); + USETW(ptr->dgram[0].wDatagramIndex, MBIM_HDR16_LEN); + USETW(ptr->dgram[0].wDatagramLen, len); + USETW(ptr->dgram[1].wDatagramIndex, 0); + USETW(ptr->dgram[1].wDatagramLen, 0); + + m_copydata(m, 0, len, (caddr_t)(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_tx_pipe, sc, sc->sc_tx_buf, len, + USBD_FORCE_SHORT_XFER | USBD_NO_COPY, mbim_xfer_tout, mbim_txeof); + err = usbd_transfer(sc->sc_tx_xfer); + if (err != USBD_IN_PROGRESS) { + DPRINTF("%s: start tx error: %s\n", DEVNAM(sc), + usbd_errstr(err)); + return 0; + } + return 1; +} + +void +mbim_txeof(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + struct mbim_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); + } + } else { + ifp->if_opackets++; + if (IFQ_IS_EMPTY(&ifp->if_snd) == 0) + mbim_start(ifp); + } + + splx(s); +} + +void +mbim_decap(struct mbim_softc *sc, struct usbd_xfer *xfer) +{ + struct ifnet *ifp = GET_IFP(sc); + int s; + void *buf; + uint32_t len; + char *dp; + struct ncm_header16 *hdr16; + struct ncm_header32 *hdr32; + struct ncm_pointer16 *ptr16; + struct ncm_pointer16_dgram *dgram16; + struct ncm_pointer32_dgram *dgram32; + uint32_t hsig, psig; + int hlen, blen; + int ptrlen, ptroff, dgentryoff; + uint32_t doff, dlen; + struct mbuf_list ml = MBUF_LIST_INITIALIZER(); + struct mbuf *m; + + usbd_get_xfer_status(xfer, NULL, &buf, &len, NULL); + DPRINTFN(4, "%s: recv %d bytes\n", DEVNAM(sc), len); + DDUMPN(5, buf, len); + s = splnet(); + if (len < sizeof (*hdr16)) + goto toosmall; + if (len > sc->sc_maxpktlen) { + DPRINTF("%s: packet too large (%d)\n", DEVNAM(sc), len); + goto fail; + } + + hdr16 = (struct ncm_header16 *)buf; + hsig = UGETDW(hdr16->dwSignature); + hlen = UGETW(hdr16->wHeaderLength); + switch (hsig) { + case NCM_HDR16_SIG: + blen = UGETW(hdr16->wBlockLength); + 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); + 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 < hlen) + goto toosmall; + if (len < blen) { + DPRINTF("%s: bad NTB len (%d) for %d bytes of data\n", + DEVNAM(sc), blen, len); + goto fail; + } + + ptroff = hlen; + 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); + if (m == NULL) { + ifp->if_iqdrops++; + continue; + } + + ml_enqueue(&ml, m); + } +done: + if_input(ifp, &ml); + splx(s); + return; +toosmall: + DPRINTF("%s: packet too small (%d)\n", DEVNAM(sc), len); +fail: + ifp->if_ierrors++; + splx(s); +} + +usbd_status +mbim_send_encap_command(struct mbim_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 ((xfer = usbd_alloc_xfer(sc->sc_udev)) == NULL) + return USBD_NOMEM; + if ((buf = usbd_alloc_buffer(xfer, len)) == NULL) { + usbd_free_xfer(xfer); + return USBD_NOMEM; + } + memcpy(buf, data, len); + + /* XXX FIXME: if (total len > sc->sc_ctrl_len) => must fragment */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ctrl_ifaceno); + USETW(req.wLength, len); + DELAY(mbim_delay); + return usbd_request_async(xfer, &req, NULL, NULL); +} + +int +mbim_get_encap_response(struct mbim_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(mbim_delay); + err = usbd_do_request_flags(sc->sc_udev, &req, buf, USBD_SHORT_XFER_OK, + len, mbim_xfer_tout); + if (err == USBD_NORMAL_COMPLETION) + return 1; + DPRINTF("%s: ctrl recv: %s\n", DEVNAM(sc), usbd_errstr(err)); + return 0; +} + +void +mbim_ctrl_msg(struct mbim_softc *sc, uint32_t req, void *data, int len) +{ + uint32_t tid; + struct mbim_msghdr *hdr = data; + usbd_status err; + int s; + + assertwaitok(); + if (usbd_is_dying(sc->sc_udev)) + return; + if (len < sizeof (*hdr)) + return; + tid = ++sc->sc_tid; + + hdr->type = htole32(req); + hdr->len = htole32(len); + hdr->tid = htole32(tid); + +#ifdef MBIM_DEBUG + if (mbim_debug) { + const char *op, *str; + if (req == MBIM_COMMAND_MSG) { + struct mbim_h2f_cmd *c = data; + if (letoh32(c->op) == MBIM_CMDOP_SET) + op = "set"; + else + op = "qry"; + str = mbim_cid2str(letoh32(c->cid)); + } else { + op = "snd"; + str = mbim_request2str(req); + } + DPRINTF("%s: -> %s %s (tid %u)\n", DEVNAM(sc), op, str, tid); + } +#endif + s = splusb(); + err = mbim_send_encap_command(sc, data, len); + splx(s); + if (err != USBD_NORMAL_COMPLETION) { + log(LOG_ERR, "%s: send %s msg (tid %u) failed: %s\n", + DEVNAM(sc), mbim_request2str(req), tid, usbd_errstr(err)); + + /* will affect other transactions, too */ + usbd_abort_pipe(sc->sc_udev->default_pipe); + } else { + DPRINTFN(2, "%s: sent %s (tid %u)\n", DEVNAM(sc), + mbim_request2str(req), tid); + DDUMPN(3, data, len); + } + return; +} + +void +mbim_open(struct mbim_softc *sc) +{ + struct mbim_h2f_openmsg msg; + + memset(&msg, 0, sizeof (msg)); + msg.maxlen = htole32(sc->sc_ctrl_len); + mbim_ctrl_msg(sc, MBIM_OPEN_MSG, &msg, sizeof (msg)); + return; +} + +void +mbim_close(struct mbim_softc *sc) +{ + struct mbim_h2f_closemsg msg; + + memset(&msg, 0, sizeof (msg)); + mbim_ctrl_msg(sc, MBIM_CLOSE_MSG, &msg, sizeof (msg)); +} + +int +mbim_setpin(struct mbim_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 (!mbim_addstr(&cp, sizeof (cp), &off, pin, pinlen, + &cp.pin_offs, &cp.pin_size)) + return EINVAL; + + cp.op = htole32(op); + if (newpinlen) { + if (!mbim_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 (!mbim_addstr(&cp, sizeof (cp), &off, NULL, 0, + &cp.newpin_offs, &cp.newpin_size)) + return EINVAL; + } + mbim_cmd(sc, MBIM_CID_PIN, MBIM_CMDOP_SET, &cp, off); + return 0; +} + +void +mbim_setdataclass(struct mbim_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); + mbim_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_SET, &rs, sizeof (rs)); +} + +void +mbim_radio(struct mbim_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); + mbim_cmd(sc, MBIM_CID_RADIO_STATE, MBIM_CMDOP_SET, &s, sizeof (s)); +} + +void +mbim_packet_service(struct mbim_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); + mbim_cmd(sc, MBIM_CID_PACKET_SERVICE, MBIM_CMDOP_SET, &s, sizeof (s)); +} + +void +mbim_connect(struct mbim_softc *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; + } + log(LOG_DEBUG, "%s: connecting ...\n", DEVNAM(sc)); + mbim_send_connect(sc, MBIM_CONNECT_ACTIVATE); +} + +void +mbim_disconnect(struct mbim_softc *sc) +{ + log(LOG_DEBUG, "%s: disconnecting ...\n", DEVNAM(sc)); + mbim_send_connect(sc, MBIM_CONNECT_DEACTIVATE); +} + +void +mbim_send_connect(struct mbim_softc *sc, int command) +{ + struct mbim_cid_connect *c; + int off; + + /* Too large or the stack */ + c = malloc(sizeof (*c), M_USBDEV, M_WAIT|M_ZERO); + c->sessionid = htole32(mbim_session_id); + c->command = htole32(command); + off = offsetof(struct mbim_cid_connect, data); + if (!mbim_addstr(c, sizeof (*c), &off, sc->sc_info.apn, + sc->sc_info.apnlen, &c->access_offs, &c->access_size)) + goto done; + /* XXX FIXME: support user name and passphrase */ + c->user_offs = htole32(0); + c->user_size = htole32(0); + c->passwd_offs = htole32(0); + c->passwd_size = htole32(0); + c->authprot = htole32(MBIM_AUTHPROT_NONE); + c->compression = htole32(MBIM_COMPRESSION_NONE); + c->iptype = htole32(MBIM_CONTEXT_IPTYPE_IPV4); + memcpy(c->context, mbim_uuid_context_internet, sizeof (c->context)); + mbim_cmd(sc, MBIM_CID_CONNECT, MBIM_CMDOP_SET, c, off); +done: + free(c, M_USBDEV, sizeof (*c)); + return; +} + +void +mbim_qry_ipconfig(struct mbim_softc *sc) +{ + struct mbim_cid_ip_configuration_info ipc; + + memset(&ipc, 0, sizeof (ipc)); + ipc.sessionid = htole32(mbim_session_id); + mbim_cmd(sc, MBIM_CID_IP_CONFIGURATION, MBIM_CMDOP_QRY, + &ipc, sizeof (ipc)); +} + +void +mbim_cmd(struct mbim_softc *sc, int cid, int op, void *data, int len) +{ + 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), mbim_cid2str(cid)); + return; + } + cmd = sc->sc_ctrl_msg; + memset(cmd, 0, sizeof (*cmd)); + cmd->frag.nfrag = htole32(1); + memcpy(cmd->devid, mbim_uuid_basic_connect, 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; + } + mbim_ctrl_msg(sc, MBIM_COMMAND_MSG, cmd, totlen); +} + +void +mbim_command_done(struct mbim_softc *sc, void *data, int len) +{ + struct mbim_f2h_cmddone *cmd = data; + uint32_t status; + uint32_t cid; + uint32_t infolen; + + if (len < sizeof (*cmd)) { + DPRINTF("%s: discard short %s messsage\n", DEVNAM(sc), + mbim_request2str(letoh32(cmd->hdr.type))); + return; + } + cid = letoh32(cmd->cid); + if (memcmp(cmd->devid, mbim_uuid_basic_connect, sizeof (cmd->devid))) { + DPRINTF("%s: discard %s messsage for other UUID '%s'\n", + DEVNAM(sc), mbim_request2str(letoh32(cmd->hdr.type)), + mbim_uuid2str(cmd->devid)); + return; + } + + status = letoh32(cmd->status); + switch (status) { + case MBIM_STATUS_SUCCESS: + break; + case MBIM_STATUS_NOT_INITIALIZED: + log(LOG_ERR, "%s: SIM not initialized (PIN missing)\n", + DEVNAM(sc)); + return; + case MBIM_STATUS_PIN_REQUIRED: + sc->sc_info.pin_state = MBIM_PIN_REQUIRED; + /*FALLTHROUGH*/ + default: + log(LOG_ERR, "%s: set/qry %s failed: %s\n", DEVNAM(sc), + mbim_cid2str(cid), mbim_status2str(status)); + return; + } + + infolen = letoh32(cmd->infolen); + if (len < sizeof (*cmd) + infolen) { + DPRINTF("%s: discard truncated %s messsage (want %d, got %d)\n", + DEVNAM(sc), mbim_cid2str(cid), + (int)sizeof (*cmd) + infolen, len); + return; + } + DPRINTFN(2, "%s: set/qry %s done\n", DEVNAM(sc), mbim_cid2str(cid)); + mbim_decode_cid(sc, cid, cmd->info, infolen); +} + +void +mbim_decode_cid(struct mbim_softc *sc, uint32_t cid, void *data, int len) +{ + int ok = 1; + + switch (cid) { + case MBIM_CID_DEVICE_CAPS: + ok = mbim_decode_devices_caps(sc, data, len); + break; + case MBIM_CID_SUBSCRIBER_READY_STATUS: + ok = mbim_decode_subscriber_status(sc, data, len); + break; + case MBIM_CID_RADIO_STATE: + ok = mbim_decode_radio_state(sc, data, len); + break; + case MBIM_CID_PIN: + ok = mbim_decode_pin(sc, data, len); + break; + case MBIM_CID_REGISTER_STATE: + ok = mbim_decode_register_state(sc, data, len); + break; + case MBIM_CID_PACKET_SERVICE: + ok = mbim_decode_packet_service(sc, data, len); + break; + case MBIM_CID_SIGNAL_STATE: + ok = mbim_decode_signal_state(sc, data, len); + break; + case MBIM_CID_CONNECT: + ok = mbim_decode_connect_info(sc, data, len); + break; + case MBIM_CID_IP_CONFIGURATION: + ok = mbim_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), mbim_cid2str(cid)); + break; + } + if (!ok) + DPRINTF("%s: discard %s with bad info length %d\n", + DEVNAM(sc), mbim_cid2str(cid), len); + return; +} + +void +mbim_intr(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + struct mbim_softc *sc = priv; + 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: + 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: mbim_intr: response available\n", DEVNAM(sc)); + ++sc->sc_nresp; + usb_add_task(sc->sc_udev, &sc->sc_get_response_task); + break; + case UCDC_N_CONNECTION_SPEED_CHANGE: + DPRINTFN(2, "%s: mbim_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 + */ +char * +mbim_ntop(struct sockaddr *sa) +{ +#define NUMBUFS 4 + static char astr[NUMBUFS][INET_ADDRSTRLEN]; + static unsigned nbuf = 0; + char *s; + void *d; + + s = astr[nbuf++]; + if (nbuf >= NUMBUFS) + nbuf = 0; + + if (sa->sa_family == AF_INET) + d = &satosin(sa)->sin_addr; + else + d = &satosin6(sa)->sin6_addr; + + inet_ntop(sa->sa_family, d, s, sizeof (astr[0])); + return s; +} + +#ifdef MBIM_DEBUG +char * +mbim_uuid2str(uint8_t uuid[MBIM_UUID_LEN]) +{ + static char uuidstr[2 * MBIM_UUID_LEN + 5]; + +#define UUID_BFMT "%02X" +#define UUID_SEP "-" + snprintf(uuidstr, sizeof (uuidstr), + UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_SEP + UUID_BFMT UUID_BFMT UUID_SEP + UUID_BFMT UUID_BFMT UUID_SEP + UUID_BFMT UUID_BFMT UUID_SEP + UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT, + uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], + uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]); + return uuidstr; +} + +void +mbim_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 /* MBIM_DEBUG */ Index: sys/dev/usb/if_mbim.h =================================================================== RCS file: sys/dev/usb/if_mbim.h diff -N sys/dev/usb/if_mbim.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/dev/usb/if_mbim.h 23 May 2016 09:50:08 -0000 @@ -0,0 +1,376 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2016 genua mbH + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Mobile Broadband Interface Model + * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf + */ + +struct mbim_valdescr { + int val; + char *descr; +}; + +static const char * +mbim_val2descr(const struct mbim_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 mbim_state { + MBIM_S_DOWN = 0, /* interface down */ + MBIM_S_OPEN, /* MBIM device has been opened */ + MBIM_S_RADIO, /* radio is on */ + MBIM_S_SIMREADY, /* SIM is ready */ + MBIM_S_ATTACHED, /* packet service is attached */ + MBIM_S_CONNECTED, /* connected to provider */ + MBIM_S_UP, /* have IP configuration */ +}; + +#define MBIM_INTERNAL_STATE_DESCRIPTIONS { \ + { MBIM_S_DOWN, "down" }, \ + { MBIM_S_OPEN, "open" }, \ + { MBIM_S_RADIO, "radio on" }, \ + { MBIM_S_SIMREADY, "SIM is ready" }, \ + { MBIM_S_ATTACHED, "attached" }, \ + { MBIM_S_CONNECTED, "connected" }, \ + { MBIM_S_UP, "up" }, \ + { 0, NULL } } + +/* + * MBIM parameters (SIOC[GS]MBIMPARAM ioctls) + */ +struct mbim_parameter { + int op; + int is_puk; + char pin[MBIM_PIN_MAXLEN]; + int pinlen; + + char newpin[MBIM_PIN_MAXLEN]; + int newpinlen; + +#define MBIM_APN_MAXLEN 100 + uint16_t apn[MBIM_APN_MAXLEN]; + int apnlen; + + int roaming; + uint32_t preferredclasses; +}; + +/* + * MBIM device status info (SIOCGMBIMINFO ioctl) + */ +struct mbim_info { + enum mbim_state state; + int enable_roaming; +#define MBIM_PIN_REQUIRED 0 +#define MBIM_PIN_UNLOCKED 1 +#define MBIM_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 MBIM_PROVIDERNAME_MAXLEN 20 + uint16_t provider[MBIM_PROVIDERNAME_MAXLEN]; +#define MBIM_PHONENR_MAXLEN 22 + uint16_t pn[MBIM_PHONENR_MAXLEN]; +#define MBIM_SUBSCRIBERID_MAXLEN 15 + uint16_t sid[MBIM_SUBSCRIBERID_MAXLEN]; +#define MBIM_ICCID_MAXLEN 20 + uint16_t iccid[MBIM_ICCID_MAXLEN]; +#define MBIM_ROAMINGTEXT_MAXLEN 63 + uint16_t roamingtxt[MBIM_ROAMINGTEXT_MAXLEN]; + +#define MBIM_DEVID_MAXLEN 18 + uint16_t devid[MBIM_DEVID_MAXLEN]; +#define MBIM_FWINFO_MAXLEN 30 + uint16_t fwinfo[MBIM_FWINFO_MAXLEN]; +#define MBIM_HWINFO_MAXLEN 30 + uint16_t hwinfo[MBIM_HWINFO_MAXLEN]; + + uint16_t apn[MBIM_APN_MAXLEN]; + int apnlen; + +#define MBIM_VALUE_UNKNOWN -999 + int rssi; +#define MBIM_BER_EXCELLENT 0 +#define MBIM_BER_VERYGOOD 1 +#define MBIM_BER_GOOD 2 +#define MBIM_BER_OK 3 +#define MBIM_BER_MEDIUM 4 +#define MBIM_BER_BAD 5 +#define MBIM_BER_VERYBAD 6 +#define MBIM_BER_EXTREMELYBAD 7 + int ber; + + int hw_radio_on; + int sw_radio_on; + + uint64_t uplink_speed; + uint64_t downlink_speed; + +#define MBIM_MAX_DNSSRV 2 + u_int32_t ipv4dns[MBIM_MAX_DNSSRV]; +}; + +#ifdef _KERNEL +/* + * MBIM device + */ +struct mbim_softc { + struct device sc_dev; + struct ifnet sc_if; +#define GET_IFP(sc) (&(sc)->sc_if) + struct usbd_device *sc_udev; + + int sc_ver_maj; + int sc_ver_min; + int sc_ctrl_len; + int sc_maxpktlen; + int sc_maxsessions; + + struct usb_task sc_mbim_task; + struct usb_task sc_get_response_task; + int sc_nresp; + struct timeout sc_statechg_timer; + + uint8_t sc_ctrl_ifaceno; + struct usbd_pipe *sc_ctrl_pipe; + struct usb_cdc_notification 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; + void *sc_rx_buf; + struct usbd_pipe *sc_rx_pipe; + unsigned sc_rx_nerr; + struct timeval sc_rx_ratechk; + + int sc_tx_ep; + struct usbd_xfer *sc_tx_xfer; + void *sc_tx_buf; + struct usbd_pipe *sc_tx_pipe; + struct mbuf *sc_tx_m; + uint32_t sc_tx_seq; + + uint32_t sc_tid; + +#define MBIM_IFADDR_NONE ((void *)-1) + void *sc_saved_ifaddr; + +#define sc_state sc_info.state +#define sc_roaming sc_info.enable_roaming + struct mbim_info sc_info; +}; +#endif /* _KERNEL */ Index: sys/dev/usb/mbim.h =================================================================== RCS file: sys/dev/usb/mbim.h diff -N sys/dev/usb/mbim.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/dev/usb/mbim.h 23 May 2016 09:50:08 -0000 @@ -0,0 +1,669 @@ +/* $OpenBSD$ */ + +/* + * 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_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 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_ */