Some MBIM devices need a FCC Authentication before they're willing to
turn on the radio. This has to be done by sending a QMI command inside
an MBIM message.

This patch is based on an earlier patch by Stuart Henderson. One
crucial thing was missing in sthen@'s patch: first a client-id (CID)
has to be allocated and this CID must then be patched into the
right field of the FCC-Auth.

Sending the FCC-Auth is limited to a list of devices known to require
this. Currently, this is only the Sierra Wireless EM7455.

This patch was possible thanks to a lot of testing by Bryan Vyhmeister.


Gerhard


Index: sys/dev/usb/if_umb.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/if_umb.c,v
retrieving revision 1.6
diff -u -p -u -p -r1.6 if_umb.c
--- sys/dev/usb/if_umb.c        14 Nov 2016 12:55:56 -0000      1.6
+++ sys/dev/usb/if_umb.c        16 Nov 2016 08:42:44 -0000
@@ -170,6 +170,8 @@ int          umb_setpin(struct umb_softc *, int
                    int);
 void            umb_setdataclass(struct umb_softc *);
 void            umb_radio(struct umb_softc *, int);
+void            umb_allocate_cid(struct umb_softc *);
+void            umb_send_fcc_auth(struct umb_softc *);
 void            umb_packet_service(struct umb_softc *, int);
 void            umb_connect(struct umb_softc *);
 void            umb_disconnect(struct umb_softc *);
@@ -177,8 +179,10 @@ void                umb_send_connect(struct umb_softc
 
 void            umb_qry_ipconfig(struct umb_softc *);
 void            umb_cmd(struct umb_softc *, int, int, void *, int);
+void            umb_cmd1(struct umb_softc *, int, int, void *, int, uint8_t *);
 void            umb_command_done(struct umb_softc *, void *, int);
 void            umb_decode_cid(struct umb_softc *, uint32_t, void *, int);
+void            umb_decode_qmi(struct umb_softc *, uint8_t *, int);
 
 void            umb_intr(struct usbd_xfer *, void *, usbd_status);
 
@@ -188,6 +192,7 @@ int          umb_xfer_tout = USBD_DEFAULT_TIMEO
 
 uint8_t                 umb_uuid_basic_connect[] = MBIM_UUID_BASIC_CONNECT;
 uint8_t                 umb_uuid_context_internet[] = 
MBIM_UUID_CONTEXT_INTERNET;
+uint8_t                 umb_uuid_qmi_mbim[] = MBIM_UUID_QMI_MBIM;
 uint32_t        umb_session_id = 0;
 
 struct cfdriver umb_cd = {
@@ -204,6 +209,39 @@ const struct cfattach umb_ca = {
 
 int umb_delay = 4000;
 
+/*
+ * These devices require an "FCC Authentication" command.
+ */
+const struct usb_devno umb_fccauth_devs[] = {
+       { USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_EM7455 },
+};
+
+uint8_t umb_qmi_alloc_cid[] = {
+       0x01,
+       0x0f, 0x00,             /* len */
+       0x00,                   /* QMUX flags */
+       0x00,                   /* service "ctl" */
+       0x00,                   /* CID */
+       0x00,                   /* QMI flags */
+       0x01,                   /* transaction */
+       0x22, 0x00,             /* msg "Allocate CID" */
+       0x04, 0x00,             /* TLV len */
+       0x01, 0x01, 0x00, 0x02  /* TLV */
+};
+
+uint8_t umb_qmi_fcc_auth[] = {
+       0x01,
+       0x0c, 0x00,             /* len */
+       0x00,                   /* QMUX flags */
+       0x02,                   /* service "dms" */
+#define UMB_QMI_CID_OFFS       5
+       0x00,                   /* CID (filled in later) */
+       0x00,                   /* QMI flags */
+       0x01, 0x00,             /* transaction */
+       0x5f, 0x55,             /* msg "Send FCC Authentication" */
+       0x00, 0x00              /* TLV len */
+};
+
 int
 umb_match(struct device *parent, void *match, void *aux)
 {
@@ -328,6 +366,10 @@ umb_attach(struct device *parent, struct
                printf("%s: missing MBIM descriptor\n", DEVNAM(sc));
                goto fail;
        }
+       if (usb_lookup(umb_fccauth_devs, uaa->vendor, uaa->product)) {
+               sc->sc_flags |= UMBFLG_FCC_AUTH_REQUIRED;
+               sc->sc_cid = -1;
+       }
 
        for (i = 0; i < uaa->nifaces; i++) {
                if (usbd_iface_claimed(sc->sc_udev, i))
@@ -783,7 +825,14 @@ umb_statechg_timeout(void *arg)
 {
        struct umb_softc *sc = arg;
 
-       printf("%s: state change timeout\n",DEVNAM(sc));
+       if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING && !sc->sc_roaming) {
+               /*
+                * Query the registration state until we're with the home
+                * network again.
+                */
+               umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_QRY, NULL, 0);
+       } else
+               printf("%s: state change timeout\n",DEVNAM(sc));
        usb_add_task(sc->sc_udev, &sc->sc_umb_task);
 }
 
@@ -863,8 +912,23 @@ umb_up(struct umb_softc *sc)
                umb_open(sc);
                break;
        case UMB_S_OPEN:
-               DPRINTF("%s: init: turning radio on ...\n", DEVNAM(sc));
-               umb_radio(sc, 1);
+               if (sc->sc_flags & UMBFLG_FCC_AUTH_REQUIRED) {
+                       if (sc->sc_cid == -1) {
+                               DPRINTF("%s: init: allocating CID ...\n",
+                                   DEVNAM(sc));
+                               umb_allocate_cid(sc);
+                               break;
+                       } else
+                               umb_newstate(sc, UMB_S_CID, UMB_NS_DONT_DROP);
+               } else {
+                       DPRINTF("%s: init: turning radio on ...\n", DEVNAM(sc));
+                       umb_radio(sc, 1);
+                       break;
+               }
+               /*FALLTHROUGH*/
+       case UMB_S_CID:
+               DPRINTF("%s: init: sending FCC auth ...\n", DEVNAM(sc));
+               umb_send_fcc_auth(sc);
                break;
        case UMB_S_RADIO:
                DPRINTF("%s: init: checking SIM state ...\n", DEVNAM(sc));
@@ -936,6 +1000,7 @@ umb_down(struct umb_softc *sc, int force
                if (!force)
                        break;
                /*FALLTHROUGH*/
+       case UMB_S_CID:
        case UMB_S_OPEN:
        case UMB_S_DOWN:
                /* Do not close the device */
@@ -1202,7 +1267,7 @@ umb_decode_register_state(struct umb_sof
                        log(LOG_INFO,
                            "%s: disconnecting from roaming network\n",
                            DEVNAM(sc));
-               umb_newstate(sc, UMB_S_ATTACHED, UMB_NS_DONT_RAISE);
+               umb_disconnect(sc);
        }
        return 1;
 }
@@ -2040,6 +2105,29 @@ umb_radio(struct umb_softc *sc, int on)
 }
 
 void
+umb_allocate_cid(struct umb_softc *sc)
+{
+       umb_cmd1(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_SET,
+           umb_qmi_alloc_cid, sizeof (umb_qmi_alloc_cid), umb_uuid_qmi_mbim);
+}
+
+void
+umb_send_fcc_auth(struct umb_softc *sc)
+{
+       uint8_t  fccauth[sizeof (umb_qmi_fcc_auth)];
+
+       if (sc->sc_cid == -1) {
+               DPRINTF("%s: missing CID, cannot send FCC auth\n", DEVNAM(sc));
+               umb_allocate_cid(sc);
+               return;
+       }
+       memcpy(fccauth, umb_qmi_fcc_auth, sizeof (fccauth));
+       fccauth[UMB_QMI_CID_OFFS] = sc->sc_cid;
+       umb_cmd1(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_SET,
+           fccauth, sizeof (fccauth), umb_uuid_qmi_mbim);
+}
+
+void
 umb_packet_service(struct umb_softc *sc, int attach)
 {
        struct mbim_cid_packet_service  s;
@@ -2120,6 +2208,13 @@ umb_qry_ipconfig(struct umb_softc *sc)
 void
 umb_cmd(struct umb_softc *sc, int cid, int op, void *data, int len)
 {
+       umb_cmd1(sc, cid, op, data, len, umb_uuid_basic_connect);
+}
+
+void
+umb_cmd1(struct umb_softc *sc, int cid, int op, void *data, int len,
+    uint8_t *uuid)
+{
        struct mbim_h2f_cmd *cmd;
        int     totlen;
 
@@ -2132,7 +2227,7 @@ umb_cmd(struct umb_softc *sc, int cid, i
        cmd = sc->sc_ctrl_msg;
        memset(cmd, 0, sizeof (*cmd));
        cmd->frag.nfrag = htole32(1);
-       memcpy(cmd->devid, umb_uuid_basic_connect, sizeof (cmd->devid));
+       memcpy(cmd->devid, uuid, sizeof (cmd->devid));
        cmd->cid = htole32(cid);
        cmd->op = htole32(op);
        cmd->infolen = htole32(len);
@@ -2152,6 +2247,7 @@ umb_command_done(struct umb_softc *sc, v
        uint32_t status;
        uint32_t cid;
        uint32_t infolen;
+       int      qmimsg = 0;
 
        if (len < sizeof (*cmd)) {
                DPRINTF("%s: discard short %s messsage\n", DEVNAM(sc),
@@ -2160,10 +2256,14 @@ umb_command_done(struct umb_softc *sc, v
        }
        cid = letoh32(cmd->cid);
        if (memcmp(cmd->devid, umb_uuid_basic_connect, sizeof (cmd->devid))) {
-               DPRINTF("%s: discard %s messsage for other UUID '%s'\n",
-                   DEVNAM(sc), umb_request2str(letoh32(cmd->hdr.type)),
-                   umb_uuid2str(cmd->devid));
-               return;
+               if (memcmp(cmd->devid, umb_uuid_qmi_mbim,
+                   sizeof (cmd->devid))) {
+                       DPRINTF("%s: discard %s messsage for other UUID '%s'\n",
+                           DEVNAM(sc), umb_request2str(letoh32(cmd->hdr.type)),
+                           umb_uuid2str(cmd->devid));
+                       return;
+               } else
+                       qmimsg = 1;
        }
 
        status = letoh32(cmd->status);
@@ -2192,8 +2292,14 @@ umb_command_done(struct umb_softc *sc, v
                    (int)sizeof (*cmd) + infolen, len);
                return;
        }
-       DPRINTFN(2, "%s: set/qry %s done\n", DEVNAM(sc), umb_cid2str(cid));
-       umb_decode_cid(sc, cid, cmd->info, infolen);
+       if (qmimsg) {
+               if (sc->sc_flags & UMBFLG_FCC_AUTH_REQUIRED)
+                       umb_decode_qmi(sc, cmd->info, infolen);
+       } else {
+               DPRINTFN(2, "%s: set/qry %s done\n", DEVNAM(sc),
+                   umb_cid2str(cid));
+               umb_decode_cid(sc, cid, cmd->info, infolen);
+       }
 }
 
 void
@@ -2241,6 +2347,122 @@ umb_decode_cid(struct umb_softc *sc, uin
        if (!ok)
                DPRINTF("%s: discard %s with bad info length %d\n",
                    DEVNAM(sc), umb_cid2str(cid), len);
+       return;
+}
+
+void
+umb_decode_qmi(struct umb_softc *sc, uint8_t *data, int len)
+{
+       uint8_t srv;
+       uint16_t msg, tlvlen;
+       uint32_t val;
+
+#define UMB_QMI_QMUXLEN                6
+       if (len < UMB_QMI_QMUXLEN)
+               goto tooshort;
+
+       srv = data[4];
+       data += UMB_QMI_QMUXLEN;
+       len -= UMB_QMI_QMUXLEN;
+
+#define UMB_GET16(p)   ((uint16_t)*p | (uint16_t)*(p + 1) << 8)
+#define UMB_GET32(p)   ((uint32_t)*p | (uint32_t)*(p + 1) << 8 | \
+                           (uint32_t)*(p + 2) << 16 |(uint32_t)*(p + 3) << 24)
+       switch (srv) {
+       case 0: /* ctl */
+#define UMB_QMI_CTLLEN         6
+               if (len < UMB_QMI_CTLLEN)
+                       goto tooshort;
+               msg = UMB_GET16(&data[2]);
+               tlvlen = UMB_GET16(&data[4]);
+               data += UMB_QMI_CTLLEN;
+               len -= UMB_QMI_CTLLEN;
+               break;
+       case 2: /* dms  */
+#define UMB_QMI_DMSLEN         7
+               if (len < UMB_QMI_DMSLEN)
+                       goto tooshort;
+               msg = UMB_GET16(&data[3]);
+               tlvlen = UMB_GET16(&data[5]);
+               data += UMB_QMI_DMSLEN;
+               len -= UMB_QMI_DMSLEN;
+               break;
+       default:
+               DPRINTF("%s: discard QMI message for unknown service type %d\n",
+                   DEVNAM(sc), srv);
+               return;
+       }
+
+       if (len < tlvlen)
+               goto tooshort;
+
+#define UMB_QMI_TLVLEN         3
+       while (len > 0) {
+               if (len < UMB_QMI_TLVLEN)
+                       goto tooshort;
+               tlvlen = UMB_GET16(&data[1]);
+               if (len < UMB_QMI_TLVLEN + tlvlen)
+                       goto tooshort;
+               switch (data[0]) {
+               case 1: /* allocation info */
+                       if (msg == 0x0022) {    /* Allocate CID */
+                               if (tlvlen != 2 || data[3] != 2) /* dms */
+                                       break;
+                               sc->sc_cid = data[4];
+                               DPRINTF("%s: QMI CID %d allocated\n",
+                                   DEVNAM(sc), sc->sc_cid);
+                               umb_newstate(sc, UMB_S_CID, UMB_NS_DONT_DROP);
+                       }
+                       break;
+               case 2: /* response */
+                       if (tlvlen != sizeof (val))
+                               break;
+                       val = UMB_GET32(&data[3]);
+                       switch (msg) {
+                       case 0x0022:    /* Allocate CID */
+                               if (val != 0) {
+                                       log(LOG_ERR, "%s: allocation of QMI CID"
+                                           " failed, error 0x%x\n", DEVNAM(sc),
+                                           val);
+                                       /* XXX how to proceed? */
+                                       return;
+                               }
+                               break;
+                       case 0x555f:    /* Send FCC Authentication */
+                               if (val == 0)
+                                       log(LOG_INFO, "%s: send FCC "
+                                           "Authentication succeeded\n",
+                                           DEVNAM(sc));
+                               else if (val == 0x001a0001)
+                                       log(LOG_INFO, "%s: FCC Authentication "
+                                           "not required\n", DEVNAM(sc));
+                               else
+                                       log(LOG_INFO, "%s: send FCC "
+                                           "Authentication failed, "
+                                           "error 0x%x\n", DEVNAM(sc), val);
+
+                               /* FCC Auth is needed only once after power-on*/
+                               sc->sc_flags &= ~UMBFLG_FCC_AUTH_REQUIRED;
+
+                               /* Try to proceed anyway */
+                               DPRINTF("%s: init: turning radio on ...\n",
+                                   DEVNAM(sc));
+                               umb_radio(sc, 1);
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+               data += UMB_QMI_TLVLEN + tlvlen;
+               len -= UMB_QMI_TLVLEN + tlvlen;
+       }
+       return;
+
+tooshort:
+       DPRINTF("%s: discard short QMI message\n", DEVNAM(sc));
        return;
 }
 
Index: sys/dev/usb/if_umb.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/if_umb.h,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 if_umb.h
--- sys/dev/usb/if_umb.h        15 Jun 2016 19:39:34 -0000      1.1
+++ sys/dev/usb/if_umb.h        19 Oct 2016 09:03:49 -0000
@@ -220,6 +220,7 @@ umb_val2descr(const struct umb_valdescr 
 enum umb_state {
        UMB_S_DOWN = 0,         /* interface down */
        UMB_S_OPEN,             /* MBIM device has been opened */
+       UMB_S_CID,              /* QMI client id allocated */
        UMB_S_RADIO,            /* radio is on */
        UMB_S_SIMREADY,         /* SIM is ready */
        UMB_S_ATTACHED,         /* packet service is attached */
@@ -228,11 +229,12 @@ enum umb_state {
 };
 
 #define UMB_INTERNAL_STATE_DESCRIPTIONS {      \
-       { UMB_S_DOWN, "down" },         \
-       { UMB_S_OPEN, "open" },         \
+       { UMB_S_DOWN, "down" },                 \
+       { UMB_S_OPEN, "open" },                 \
+       { UMB_S_CID, "CID allocated" },         \
        { UMB_S_RADIO, "radio on" },            \
        { UMB_S_SIMREADY, "SIM is ready" },     \
-       { UMB_S_ATTACHED, "attached" }, \
+       { UMB_S_ATTACHED, "attached" },         \
        { UMB_S_CONNECTED, "connected" },       \
        { UMB_S_UP, "up" },                     \
        { 0, NULL } }
@@ -336,6 +338,10 @@ struct umb_softc {
        int                      sc_ctrl_len;
        int                      sc_maxpktlen;
        int                      sc_maxsessions;
+
+#define UMBFLG_FCC_AUTH_REQUIRED       0x0001
+       uint32_t                 sc_flags;
+       int                      sc_cid;
 
        struct usb_task          sc_umb_task;
        struct usb_task          sc_get_response_task;
Index: sys/dev/usb/mbim.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/mbim.h,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 mbim.h
--- sys/dev/usb/mbim.h  15 Jun 2016 19:39:34 -0000      1.1
+++ sys/dev/usb/mbim.h  19 Oct 2016 10:40:28 -0000
@@ -85,6 +85,11 @@
                0x83, 0xac, 0xca, 0x41, 0x31, 0x8d, 0xf7, 0xa0  \
        }
 
+#define MBIM_UUID_QMI_MBIM {                           \
+               0xd1, 0xa3, 0x0b, 0xc2, 0xf9, 0x7a, 0x6e, 0x43, \
+               0xbf, 0x65, 0xc7, 0xe2, 0x4f, 0xb0, 0xf0, 0xd3  \
+       }
+
 #define MBIM_CTRLMSG_MINLEN            64
 #define MBIM_CTRLMSG_MAXLEN            (4 * 1204)
 

 

Reply via email to