uqmi: add support for MBIM devices with QMI service
authorBjørn Mork <bjorn@mork.no>
Sun, 21 Aug 2016 00:01:09 +0000 (02:01 +0200)
committerFelix Fietkau <nbd@nbd.name>
Tue, 23 Aug 2016 11:38:27 +0000 (13:38 +0200)
Many Qualcomm based devices offer a QMI service when running in MBIM
mode. This is useful for a number of requests which have no MBIM
counterpart.

This implementation is very basic, simply wrapping the QMI requests
in an MBIM command. It is up to the user to make sure that the MBIM
command sequence is valid, using a mix of umbim and uqmi requests.

umbim must be used to send "OPEN" before uqmi can issue any MBIM
requests. Example:

1. use umbim to open the session, using the '-n' option:

$ umbim -d /dev/cdc-wdm0 -n caps
  devicetype: 0003 - remote
  cellularclass: 0001
  voiceclass: 0001 - no-voice
  simclass: 0002
  dataclass: 003C
  smscaps: 0003
  controlcaps: 0001
  maxsessions: 0008
  deviceid: 0145820007xxxxx
  firmwareinfo: SWI9X30C_02.08.02.00
  hardwareinfo: EM7455

2. use uqmi to send an MBIM request, using the '-m' option:

$ uqmi -m -d /dev/cdc-wdm0 --get-serving-system
{
        "registration": "registered",
        "plmn_mcc": 242,
        "plmn_mnc": 1,
        "plmn_description": "TELENOR",
        "roaming": false
}

3. use umbim to close the open session, using the '-t X' option:

$ umbim -d /dev/cdc-wdm0 -t 2 caps
  devicetype: 0003 - remote
  cellularclass: 0001
  voiceclass: 0001 - no-voice
  simclass: 0002
  dataclass: 003C
  smscaps: 0003
  controlcaps: 0001
  maxsessions: 0008
  deviceid: 0145820007xxxxx
  firmwareinfo: SWI9X30C_02.08.02.00
  hardwareinfo: EM7455

Signed-off-by: Bjørn Mork <bjorn@mork.no>
Signed-off-by: Felix Fietkau <nbd@nbd.name> [cleanup, portability fixes]
CMakeLists.txt
dev.c
main.c
mbim.c [new file with mode: 0644]
mbim.h [new file with mode: 0644]
uqmi.h

index 3bb02305fcdaba9de8d945d209189b49029af747..d91411a5f004ed44051953d7f192dfef90274a51 100644 (file)
@@ -8,7 +8,7 @@ ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations -Wno-
 
 SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
 
-SET(SOURCES main.c dev.c commands.c qmi-message.c)
+SET(SOURCES main.c dev.c commands.c qmi-message.c mbim.c)
 
 FIND_PATH(ubox_include_dir libubox/usock.h)
 FIND_PATH(blobmsg_json_include_dir libubox/blobmsg_json.h)
diff --git a/dev.c b/dev.c
index c25900bb9f4abb377d05e2912fa109df83a1bf4a..06b0b33ef9492804f6d601ab61d65e676e4cdd4b 100644 (file)
--- a/dev.c
+++ b/dev.c
@@ -27,6 +27,7 @@
 #include "uqmi.h"
 #include "qmi-errors.h"
 #include "qmi-errors.c"
+#include "mbim.h"
 
 bool cancel_all_requests = false;
 
@@ -36,10 +37,13 @@ static const uint8_t qmi_services[__QMI_SERVICE_LAST] = {
 };
 #undef __qmi_service
 
-static union {
-       char buf[512];
-       struct qmi_msg msg;
-} msgbuf;
+static struct {
+       struct mbim_command_message mbim;
+       union {
+               char buf[512];
+               struct qmi_msg msg;
+       } u;
+} __packed msgbuf;
 
 #ifdef DEBUG_PACKET
 void dump_packet(const char *prefix, void *ptr, int len)
@@ -124,20 +128,35 @@ static void qmi_notify_read(struct ustream *us, int bytes)
        char *buf;
        int len, msg_len;
 
+
        while (1) {
                buf = ustream_get_read_buf(us, &len);
                if (!buf || !len)
                        return;
 
-               if (len < offsetof(struct qmi_msg, flags))
-                       return;
+               dump_packet("Received packet", buf, len);
+               if (qmi->is_mbim) {
+                       struct mbim_command_message *mbim = (void *) buf;
+
+                       if (len < sizeof(*mbim))
+                               return;
+                       msg = (struct qmi_msg *) (buf + sizeof(*mbim));
+                       msg_len = le32_to_cpu(mbim->header.length);
+                       if (!is_mbim_qmi(mbim)) {
+                               /* must consume other MBIM packets */
+                               ustream_consume(us, msg_len);
+                               return;
+                       }
+               } else {
+                       if (len < offsetof(struct qmi_msg, flags))
+                               return;
+                       msg = (struct qmi_msg *) buf;
+                       msg_len = le16_to_cpu(msg->qmux.len) + 1;
+               }
 
-               msg = (struct qmi_msg *) buf;
-               msg_len = le16_to_cpu(msg->qmux.len) + 1;
                if (len < msg_len)
                        return;
 
-               dump_packet("Received packet", msg, msg_len);
                qmi_process_msg(qmi, msg);
                ustream_consume(us, msg_len);
        }
@@ -147,6 +166,7 @@ int qmi_request_start(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_m
 {
        int len = qmi_complete_request_message(msg);
        uint16_t tid;
+       char *buf = (void *) msg;
 
        memset(req, 0, sizeof(*req));
        req->ret = -1;
@@ -170,8 +190,14 @@ int qmi_request_start(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_m
        req->pending = true;
        list_add(&req->list, &qmi->req);
 
-       dump_packet("Send packet", msg, len);
-       ustream_write(&qmi->sf.stream, (void *) msg, len, false);
+       if (qmi->is_mbim) {
+               buf -= sizeof(struct mbim_command_message);
+               mbim_qmi_cmd((struct mbim_command_message *) buf, len, tid);
+               len += sizeof(struct mbim_command_message);
+       }
+
+       dump_packet("Send packet", buf, len);
+       ustream_write(&qmi->sf.stream, buf, len, false);
        return 0;
 }
 
@@ -234,7 +260,7 @@ int qmi_service_connect(struct qmi_dev *qmi, QmiService svc, int client_id)
        };
        struct qmi_connect_request req;
        int idx = qmi_get_service_idx(svc);
-       struct qmi_msg *msg = &msgbuf.msg;
+       struct qmi_msg *msg = &msgbuf.u.msg;
 
        if (idx < 0)
                return -1;
@@ -273,7 +299,7 @@ static void __qmi_service_disconnect(struct qmi_dev *qmi, int idx)
                )
        };
        struct qmi_request req;
-       struct qmi_msg *msg = &msgbuf.msg;
+       struct qmi_msg *msg = &msgbuf.u.msg;
 
        qmi->service_connected &= ~(1 << idx);
        qmi->service_data[idx].client_id = -1;
diff --git a/main.c b/main.c
index 028c3a9bb11e2aa9c35e445ebd8f9c43e42661bc..e4617e295c92da4cad9e773d8dfffc9f22093267 100644 (file)
--- a/main.c
+++ b/main.c
@@ -43,6 +43,7 @@ static const struct option uqmi_getopt[] = {
        { "device", required_argument, NULL, 'd' },
        { "keep-client-id", required_argument, NULL, 'k' },
        { "release-client-id", required_argument, NULL, 'r' },
+       { "mbim",  no_argument, NULL, 'm' },
        { NULL, 0, NULL, 0 }
 };
 #undef __uqmi_command
@@ -55,6 +56,7 @@ static int usage(const char *progname)
                "  --device=NAME, -d NAME:           Set device name to NAME (required)\n"
                "  --keep-client-id <name>:          Keep Client ID for service <name>\n"
                "  --release-client-id <name>:       Release Client ID after exiting\n"
+               "  --mbim, -m                        NAME is an MBIM device with EXT_QMUX support\n"
                "\n"
                "Services:                           dms, nas, pds, wds, wms\n"
                "\n"
@@ -108,7 +110,7 @@ int main(int argc, char **argv)
        signal(SIGINT, handle_exit_signal);
        signal(SIGTERM, handle_exit_signal);
 
-       while ((ch = getopt_long(argc, argv, "d:k:s", uqmi_getopt, NULL)) != -1) {
+       while ((ch = getopt_long(argc, argv, "d:k:sm", uqmi_getopt, NULL)) != -1) {
                int cmd_opt = CMD_OPT(ch);
 
                if (ch < 0 && cmd_opt >= 0 && cmd_opt < __UQMI_COMMAND_LAST) {
@@ -129,6 +131,9 @@ int main(int argc, char **argv)
                case 's':
                        single_line = true;
                        break;
+               case 'm':
+                       dev.is_mbim = true;
+                       break;
                default:
                        return usage(argv[0]);
                }
diff --git a/mbim.c b/mbim.c
new file mode 100644 (file)
index 0000000..33ce19d
--- /dev/null
+++ b/mbim.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016  Bjørn Mork <bjorn@mork.no>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <stdio.h>
+
+#include "mbim.h"
+
+static const uint8_t qmiuuid[16] = { 0xd1, 0xa3, 0x0b, 0xc2, 0xf9, 0x7a, 0x6e, 0x43,
+                                    0xbf, 0x65, 0xc7, 0xe2, 0x4f, 0xb0, 0xf0, 0xd3 };
+
+bool is_mbim_qmi(struct mbim_command_message *msg)
+{
+       return msg->header.type == cpu_to_le32(MBIM_MESSAGE_TYPE_COMMAND_DONE) &&
+               msg->command_id == cpu_to_le32(MBIM_CID_QMI_MSG) &&
+               !msg->command_type &&   /* actually 'status' here */
+               !memcmp(msg->service_id, qmiuuid, 16);
+                                                                           }
+
+void mbim_qmi_cmd(struct mbim_command_message *msg, int len, uint16_t tid)
+{
+       msg->header.type = cpu_to_le32(MBIM_MESSAGE_TYPE_COMMAND);
+       msg->header.length = sizeof(*msg) + len;
+       msg->header.transaction_id = cpu_to_le32(tid);
+       msg->fragment_header.total = 1;
+       msg->fragment_header.current = 0;
+       memcpy(msg->service_id, qmiuuid, 16);
+       msg->command_id = cpu_to_le32(MBIM_CID_QMI_MSG);
+       msg->command_type = cpu_to_le32(MBIM_MESSAGE_COMMAND_TYPE_SET);
+       msg->buffer_length = len;
+}
diff --git a/mbim.h b/mbim.h
new file mode 100644 (file)
index 0000000..13783bf
--- /dev/null
+++ b/mbim.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2016  Bjørn Mork <bjorn@mork.no>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _MBIM_H__
+#define _MBIM_H__
+
+#include <libubox/utils.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#define MBIM_MESSAGE_TYPE_COMMAND      0x00000003
+#define MBIM_MESSAGE_TYPE_COMMAND_DONE 0x80000003
+#define MBIM_MESSAGE_COMMAND_TYPE_SET  1
+#define MBIM_CID_QMI_MSG               1
+
+struct mbim_message_header {
+       uint32_t type;
+       uint32_t length;
+       uint32_t transaction_id;
+} __packed;
+
+struct mbim_fragment_header {
+       uint32_t total;
+       uint32_t current;
+} __packed;
+
+struct mbim_command_message {
+       struct mbim_message_header header;
+       struct mbim_fragment_header fragment_header;
+       uint8_t service_id[16];
+       uint32_t command_id;
+       uint32_t command_type;
+       uint32_t buffer_length;
+} __packed;
+
+bool is_mbim_qmi(struct mbim_command_message *msg);
+void mbim_qmi_cmd(struct mbim_command_message *msg, int len, uint16_t tid);
+
+#endif
diff --git a/uqmi.h b/uqmi.h
index 5cd3bff06e9ae18eccbf89f4d356e75c351f58ae..299997754100725144b66a36671565b09ad60c42 100644 (file)
--- a/uqmi.h
+++ b/uqmi.h
@@ -87,6 +87,8 @@ struct qmi_dev {
        uint32_t service_release_cid;
 
        uint8_t ctl_tid;
+
+       bool is_mbim;
 };
 
 struct qmi_request {