Browse Source

uqmi: add support for MBIM devices with QMI service

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]
Bjørn Mork 7 years ago
parent
commit
e69bf24b00
6 changed files with 154 additions and 15 deletions
  1. 1 1
      CMakeLists.txt
  2. 39 13
      dev.c
  3. 6 1
      main.c
  4. 50 0
      mbim.c
  5. 56 0
      mbim.h
  6. 2 0
      uqmi.h

+ 1 - 1
CMakeLists.txt

@@ -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)

+ 39 - 13
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;

+ 6 - 1
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]);
 		}

+ 50 - 0
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;
+}

+ 56 - 0
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

+ 2 - 0
uqmi.h

@@ -87,6 +87,8 @@ struct qmi_dev {
 	uint32_t service_release_cid;
 
 	uint8_t ctl_tid;
+
+	bool is_mbim;
 };
 
 struct qmi_request {