Netdev Archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Adding a new driver for Huawei E392 WWAN modem
@ 2011-12-13  4:33 Bjorn Mork
       [not found] ` <1323750784-32608-1-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
  2011-12-13  9:02 ` [PATCH 0/3] Adding a new driver for Huawei E392 WWAN modem Bjørn Mork
  0 siblings, 2 replies; 16+ messages in thread
From: Bjorn Mork @ 2011-12-13  4:33 UTC (permalink / raw
  To: netdev, linux-usb; +Cc: Bjørn Mork

From: Bjørn Mork <bjorn@mork.no>

This can probably support many more devices based on modern Qualcomm
chipsets, but has only been tested with a Huawei E392 LTE modem based
on Qualcomm MDM9200.

These chips require using the QMI protocol to enable anything but
serial mode.  They can, and should, still be configured using 
AT commands on one of the serial interfaces.  In particular, this
driver does not support entering SIM PIN code or APN, which may be
required before opening a connection. PIN can be set by using the
standard AT+CPIN="xxxx" command.  APN can most likely be set by
storing it in the default profile.

patch 1 releases one of the interfaces used by this protocol from
the option driver. Note that this interface does not support serial
data anyway, so the option driver binding is completely bogus.

patch 2 relaxes the cdc_ether CDC descriptor parsing in such a way
that it will allow a vendor specific data interface if and only if
the control interface also is vendor specific.  This allows us to
reuse the bind() function from cdc_ether for one of the possible
device configurations we support

patch 3 adds the new driver.  Most of it is QMI protocol handling.
The network device is a minimalistic usbnet minidriver.



Bjørn Mork (3):
  option: Removing one bogus and adding some new Huawei combinations
  cdc_ether: allow vendor specific data interface if control interface
    is vendor specific
  qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol

 drivers/net/usb/Kconfig         |   13 +
 drivers/net/usb/Makefile        |    2 +
 drivers/net/usb/cdc_ether.c     |    8 +-
 drivers/net/usb/qmi.c           |  730 +++++++++++++++++++++++++++++++++++++++
 drivers/net/usb/qmi.h           |  101 ++++++
 drivers/net/usb/qmi_wwan_core.c |  206 +++++++++++
 drivers/usb/serial/option.c     |    4 +-
 7 files changed, 1061 insertions(+), 3 deletions(-)
 create mode 100644 drivers/net/usb/qmi.c
 create mode 100644 drivers/net/usb/qmi.h
 create mode 100644 drivers/net/usb/qmi_wwan_core.c

-- 
1.7.7.3

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 1/3] option: Removing one bogus and adding some new Huawei combinations
       [not found] ` <1323750784-32608-1-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
@ 2011-12-13  4:33   ` Bjorn Mork
  2011-12-13 17:16     ` Greg KH
  2011-12-13  4:33   ` [PATCH 2/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific Bjorn Mork
  2011-12-13  4:33   ` [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol Bjorn Mork
  2 siblings, 1 reply; 16+ messages in thread
From: Bjorn Mork @ 2011-12-13  4:33 UTC (permalink / raw
  To: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA
  Cc: Bjørn Mork

From: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>

Huawei use the product code HUAWEI_PRODUCT_E353 (0x1506) for a
number of different devices, which each can appear with a number
of different descriptor sets.  Different types of interfaces
can be identified by looking at the subclass and protocol fields

Subclass 1 protocol 8 is actually the data interface of a CDC
ECM set, with subclass 1 protocol 9 as the control interface.
Neither support serial data communcation, and cannot therefore
be supported by this driver.

At the same time, add a few other sets which appear if the
device is configured in "Windows mode" using this modeswitch
message:
55534243000000000000000000000011060000000100000000000000000000

Signed-off-by: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
---
 drivers/usb/serial/option.c |    4 +++-
 1 files changed, 3 insertions(+), 1 deletions(-)

diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index e342660..c90f5a4 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -663,7 +663,9 @@ static const struct usb_device_id option_ids[] = {
 	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x01) },
 	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x02) },
 	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x03) },
-	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x08) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x10) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x12) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x13) },
 	{ USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V640) },
 	{ USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V620) },
 	{ USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V740) },
-- 
1.7.7.3

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 2/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific
       [not found] ` <1323750784-32608-1-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
  2011-12-13  4:33   ` [PATCH 1/3] option: Removing one bogus and adding some new Huawei combinations Bjorn Mork
@ 2011-12-13  4:33   ` Bjorn Mork
       [not found]     ` <1323750784-32608-3-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
  2011-12-13  4:33   ` [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol Bjorn Mork
  2 siblings, 1 reply; 16+ messages in thread
From: Bjorn Mork @ 2011-12-13  4:33 UTC (permalink / raw
  To: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA
  Cc: Bjørn Mork

From: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>

Some vendors, like Huawei, use the vendor specific class code all over the
place even for devices which otherwise conform pretty well to the CDC ECM
specification.  This allows such devices to be supported merely by
adding them to the device specific whitelist.

Signed-off-by: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
---
 drivers/net/usb/cdc_ether.c |    8 ++++++--
 1 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c
index 99ed6eb..84840dd 100644
--- a/drivers/net/usb/cdc_ether.c
+++ b/drivers/net/usb/cdc_ether.c
@@ -211,8 +211,12 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
 
 			/* a data interface altsetting does the real i/o */
 			d = &info->data->cur_altsetting->desc;
-			if (d->bInterfaceClass != USB_CLASS_CDC_DATA) {
-				dev_dbg(&intf->dev, "slave class %u\n",
+			if ((d->bInterfaceClass != USB_CLASS_CDC_DATA) &&
+				/* Allow vendor specific data interface iff
+				   control interface is vendor specific */
+				!(info->control->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
+					d->bInterfaceClass == USB_CLASS_VENDOR_SPEC)) {
+				dev_dbg(&intf->dev, "xslave class %u\n",
 					d->bInterfaceClass);
 				goto bad_desc;
 			}
-- 
1.7.7.3

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
       [not found] ` <1323750784-32608-1-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
  2011-12-13  4:33   ` [PATCH 1/3] option: Removing one bogus and adding some new Huawei combinations Bjorn Mork
  2011-12-13  4:33   ` [PATCH 2/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific Bjorn Mork
@ 2011-12-13  4:33   ` Bjorn Mork
       [not found]     ` <1323750784-32608-4-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
  2 siblings, 1 reply; 16+ messages in thread
From: Bjorn Mork @ 2011-12-13  4:33 UTC (permalink / raw
  To: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA
  Cc: Bjørn Mork

From: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>

Some WWAN LTE/3G devices based on chipsets from Qualcomm provide
near standard CDC ECM interfaces in addition to the usual serial
interfaces.  But they cannot be fully configured using AT commands
over a serial interface.  It is necessary to speak the proprietary
Qualcomm Messaging Interface (QMI) protocol to the device to
enable the ethernet proxy functionality.

This protocol is exposed by the device through the standard CDC
USB_CDC_SEND_ENCAPSULATED_COMMAND and USB_CDC_GET_ENCAPSULATED_RESPONSE
control messages, and using USB_CDC_NOTIFY_RESPONSE_AVAILABLE
notifications.

This driver adds a usbnet minidriver with very rudimentary QMI
support.

The layout of the usb interfaces can vary a lot within a single
device, depending on which "modeswitch" command has been used to
switch it from usb-storage mode.  Control and data interfaces
can be combined, or they can look like standard CDC ECM interfaces
with the appropriate descriptors but with vendor specific class
code. This driver is attempting to support them all, by reusing
as much as possible of the existing usbnet infrastructure.

Signed-off-by: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
---
 drivers/net/usb/Kconfig         |   13 +
 drivers/net/usb/Makefile        |    2 +
 drivers/net/usb/qmi.c           |  730 +++++++++++++++++++++++++++++++++++++++
 drivers/net/usb/qmi.h           |  101 ++++++
 drivers/net/usb/qmi_wwan_core.c |  206 +++++++++++
 5 files changed, 1052 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/usb/qmi.c
 create mode 100644 drivers/net/usb/qmi.h
 create mode 100644 drivers/net/usb/qmi_wwan_core.c

diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 2335761..eed9a37 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -461,4 +461,17 @@ config USB_VL600
 
 	  http://ubuntuforums.org/showpost.php?p=10589647&postcount=17
 
+config USB_NET_QMI_WWAN
+	tristate "USB-to-WWAN driver for QMI based 3G and LTE modems"
+	depends on USB_NET_CDCETHER
+	help
+	  Support WWAN LTE/3G devices based on chipsets from Qualcomm,
+	  using the Qualcomm Messaging Interface (QMI) protocol to
+	  configure the device.
+
+	  Note that it is still necessary to configure some aspects of
+	  the device using AT commands on a tty.  Select the option
+	  driver to get this support.
+
+
 endmenu
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index c203fa2..ed715da 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -29,4 +29,6 @@ obj-$(CONFIG_USB_SIERRA_NET)	+= sierra_net.o
 obj-$(CONFIG_USB_NET_CX82310_ETH)	+= cx82310_eth.o
 obj-$(CONFIG_USB_NET_CDC_NCM)	+= cdc_ncm.o
 obj-$(CONFIG_USB_VL600)		+= lg-vl600.o
+obj-$(CONFIG_USB_NET_QMI_WWAN)	+= qmi_wwan.o
+qmi_wwan-objs := qmi_wwan_core.o qmi.o
 
diff --git a/drivers/net/usb/qmi.c b/drivers/net/usb/qmi.c
new file mode 100644
index 0000000..b4f6a4b
--- /dev/null
+++ b/drivers/net/usb/qmi.c
@@ -0,0 +1,730 @@
+/*
+ * Copyright (c) 2011  Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+
+/*
+ * Dealing with devices using Qualcomm Messaging Interface (QMI) for
+ * configuration.  Full documentation of the protocol can be obtained
+ * from http://developer.qualcomm.com/
+ *
+ * Some of this code may be inspired by code from the Qualcomm Gobi
+ * 2000 and Gobi 3000 drivers, available at http://www.codeaurora.org/
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include "qmi.h"
+
+
+/* QMI protocol debug output */
+static int qmi_debug;
+module_param(qmi_debug, int, 0);
+MODULE_PARM_DESC(qmi_debug, "Enable QMI protocol debugging");
+
+/* find and return a pointer to the requested tlv */
+struct qmi_tlv *qmi_get_tlv(u8 type, u8 *buf, size_t len)
+{
+	u8 *p;
+	struct qmi_tlv *t;
+
+	for (p = buf; p < buf + len; p += t->len + sizeof(struct qmi_tlv)) {
+		t = (struct qmi_tlv *)p;
+		if (t->type == type)
+			return (p + t->len <= buf + len) ? t : NULL;
+	}
+	return NULL;
+}
+
+
+/* TLV 0x02 is status: return a negative QMI error code or 0 if OK */
+int qmi_verify_status_tlv(u8 *buf, size_t len)
+{
+	struct qmi_tlv *tlv = (void *)qmi_get_tlv(0x02, buf, len);
+	struct qmi_tlv_response_data *r = (void *)tlv->bytes;
+
+	if (!tlv || tlv->len != 2 * sizeof(__le16))
+		return -1; /* QMI_ERR_MALFORMED_MSG */
+
+	return r->error ? -r->code : 0;
+}
+
+static char *decode_trans_flags(const u8 flags)
+{
+	switch (flags) {
+	case 0: return "request";
+	case 2:	return "response";
+	case 4:	return "indication";
+	case 6:	return "reserved";
+	default: return "invalid";
+	}
+}
+
+static char *qmi_system(u8 system)
+{
+	switch (system) {
+	case 0:	return "QMI_CTL";
+	case 1:	return "QMI_WDS";
+	case 2:	return "QMI_DMS";
+	default: return "(unknown)";
+	}
+}
+
+
+/* debug print TLVs from a QMI message */
+static void qmi_tlv_dump(struct qmi_state *qmi, u8 *data, size_t len, u8 system, __le16 msgid)
+{
+	u8 *p;
+	struct qmi_tlv *t;
+	char linebuf[100];
+
+	for (p = data; p < data + len; p += t->len + sizeof(struct qmi_tlv)) {
+		t = (struct qmi_tlv *)p;
+
+		/* mark an empty string */
+		linebuf[0] = 0;
+		if (t->type == 0x02) { /* status response */
+			struct qmi_tlv_response_data *r = (void *)t->bytes;
+			snprintf(linebuf, sizeof(linebuf), "%s (0x%04x)",
+				r->error ? "FAILED" : "SUCCESS", r->code);
+		} else {
+			switch (system) {
+			case QMI_WDS:
+				switch (msgid) {
+				case 0x0001:
+					switch (t->type) {
+					case 0x16:
+						snprintf(linebuf, sizeof(linebuf), "tx: %d bps, rx: %d bps",
+							*(__le32 *)t->bytes, *(__le32 *)(t->bytes + 4));
+					}
+				}
+				break;
+			case QMI_DMS:
+				switch (msgid) {
+				case 0x002b:
+					if (t->type == 0x11 || t->type == 0x12)
+						snprintf(linebuf, sizeof(linebuf),
+							"PIN%d status=%d, %d verify retries left, %d unblock retries left",
+							t->type & 0x3, t->bytes[0], t->bytes[1], t->bytes[2]);
+				}
+			}
+		}
+
+		/* do a default byte dump if the linebuf is empty */
+		if (linebuf[0] == 0)
+			hex_dump_to_buffer(t->bytes, t->len, 16, 1, linebuf, sizeof(linebuf), true);
+
+		dev_info(&qmi->dev->dev, "[0x%02x] (%d) %s\n", t->type, t->len, linebuf);
+	}
+}
+
+/* verify QMUX message header and return pointer to the (first) message if OK */
+static struct qmi_msg *qmi_qmux_verify(u8 *data, size_t len)
+{
+	struct qmux *h =  (void *)data;
+	struct qmi_msg *m;
+	ssize_t hsize = sizeof(struct qmux);
+
+	if (len < sizeof(struct qmux) || /* short packet */
+		h->tf != 0x01 || h->len != (len - 1)) /* invalid QMUX packet! */
+		return NULL;
+
+	/* tid has a different size for QMI_CTL for some fucking stupid reason */
+	if (h->service == QMI_CTL)
+		hsize--;
+
+	m = (struct qmi_msg *)(data + hsize);
+
+	/* insanity checking before any further dereferencing... */
+	if (hsize + sizeof(struct qmi_msg) + m->len > len)
+		return NULL;
+
+	return m;
+}
+
+/* debug print a QMUX packet */
+static void qmi_dump_qmux(struct qmi_state *qmi, u8 *data, size_t len)
+{
+	struct qmux *h =  (void *)data;
+	struct qmi_msg *m;
+
+	m = qmi_qmux_verify(data, len);
+	if (!m) {
+		dev_info(&qmi->dev->dev, "invalid QMUX packet (%zu bytes)\n", len);
+		return;
+	}
+
+	/* dump parts of the QMUX header and the message ID */
+	dev_info(&qmi->dev->dev, "%s %s: msg 0x%04x (len %d) from \"%s\" with cid=0x%02x%s and TLVs:\n",
+		qmi_system(h->service),
+		decode_trans_flags(h->service == QMI_CTL ? h->flags << 1 : h->flags),
+		m->msgid,
+		h->len,
+		h->ctrl & 0x80 ? "service" : "control point",
+		h->qmicid, h->qmicid == 0xff ? " (broadcast)" : "");
+
+	/* dump the TLVs */
+	qmi_tlv_dump(qmi, m->tlv, m->len, h->service, m->msgid);
+}
+
+static int qmi_dms_parse(struct qmi_state *qmi, struct qmi_msg *m)
+{
+	int status;
+	struct qmi_tlv *tlv;
+
+	if (!m)
+		return -1;
+
+	/* check and save status TLV */
+	status = qmi_verify_status_tlv(m->tlv, m->len);
+
+	/* no QMI_DMS messages requires parsing unless status is OK */
+	if (status < 0)
+		return status;
+
+	switch (m->msgid) {
+	case 0x002b: /* QMI_DMS_UIM_GET_PIN_STATUS */
+		if (status == 0) {
+			/* PIN1 status */
+			tlv = qmi_get_tlv(0x11, m->tlv, m->len);
+			if (tlv && tlv->len == 3) {
+				switch (tlv->bytes[0]) {
+				case 2: /* PIN enabled, verified */
+				case 3: /* PIN disabled */
+					qmi->flags |= QMI_FLAG_PINOK;
+				}
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int qmi_ctl_parse(struct qmi_state *qmi, struct qmi_msg *m)
+{
+	int status;
+	struct qmi_tlv *tlv;
+
+	if (!m)
+		return -1;
+
+	/* check and save status TLV */
+	status = qmi_verify_status_tlv(m->tlv, m->len);
+
+	if (status < 0)
+		return status;
+
+	/* TLVs are message dependent */
+	switch (m->msgid) {
+	case 0x0022: /* allocate cid */
+		/* TLV 0x01 is system + cid */
+		tlv = qmi_get_tlv(0x01, m->tlv, m->len);
+
+		/* can only save CID if there is a slot for the subsystem */
+		if (tlv && tlv->len == 2 && tlv->bytes[0] < sizeof(qmi->cid))
+			qmi->cid[tlv->bytes[0]] = tlv->bytes[1];
+		else
+			return -1;
+	}
+
+	return status;
+}
+
+static int qmi_wds_parse(struct qmi_state *qmi, struct qmi_msg *m)
+{
+	int status;
+	struct qmi_tlv *tlv;
+
+	if (!m)
+		return -1;
+
+	/* check and save status TLV */
+	status = qmi_verify_status_tlv(m->tlv, m->len);
+
+	if (m->msgid == 0x0020 && -status == 0x001A) { /* QMI_ERR_NO_EFFECT */
+		memset(qmi->handle, 0xff, sizeof(qmi->handle)); /* global handle */
+		return 0;
+	}
+
+	if (status < 0)
+		return status;
+
+	switch (m->msgid) {
+	case 0x0020: /* QMI_WDS_START_NETWORK_INTERFACE */
+		/* TLV 0x01 is a 4 byte packet data handle we need to keep */
+		tlv = qmi_get_tlv(0x01, m->tlv, m->len);
+		if (tlv && tlv->len == sizeof(qmi->handle))
+			memcpy(qmi->handle, tlv->bytes, sizeof(qmi->handle));
+
+		break;
+
+	case 0x0022: /* QMI_WDS_GET_PKT_SRVC_STATUS */
+		/* TLV 0x01 is a 1 byte connection status */
+		tlv = qmi_get_tlv(0x01, m->tlv, m->len);
+		if (tlv && tlv->len == 1)
+			qmi->wds_status = tlv->bytes[0];
+
+	}
+
+	return status;
+}
+
+static void qmi_read_callback(struct urb *urb)
+{
+	struct qmi_state *qmi = (void *)urb->context;
+
+	if (urb->status == 0) {
+		struct qmux *h = (void *)urb->transfer_buffer;
+		struct qmi_msg *m = qmi_qmux_verify(urb->transfer_buffer, urb->actual_length);
+
+		switch (h->service) {
+		case QMI_CTL:
+			qmi_ctl_parse(qmi, m);
+			break;
+		case QMI_WDS:
+			qmi_wds_parse(qmi, m);
+			break;
+		case QMI_DMS:
+			qmi_dms_parse(qmi, m);
+		}
+
+		if (qmi_debug)
+			qmi_dump_qmux(qmi, urb->transfer_buffer, urb->actual_length);
+	}
+}
+
+static int qmi_recv_sync(struct qmi_state *qmi)
+{
+	int status;
+
+	status = usb_control_msg(qmi->dev,
+			       usb_rcvctrlpipe(qmi->dev, 0),
+			       USB_CDC_GET_ENCAPSULATED_RESPONSE, USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
+			       0, qmi->intfnr,
+			       qmi->rcvbuf, QMI_BUFLEN, 1000);
+
+	if (status > 0 && qmi_debug)
+		qmi_dump_qmux(qmi, qmi->rcvbuf, status);
+
+	return status;
+}
+
+static int qmi_send_sync(struct qmi_state *qmi, unsigned char *msg, size_t len)
+{
+	int status;
+
+	status = usb_control_msg(qmi->dev,
+				usb_sndctrlpipe(qmi->dev, 0),
+				USB_CDC_SEND_ENCAPSULATED_COMMAND, USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
+				0, qmi->intfnr,
+				msg, len, 1000);
+
+	if (qmi_debug) /* NOTE: regardless of status! */
+		qmi_dump_qmux(qmi, msg, len);
+
+	return status;
+}
+
+/* send message and wait for the reply - returns pointer to reply message */
+struct qmi_msg *qmi_send_and_wait_for_ack(struct qmi_state *qmi, unsigned char *data, size_t len, int timeout)
+{
+	int ret;
+	u8 system;
+	__le16 msgid;
+	struct qmi_msg *msg = qmi_qmux_verify(data, len);
+	struct qmux *h = (void *)data;
+
+	/* should never happen? */
+	if (!msg)
+		return NULL;
+	msgid = msg->msgid;
+	system = h->service;
+
+	/* flush any pending messages  */
+	do {
+		ret = qmi_recv_sync(qmi);
+	} while (ret > 0);
+
+	/* send our message */
+	ret = qmi_send_sync(qmi, data, len);
+	if (ret != len)
+		return NULL;
+
+	/* wait a while for the (correct) reply */
+	do {
+		ret = qmi_recv_sync(qmi);
+		msg = qmi_qmux_verify(qmi->rcvbuf, ret);
+		if (msg) {
+			h = (struct qmux *)qmi->rcvbuf;
+			if (msg->msgid == msgid && h->service == system) /* found it */
+				return msg;
+
+		}
+		if (timeout--)
+			msleep(100);
+	} while (timeout > 0);
+
+	/* no reply */
+	return NULL;
+}
+
+/* add a TLV to an existing QMUX */
+static size_t qmi_add_tlv(u8 *buf, size_t buflen, u8 type, const u8 *tlvdata, size_t datalen)
+{
+	struct qmux *h =  (void *)buf;
+	struct qmi_msg *m = (void *)(buf + sizeof(struct qmux) - (h->service == QMI_CTL));
+	struct qmi_tlv *tlv = (void *)(buf + h->len + 1);
+	size_t tlvlen = sizeof(struct qmi_tlv) + datalen;
+
+	if (buflen < h->len + tlvlen - 1)
+		return -1;
+
+	tlv->type = type;
+	tlv->len = datalen;
+	memcpy(tlv->bytes, tlvdata, datalen);
+	h->len += tlvlen;
+	m->len += tlvlen;
+	return h->len + 1;
+}
+
+/* get a new transaction id */
+static __le16 new_tid(void)
+{
+	static __le16 tid;
+	return ++tid;
+}
+
+/* assemble a complete QMUX packet with one QMI message */
+static size_t qmi_create_msg(char *buf, size_t buflen, u8 cid, u8 system, __le16 msgid)
+{
+	struct qmux *h =  (void *)buf;
+	struct qmi_msg *m;
+	size_t hsize = sizeof(struct qmux);
+
+	/* this is weird, but it seems as though QMI_CTL uses 1 byte
+	   transaction ids while the other systems use 2 bytes as specified
+	   in the standard */
+	if (system == QMI_CTL)
+		hsize--;
+
+	/* sanity check */
+	if (buflen < hsize + sizeof(struct qmi_msg))
+		return -1;
+
+	h->tf = 1;     /* always 1 */
+	h->ctrl = 0;   /* 0 - control point */
+	h->service = system;
+	h->qmicid = cid;
+	h->flags = 0;  /* 0 - request */
+
+	/* see comment above */
+	if (system == QMI_CTL)
+		h->tid.b = new_tid() & 0xff;
+	else
+		h->tid.w = new_tid();
+
+	m = (struct qmi_msg *)(buf + hsize);
+	m->msgid = msgid;
+
+	h->len = hsize + sizeof(struct qmi_msg)  - 1;
+	m->len = 0;
+	return h->len + 1;
+}
+
+/* QMI_CTL msg 0x0022 is "request cid", TLV 0x01 is system */
+static int qmi_ctl_request_cid(struct qmi_state *qmi, u8 system)
+{
+	size_t len;
+	u8 tlvdata[1] = { system };
+	char buf[32];
+
+	/* return immediately if a CID is already allocated */
+	if (qmi->cid[system])
+		return qmi->cid[system];
+
+	/* else request a new one */
+	len = qmi_create_msg(buf, sizeof(buf), 0, QMI_CTL, 0x0022);
+	len = qmi_add_tlv(buf, sizeof(buf), 0x01, tlvdata, sizeof(tlvdata));
+
+	/* send message */
+	return qmi_ctl_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
+}
+
+/* QMI_WDS msg 0x0001 is "" */
+static int qmi_wds_report(struct qmi_state *qmi)
+{
+	size_t len;
+	u8 cid = qmi->cid[QMI_WDS];
+	u8 tlvdata[1] = { 1 };	/* generic "enable" */
+	char buf[32];
+
+	/* enable connection status reports */
+	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_WDS, 0x0001);
+	len = qmi_add_tlv(buf, sizeof(buf), 0x10, tlvdata, sizeof(tlvdata));	/* current channel rate */
+	len = qmi_add_tlv(buf, sizeof(buf), 0x15, tlvdata, sizeof(tlvdata));	/* current data bearer tech */
+
+	/* send message */
+	return qmi_wds_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
+}
+
+/* QMI_WDS msg 0x0020 is "" */
+static int qmi_wds_start(struct qmi_state *qmi)
+{
+	size_t len;
+	u8 cid = qmi->cid[QMI_WDS];
+	char buf[32];
+
+	if (!cid)
+		return -1;
+
+	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_WDS, 0x0020);
+
+	/* send message */
+	return qmi_wds_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
+}
+
+/* QMI_WDS msg 0x0021 is "" */
+static int qmi_wds_stop(struct qmi_state *qmi)
+{
+	size_t len;
+	u8 cid = qmi->cid[QMI_WDS];
+	char buf[32];
+
+	if (!cid)
+		return -1;
+
+	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_WDS, 0x0021);
+	len = qmi_add_tlv(buf, sizeof(buf), 0x01, qmi->handle, sizeof(qmi->handle));	 /* packet data handle */
+
+	return qmi_wds_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
+}
+
+/* QMI_WDS msg 0x0022 is "" */
+static int qmi_wds_status(struct qmi_state *qmi)
+{
+	int ret;
+	size_t len;
+	u8 cid = qmi->cid[QMI_WDS];
+	char buf[32];
+
+	if (!cid)
+		return -1;
+
+	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_WDS, 0x0022);
+
+	ret = qmi_wds_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
+	if (ret < 0)
+		return ret;
+
+	return qmi->wds_status;
+}
+
+/* QMI_CTL msg 0x0023 is "release cid", TLV 0x01 is system + cid */
+static int qmi_ctl_release_cid(struct qmi_state *qmi, u8 system)
+{
+	char buf[32];
+	size_t len = qmi_create_msg(buf, sizeof(buf), 0, QMI_CTL, 0x0023);
+
+	if (system < sizeof(qmi->cid)) {
+		u8 tlvdata[2] = { system, qmi->cid[system] };
+		len = qmi_add_tlv(buf, sizeof(buf), 0x01, tlvdata, sizeof(tlvdata));
+
+	} else {
+		return -1;
+	}
+
+	/* no need to parse result - cant reuse CID in any case */
+	qmi_send_and_wait_for_ack(qmi, buf, len, 30);
+	qmi->cid[system] = 0;
+
+	return 1;
+}
+
+static int qmi_dms_verify_pin(struct qmi_state *qmi)
+{
+	int ret;
+	size_t len;
+	u8 cid = qmi->cid[QMI_DMS];
+	char buf[32];
+
+	if (!cid)
+		return -1;
+
+	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_DMS, 0x002b);
+	ret = qmi_dms_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
+	return qmi->flags && QMI_FLAG_PINOK;
+}
+
+
+/* ----- exported API ----- */
+
+int qmi_reset(struct qmi_state *qmi)
+{
+	/* NOTE:  We do NOT clear the allocated CIDs! */
+	qmi->state = QMI_STATE_INIT;
+	qmi->wds_status = 0;
+	qmi->flags = 0;
+	return 1;
+}
+
+int qmi_disconnect(struct qmi_state *qmi)
+{
+	qmi->flags &= ~QMI_FLAG_RECV;         /* disable async receiving */
+	qmi_wds_stop(qmi);
+	qmi_wds_status(qmi);
+	qmi->state = QMI_STATE_DOWN;
+	return 1;
+}
+
+/* receive a QMUX can be called in interrupt context! */
+int qmi_recv(struct qmi_state *qmi)
+{
+	if (qmi->flags & QMI_FLAG_RECV) /* async receiving enabled? */
+		return usb_submit_urb(qmi->urb, GFP_ATOMIC);
+	else
+		return 0;
+}
+
+/*
+ * return current connection state with side effects: will run through
+ *  the states from INIT to CONN if possible
+ */
+int qmi_conn_state(struct qmi_state *qmi)
+{
+	int ret;
+
+	dev_dbg(&qmi->dev->dev, ".state=%d, .cid[QMI_WDS]=0x%02x, .cid[QMI_DMS]=0x%02x, .flags=0x%08lx, .handle=0x%08x\n",
+		qmi->state,
+		qmi->cid[QMI_WDS],
+		qmi->cid[QMI_DMS],
+		qmi->flags,
+		*(__le32 *)qmi->handle);
+
+	switch (qmi->state) {
+	/* these states don't trigger any action */
+	case QMI_STATE_DOWN:
+		return 1;
+	case QMI_STATE_UP:
+		qmi->flags |= QMI_FLAG_RECV;  /* enable async receiving  */
+		return 0;
+	case QMI_STATE_ERR:
+		return -1;
+
+	case QMI_STATE_INIT:
+		qmi->flags &= ~QMI_FLAG_RECV;  /* disable async receiving */
+
+		/* request CIDs */
+		if (qmi_ctl_request_cid(qmi, QMI_WDS) < 0 || qmi_ctl_request_cid(qmi, QMI_DMS) < 0) {
+			qmi->state = QMI_STATE_ERR; /* FATAL - no way out except for unplugging the device */
+			return -1;
+		}
+
+		/* stay in this state until PIN code is entered */
+		if (!qmi_dms_verify_pin(qmi)) {
+			dev_notice(&qmi->dev->dev, "please enter PIN code using AT+CPIN=\"xxxx\" on a ttyUSB0 device\n");
+			return 1;
+		}
+		/* check status */
+		switch (qmi_wds_status(qmi)) {
+		case 1: /* DISCONNECTED - attempt to connect */
+			ret = qmi_wds_start(qmi);
+			if (ret < 0) {
+				dev_notice(&qmi->dev->dev, "connection failed: 0x%04x\n", -ret);
+				break;
+			}
+
+			/* fallthrough */
+		case 2: /* CONNECTED - note that */
+			qmi_wds_report(qmi); /* enable status reports */
+			qmi->state = QMI_STATE_UP;
+
+		/* do nothing for the other states */
+		}
+		break;
+
+	default:
+		dev_dbg(&qmi->dev->dev, "%s() unknown state=%d\n", __func__, qmi->state);
+	}
+	return 1;  /* not connected yet - */
+}
+
+
+/* allocate and initiate a QMI state device */
+struct qmi_state *qmi_init(struct usb_interface *intf)
+{
+	struct qmi_state *qmi = kmalloc(sizeof(struct qmi_state), GFP_KERNEL);
+
+	if (!qmi)
+		return NULL;
+
+	memset(qmi, 0, sizeof(*qmi));
+
+	/*
+	 * keep track of the device receiving the control messages and the
+	 * number of the CDC (like) control interface which is our target.
+	 * Note that the interface might be disguised as vendor specific,
+	 * and be a combined CDC control/data interface
+	 */
+	qmi->dev = interface_to_usbdev(intf);
+	qmi->intfnr = intf->cur_altsetting->desc.bInterfaceNumber;
+	/* FIXME: it would be useful to verify that this interface actually talks CDC */
+
+	/* create async receive URB */
+	qmi->urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!qmi->urb) {
+		kfree(qmi);
+		return NULL;
+	}
+
+	/* pre-allocate buffer for receiving */
+	qmi->rcvbuf = kmalloc(QMI_BUFLEN, GFP_KERNEL);
+
+	if (!qmi->rcvbuf) {
+		usb_free_urb(qmi->urb);
+		kfree(qmi);
+		return NULL;
+	}
+
+	/* usb control setup */
+	qmi->setup.bRequestType = USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE;
+	qmi->setup.bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
+	qmi->setup.wValue = 0; /* zero */
+	qmi->setup.wIndex = qmi->intfnr;
+	qmi->setup.wLength = QMI_BUFLEN;
+
+	/* prepare the async receive URB */
+	usb_fill_control_urb(qmi->urb, qmi->dev,
+			usb_rcvctrlpipe(qmi->dev, 0),
+			(char *)&qmi->setup,
+			qmi->rcvbuf,
+			QMI_BUFLEN,
+			qmi_read_callback, qmi);
+
+	return qmi;
+}
+
+/* disable and free a QMI state device */
+int qmi_exit(struct qmi_state *qmi)
+{
+	int i;
+
+	/* release all CIDs - may need to sleep */
+	for (i = 1; i < sizeof(qmi->cid); i++)
+		qmi_ctl_release_cid(qmi, i);
+
+	/* free all buffers */
+	kfree(qmi->rcvbuf);
+
+	/* free URBs */
+	usb_free_urb(qmi->urb);
+
+	kfree(qmi);
+
+	return 0;
+}
diff --git a/drivers/net/usb/qmi.h b/drivers/net/usb/qmi.h
new file mode 100644
index 0000000..bff94f1
--- /dev/null
+++ b/drivers/net/usb/qmi.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2011  Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+
+/*
+ * Dealing with devices using Qualcomm Messaging Interface (QMI) for
+ * configuration.  Full documentation of the protocol can be obtained
+ * from http://developer.qualcomm.com/
+ *
+ * Some of this code may be inspired by code from the Qualcomm Gobi
+ * 2000 and Gobi 3000 drivers, available at http://www.codeaurora.org/
+ */
+
+#define QMI_BUFLEN 128
+
+enum qmi_device_state {
+	QMI_STATE_INIT = 0, /* allocate CIDs, verify pin code */
+	QMI_STATE_DOWN,    /* connection is down */
+	QMI_STATE_UP,      /* connection is up */
+	QMI_STATE_ERR,     /* fatal and final error state, e.g. no CID available - no way out of here! */
+};
+
+/* the different QMI subsystems we are using */
+enum qmi_subsystems {
+	QMI_CTL = 0,
+	QMI_WDS,
+	QMI_DMS,
+	QMI_SYSMAX,
+};
+
+#define QMI_FLAG_RECV		0x00000001
+#define QMI_FLAG_PINOK		0x00000002
+
+struct qmi_state {
+	struct usb_device *dev;
+	struct urb *urb;		/* receive urb */
+	unsigned char *rcvbuf;		/* pre-allocated receive buffer */
+	struct usb_ctrlrequest setup;	/* the receive setup - 8 bytes */
+	unsigned long flags;		/* used for en/dis-abling functionality on the fly */
+	u8 state;			/* for connection state machine */
+	u8 wds_status;			/* current value for QMI_WDS message 0x0022 or 0 if uninitialized */
+	__le16 intfnr;			/* keeping track of the interface we are referring to */
+	u8 handle[4];			/* connection handle needed for disconnect */
+	u8 cid[QMI_SYSMAX];		/* keeping track of cid per subsystem */
+};
+
+/* combined QMUX and SDU */
+struct qmux {
+	u8 tf;		/* always 1 */
+	__le16 len;	/* excluding tf */
+	u8 ctrl;	/* b7: sendertype 1 => service, 0 => control point */
+	u8 service;	/* 0 => QMI_CTL, 1 => QMI_WDS, .. */
+	u8 qmicid;	/* client id or 0xff for broadcast */
+	u8 flags;	/* always 0 for req */
+	union {
+		__le16 w;  /* each control point maintains a transaction id counter - non-zero */
+		u8 b;	/* system QMI_CTL uses one byte transaction ids! */
+	} tid;
+	u8 msg[];	/* one or more messages */
+} __packed;
+
+struct qmi_msg {
+	__le16 msgid;
+	__le16 len;
+	u8 tlv[];	/* zero or more tlvs */
+} __packed;
+
+struct qmi_tlv {
+	u8 type;
+	__le16 len;
+	u8 bytes[];
+} __packed;
+
+struct qmi_tlv_response_data {
+	__le16 error;
+	__le16 code;
+} __packed;
+
+
+/* reset state to INIT */
+extern int qmi_reset(struct qmi_state *qmi);
+
+/* disconnect WWAN connection */
+extern int qmi_disconnect(struct qmi_state *qmi);
+
+/* must be called whenever USB_CDC_NOTIFY_RESPONSE_AVAILABLE is received */
+extern int qmi_recv(struct qmi_state *qmi);
+
+/* called periodically, whenever a state transition is wanted */
+extern int qmi_conn_state(struct qmi_state *qmi);
+
+/* initialize */
+extern struct qmi_state *qmi_init(struct usb_interface *intf);
+
+/* clean up */
+extern int qmi_exit(struct qmi_state *qmi);
diff --git a/drivers/net/usb/qmi_wwan_core.c b/drivers/net/usb/qmi_wwan_core.c
new file mode 100644
index 0000000..bf25e33
--- /dev/null
+++ b/drivers/net/usb/qmi_wwan_core.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2011  Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+#include "qmi.h"
+
+/* overloading the cdc_state structure */
+struct qmi_wwan_state {
+	struct qmi_state		*qmi;
+	struct usb_cdc_union_desc	*u;
+	struct usb_cdc_ether_desc	*ether;
+	struct usb_interface		*control;
+	struct usb_interface		*data;
+};
+
+
+static void qmi_wwan_cdc_status(struct usbnet *dev, struct urb *urb)
+{
+	struct usb_cdc_notification	*event;
+	struct qmi_wwan_state *info = (void *)&dev->data;
+
+	if (urb->actual_length < sizeof *event) {
+		netdev_dbg(dev->net, "short status urb rcvd: %d bytes\n", urb->actual_length);
+		return;
+	}
+
+	event = urb->transfer_buffer;
+	switch (event->bNotificationType) {
+	case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
+		qmi_recv(info->qmi);	/* request response */
+		break;
+	default:
+		/* reuse usbnet() for handling other messages */
+		usbnet_cdc_status(dev, urb);
+	}
+}
+
+static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int status;
+	struct qmi_wwan_state *info = (void *)&dev->data;
+
+	status = usbnet_generic_cdc_bind(dev, intf);
+	if (status < 0)
+		return status;
+
+	/* initialize QMI data */
+	info->qmi = qmi_init(intf);
+	if (!info->qmi) {
+		usbnet_cdc_unbind(dev, intf);
+		return -1;
+	}
+	return 0;
+}
+
+static int qmi_wwan_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int status;
+	struct qmi_wwan_state *info = (void *)&dev->data;
+
+	memset(info, 0, sizeof(*info));
+	status = usbnet_get_endpoints(dev, intf);
+	if (status < 0)
+		return status;
+
+	/* initialize QMI data */
+	info->qmi = qmi_init(intf);
+	if (!info->qmi)
+		return -1;
+
+	info->control = intf;
+	return 0;
+}
+
+static void qmi_wwan_cdc_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct qmi_wwan_state *info = (void *)&dev->data;
+
+	/* release our private structure */
+	qmi_exit(info->qmi);
+	info->qmi = NULL;
+
+	/* disconnect */
+	usbnet_cdc_unbind(dev, intf);
+}
+
+static int qmi_wwan_reset(struct usbnet *dev)
+{
+	struct qmi_wwan_state *info = (void *)&dev->data;
+
+	qmi_reset(info->qmi);
+	return 1;
+}
+
+static int qmi_wwan_stop(struct usbnet *dev)
+{
+	struct qmi_wwan_state *info = (void *)&dev->data;
+
+	qmi_disconnect(info->qmi);
+	return 1;
+}
+
+/* abusing check_connect for triggering QMI state transitions */
+static int qmi_wwan_check_connect(struct usbnet *dev)
+{
+	struct qmi_wwan_state *info = (void *)&dev->data;
+	return qmi_conn_state(info->qmi);
+}
+
+static const struct driver_info	qmi_wwan_ecm_info = {
+	.description =	"QMI speaking CDC ECM like wwan device",
+	.flags =	 FLAG_WWAN,
+	.bind =		 qmi_wwan_cdc_ecmlike_bind,
+	.unbind =	 qmi_wwan_cdc_unbind,
+	.status =	 qmi_wwan_cdc_status,
+	.check_connect = qmi_wwan_check_connect,
+	.stop =          qmi_wwan_stop,
+	.reset =         qmi_wwan_reset,
+};
+
+static const struct driver_info	qmi_wwan_info = {
+	.description =	"QMI speaking wwan device",
+	.flags =	 FLAG_WWAN,
+	.bind =		 qmi_wwan_cdc_bind,
+	.unbind =	 qmi_wwan_cdc_unbind,
+	.status =	 qmi_wwan_cdc_status,
+	.check_connect = qmi_wwan_check_connect,
+	.stop =          qmi_wwan_stop,
+	.reset =         qmi_wwan_reset,
+};
+
+#define HUAWEI_VENDOR_ID	0x12D1
+
+static const struct usb_device_id products[] = {
+{
+	/* Qmi_Wwan E392, E398, ++? */
+	.match_flags    =   USB_DEVICE_ID_MATCH_VENDOR
+		 | USB_DEVICE_ID_MATCH_INT_INFO,
+	.idVendor               = HUAWEI_VENDOR_ID,
+	.bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	.bInterfaceSubClass	= 1,
+	.bInterfaceProtocol	= 9,
+	.driver_info = (unsigned long)&qmi_wwan_ecm_info,
+}, {
+	/* Qmi_Wwan device id 1413 ++? */
+	.match_flags    =   USB_DEVICE_ID_MATCH_VENDOR
+		 | USB_DEVICE_ID_MATCH_INT_INFO,
+	.idVendor               = HUAWEI_VENDOR_ID,
+	.bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	.bInterfaceSubClass	= 6,
+	.bInterfaceProtocol	= 255,
+	.driver_info = (unsigned long)&qmi_wwan_ecm_info,
+}, {
+	/* Qmi_Wwan E392, E398, ++? "Windows mode" */
+	.match_flags    =   USB_DEVICE_ID_MATCH_VENDOR
+		 | USB_DEVICE_ID_MATCH_INT_INFO,
+	.idVendor               = HUAWEI_VENDOR_ID,
+	.bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	.bInterfaceSubClass	= 1,
+	.bInterfaceProtocol	= 17,
+	.driver_info = (unsigned long)&qmi_wwan_info,
+},
+{ },		/* END */
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver qmi_wwan_driver = {
+	.name =		"qmi_wwan",
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+	.reset_resume =	usbnet_resume,
+	.supports_autosuspend = 1,
+};
+
+static int __init qmi_wwan_init(void)
+{
+	/* we remap struct (cdc_state) so we should be compatible */
+	BUILD_BUG_ON(sizeof(struct cdc_state) != sizeof(struct qmi_wwan_state));
+
+	return usb_register(&qmi_wwan_driver);
+}
+module_init(qmi_wwan_init);
+
+static void __exit qmi_wwan_exit(void)
+{
+	usb_deregister(&qmi_wwan_driver);
+}
+module_exit(qmi_wwan_exit);
+
+MODULE_AUTHOR("Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>");
+MODULE_DESCRIPTION("Qualcomm Messaging Interface (QMI) WWAN driver");
+MODULE_LICENSE("GPL");
-- 
1.7.7.3

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH 2/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific
       [not found]     ` <1323750784-32608-3-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
@ 2011-12-13  4:54       ` Joe Perches
  2011-12-13  4:59         ` Bjørn Mork
  0 siblings, 1 reply; 16+ messages in thread
From: Joe Perches @ 2011-12-13  4:54 UTC (permalink / raw
  To: Bjorn Mork
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA

On Tue, 2011-12-13 at 05:33 +0100, Bjorn Mork wrote:
> diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c
[]
> @@ -211,8 +211,12 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
[]
> -				dev_dbg(&intf->dev, "slave class %u\n",
[]
> +				dev_dbg(&intf->dev, "xslave class %u\n",

Why change the output from slave to xslave?


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 2/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific
  2011-12-13  4:54       ` Joe Perches
@ 2011-12-13  4:59         ` Bjørn Mork
       [not found]           ` <87vcplcbj3.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>
  0 siblings, 1 reply; 16+ messages in thread
From: Bjørn Mork @ 2011-12-13  4:59 UTC (permalink / raw
  To: Joe Perches
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA

Joe Perches <joe-6d6DIl74uiNBDgjK7y7TUQ@public.gmane.org> writes:

> On Tue, 2011-12-13 at 05:33 +0100, Bjorn Mork wrote:
>> diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c
> []
>> @@ -211,8 +211,12 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
> []
>> -				dev_dbg(&intf->dev, "slave class %u\n",
> []
>> +				dev_dbg(&intf->dev, "xslave class %u\n",
>
> Why change the output from slave to xslave?

That was unintentional.  Sorry.  Don't understand how checkpatch could
have missed that :-)

Will fix and submit again, unless there are objections to the actual
code change.



Bjørn
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 0/3] Adding a new driver for Huawei E392 WWAN modem
  2011-12-13  4:33 [PATCH 0/3] Adding a new driver for Huawei E392 WWAN modem Bjorn Mork
       [not found] ` <1323750784-32608-1-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
@ 2011-12-13  9:02 ` Bjørn Mork
  1 sibling, 0 replies; 16+ messages in thread
From: Bjørn Mork @ 2011-12-13  9:02 UTC (permalink / raw
  To: netdev; +Cc: linux-usb

Bjorn Mork <bjorn@mork.no> writes:

> patch 3 adds the new driver.  Most of it is QMI protocol handling.
> The network device is a minimalistic usbnet minidriver.

In case anyone wonders what this really does, here's an excerpt of a
connection being initiated by simply doing "dhclient wwan1". 
"ip link set dev wwan1 up" would achieve the same, except for address
configuration of course.


I'm explaining inline:


Dec 13 04:15:38 nemi kernel: [23893.115819] qmi_wwan 2-2:1.3: wwan1: register 'qmi_wwan' at usb-0000:00:1d.7-2, QMI speaking CDC ECM like wwan device, 76:bb:09:ca:8e:84
Dec 13 04:15:38 nemi kernel: [23893.116732] usbcore: registered new interface driver qmi_wwan
Dec 13 04:15:40 nemi kernel: [23894.842205] usb 2-2: QMI_CTL request: msg 0x0022 (len 15) from "control point" with cid=0x00 and TLVs:
Dec 13 04:15:40 nemi kernel: [23894.842214] usb 2-2: [0x01] (1) 01                                               .

requesting a client ID for subsystem QMI_WDS.  We need this for starting
the network connection.

Dec 13 04:15:40 nemi kernel: [23894.944602] usb 2-2: QMI_CTL response: msg 0x0022 (len 23) from "service" with cid=0x00 and TLVs:
Dec 13 04:15:40 nemi kernel: [23894.944614] usb 2-2: [0x02] (4) SUCCESS (0x0000)
Dec 13 04:15:40 nemi kernel: [23894.944622] usb 2-2: [0x01] (2) 01 03                                            ..

received QMI_WDS client ID = 3

Dec 13 04:15:40 nemi kernel: [23894.945827] usb 2-2: QMI_CTL request: msg 0x0022 (len 15) from "control point" with cid=0x00 and TLVs:
Dec 13 04:15:40 nemi kernel: [23894.945837] usb 2-2: [0x01] (1) 02                                               .

requesting a client ID for subsystem QMI_DMS.  We need this for
verifying SIM PIN state (well, we don't really need to do that, but it
enables us to understand why the connection fails if a PIN code is
required).


Dec 13 04:15:40 nemi kernel: [23895.048729] usb 2-2: QMI_CTL response: msg 0x0022 (len 23) from "service" with cid=0x00 and TLVs:
Dec 13 04:15:40 nemi kernel: [23895.048738] usb 2-2: [0x02] (4) SUCCESS (0x0000)
Dec 13 04:15:40 nemi kernel: [23895.048743] usb 2-2: [0x01] (2) 02 03                                            ..

received QMI_DMS client ID = 3


Dec 13 04:15:40 nemi kernel: [23895.050066] usb 2-2: QMI_DMS request: msg 0x002b (len 12) from "control point" with cid=0x03 and TLVs:

requesting PIN code state

Dec 13 04:15:40 nemi kernel: [23895.152746] usb 2-2: QMI_DMS response: msg 0x002b (len 31) from "service" with cid=0x03 and TLVs:
Dec 13 04:15:40 nemi kernel: [23895.152758] usb 2-2: [0x02] (4) SUCCESS (0x0000)
Dec 13 04:15:40 nemi kernel: [23895.152767] usb 2-2: [0x12] (3) PIN2 status=1, 3 verify retries left, 10 unblock retries left
Dec 13 04:15:40 nemi kernel: [23895.152777] usb 2-2: [0x11] (3) PIN1 status=2, 3 verify retries left, 10 unblock retries left

PIN1 is enabled but already verified, so we're ready to go.


Dec 13 04:15:40 nemi kernel: [23895.153987] usb 2-2: QMI_WDS request: msg 0x0022 (len 12) from "control point" with cid=0x03 and TLVs:

requesting connection state

Dec 13 04:15:41 nemi kernel: [23895.256746] usb 2-2: QMI_WDS response: msg 0x0022 (len 23) from "service" with cid=0x03 and TLVs:
Dec 13 04:15:41 nemi kernel: [23895.256757] usb 2-2: [0x02] (4) SUCCESS (0x0000)
Dec 13 04:15:41 nemi kernel: [23895.256765] usb 2-2: [0x01] (1) 01                                               .

state is disconnected

Dec 13 04:15:41 nemi kernel: [23895.257967] usb 2-2: QMI_WDS request: msg 0x0020 (len 12) from "control point" with cid=0x03 and TLVs:

starting a new connection

Dec 13 04:15:41 nemi kernel: [23895.360622] usb 2-2: QMI_WDS response: msg 0x0020 (len 26) from "service" with cid=0x03 and TLVs:
Dec 13 04:15:41 nemi kernel: [23895.360634] usb 2-2: [0x02] (4) SUCCESS (0x0000)
Dec 13 04:15:41 nemi kernel: [23895.360641] usb 2-2: [0x01] (4) e8 90 15 02                                      ....


successfully connected.  Got a 32bit handle (needed for disconnect).
The connection is now fully usable, and DHCP will now succeed (not
shown).


Dec 13 04:15:41 nemi kernel: [23895.361114] usb 2-2: QMI_WDS indication: msg 0x0022 (len 21) from "service" with cid=0xff (broadcast) and TLVs:
Dec 13 04:15:41 nemi kernel: [23895.361124] usb 2-2: [0x01] (2) 02 00                                            ..
Dec 13 04:15:41 nemi kernel: [23895.361131] usb 2-2: [0x12] (1) 04                                               .


received an unsolicited QMI_WDS status message (sent to all clients)
with the new IPv4 connected state

Dec 13 04:15:41 nemi kernel: [23895.362343] usb 2-2: QMI_WDS request: msg 0x0001 (len 20) from "control point" with cid=0x03 and TLVs:
Dec 13 04:15:41 nemi kernel: [23895.362352] usb 2-2: [0x10] (1) 01                                               .
Dec 13 04:15:41 nemi kernel: [23895.362360] usb 2-2: [0x15] (1) 01                                               .

request speed and system change updates.  Nice to know, but not strictly
required.

Dec 13 04:15:41 nemi kernel: [23895.464623] usb 2-2: QMI_WDS response: msg 0x0001 (len 19) from "service" with cid=0x03 and TLVs:
Dec 13 04:15:41 nemi kernel: [23895.464635] usb 2-2: [0x02] (4) SUCCESS (0x0000)

received ack to the last request.  That's the last we'll see until
something changes.



Bjørn

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 2/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific
       [not found]           ` <87vcplcbj3.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>
@ 2011-12-13 10:39             ` Sergei Shtylyov
  0 siblings, 0 replies; 16+ messages in thread
From: Sergei Shtylyov @ 2011-12-13 10:39 UTC (permalink / raw
  To: Bjørn Mork
  Cc: Joe Perches, netdev-u79uwXL29TY76Z2rM5mHXA,
	linux-usb-u79uwXL29TY76Z2rM5mHXA

Hello.

On 13-12-2011 8:59, Bjørn Mork wrote:

>> On Tue, 2011-12-13 at 05:33 +0100, Bjorn Mork wrote:
>>> diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c
>> []
>>> @@ -211,8 +211,12 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
>> []
>>> -				dev_dbg(&intf->dev, "slave class %u\n",
>> []
>>> +				dev_dbg(&intf->dev, "xslave class %u\n",
>>
>> Why change the output from slave to xslave?
>
> That was unintentional.  Sorry.  Don't understand how checkpatch could
> have missed that :-)

    Don't understand what checkpatch.pl would have to do with that. :-)

WBR, Sergei
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 1/3] option: Removing one bogus and adding some new Huawei combinations
  2011-12-13  4:33   ` [PATCH 1/3] option: Removing one bogus and adding some new Huawei combinations Bjorn Mork
@ 2011-12-13 17:16     ` Greg KH
  0 siblings, 0 replies; 16+ messages in thread
From: Greg KH @ 2011-12-13 17:16 UTC (permalink / raw
  To: Bjorn Mork; +Cc: netdev, linux-usb

On Tue, Dec 13, 2011 at 05:33:02AM +0100, Bjorn Mork wrote:
> From: Bjørn Mork <bjorn@mork.no>
> 
> Huawei use the product code HUAWEI_PRODUCT_E353 (0x1506) for a
> number of different devices, which each can appear with a number
> of different descriptor sets.  Different types of interfaces
> can be identified by looking at the subclass and protocol fields
> 
> Subclass 1 protocol 8 is actually the data interface of a CDC
> ECM set, with subclass 1 protocol 9 as the control interface.
> Neither support serial data communcation, and cannot therefore
> be supported by this driver.
> 
> At the same time, add a few other sets which appear if the
> device is configured in "Windows mode" using this modeswitch
> message:
> 55534243000000000000000000000011060000000100000000000000000000
> 
> Signed-off-by: Bjørn Mork <bjorn@mork.no>
> ---
>  drivers/usb/serial/option.c |    4 +++-
>  1 files changed, 3 insertions(+), 1 deletions(-)

I'll take this one now through the usb tree as it's relevant there.

greg k-h

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
       [not found]     ` <1323750784-32608-4-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
@ 2011-12-14 17:08       ` Dan Williams
       [not found]         ` <1323882501.2077.3.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>
  0 siblings, 1 reply; 16+ messages in thread
From: Dan Williams @ 2011-12-14 17:08 UTC (permalink / raw
  To: Bjorn Mork
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA

On Tue, 2011-12-13 at 05:33 +0100, Bjorn Mork wrote:
> From: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
> 
> Some WWAN LTE/3G devices based on chipsets from Qualcomm provide
> near standard CDC ECM interfaces in addition to the usual serial
> interfaces.  But they cannot be fully configured using AT commands
> over a serial interface.  It is necessary to speak the proprietary
> Qualcomm Messaging Interface (QMI) protocol to the device to
> enable the ethernet proxy functionality.
> 
> This protocol is exposed by the device through the standard CDC
> USB_CDC_SEND_ENCAPSULATED_COMMAND and USB_CDC_GET_ENCAPSULATED_RESPONSE
> control messages, and using USB_CDC_NOTIFY_RESPONSE_AVAILABLE
> notifications.
> 
> This driver adds a usbnet minidriver with very rudimentary QMI
> support.
> 
> The layout of the usb interfaces can vary a lot within a single
> device, depending on which "modeswitch" command has been used to
> switch it from usb-storage mode.  Control and data interfaces
> can be combined, or they can look like standard CDC ECM interfaces
> with the appropriate descriptors but with vendor specific class
> code. This driver is attempting to support them all, by reusing
> as much as possible of the existing usbnet infrastructure.

So I thought the protocol acronym stood for "Qualcomm MSM/Modem
Interface"  (see
https://www.codeaurora.org/gitweb/quic/le/?p=kernel/msm.git;a=commitdiff;h=5f6f87b51184e13b6c493012de787895d5d18765)

In any case, great work here.  But I'm a bit concerned about how all
this should fit together.  QMI is a huge protocol that does everything a
modem would ever want to do, and we'd likely want to be able to speak
QMI from userspace to the modem too so that modem managers can expose
the full functionality of the modems.  For example Gobi modems have a
very minimal AT command set and most of the functionality is exposed
over QMI or DM.  That's why the Qualcomm GobiNet driver also
exposed /dev/qmi for userspace access.

Second, does the modem actually respond to DHCP over the ECM interface?
If you take a look at the Android rmnet code (which is similar to your
driver) you'll see they extract the IP address and DNS details from the
QMI response.  Do we need to do that here too?

Dan

> Signed-off-by: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
> ---
>  drivers/net/usb/Kconfig         |   13 +
>  drivers/net/usb/Makefile        |    2 +
>  drivers/net/usb/qmi.c           |  730 +++++++++++++++++++++++++++++++++++++++
>  drivers/net/usb/qmi.h           |  101 ++++++
>  drivers/net/usb/qmi_wwan_core.c |  206 +++++++++++
>  5 files changed, 1052 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/net/usb/qmi.c
>  create mode 100644 drivers/net/usb/qmi.h
>  create mode 100644 drivers/net/usb/qmi_wwan_core.c
> 
> diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
> index 2335761..eed9a37 100644
> --- a/drivers/net/usb/Kconfig
> +++ b/drivers/net/usb/Kconfig
> @@ -461,4 +461,17 @@ config USB_VL600
>  
>  	  http://ubuntuforums.org/showpost.php?p=10589647&postcount=17
>  
> +config USB_NET_QMI_WWAN
> +	tristate "USB-to-WWAN driver for QMI based 3G and LTE modems"
> +	depends on USB_NET_CDCETHER
> +	help
> +	  Support WWAN LTE/3G devices based on chipsets from Qualcomm,
> +	  using the Qualcomm Messaging Interface (QMI) protocol to
> +	  configure the device.
> +
> +	  Note that it is still necessary to configure some aspects of
> +	  the device using AT commands on a tty.  Select the option
> +	  driver to get this support.
> +
> +
>  endmenu
> diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
> index c203fa2..ed715da 100644
> --- a/drivers/net/usb/Makefile
> +++ b/drivers/net/usb/Makefile
> @@ -29,4 +29,6 @@ obj-$(CONFIG_USB_SIERRA_NET)	+= sierra_net.o
>  obj-$(CONFIG_USB_NET_CX82310_ETH)	+= cx82310_eth.o
>  obj-$(CONFIG_USB_NET_CDC_NCM)	+= cdc_ncm.o
>  obj-$(CONFIG_USB_VL600)		+= lg-vl600.o
> +obj-$(CONFIG_USB_NET_QMI_WWAN)	+= qmi_wwan.o
> +qmi_wwan-objs := qmi_wwan_core.o qmi.o
>  
> diff --git a/drivers/net/usb/qmi.c b/drivers/net/usb/qmi.c
> new file mode 100644
> index 0000000..b4f6a4b
> --- /dev/null
> +++ b/drivers/net/usb/qmi.c
> @@ -0,0 +1,730 @@
> +/*
> + * Copyright (c) 2011  Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
> + * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + */
> +
> +/*
> + * Dealing with devices using Qualcomm Messaging Interface (QMI) for
> + * configuration.  Full documentation of the protocol can be obtained
> + * from http://developer.qualcomm.com/
> + *
> + * Some of this code may be inspired by code from the Qualcomm Gobi
> + * 2000 and Gobi 3000 drivers, available at http://www.codeaurora.org/
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/usb.h>
> +#include <linux/usb/cdc.h>
> +#include "qmi.h"
> +
> +
> +/* QMI protocol debug output */
> +static int qmi_debug;
> +module_param(qmi_debug, int, 0);
> +MODULE_PARM_DESC(qmi_debug, "Enable QMI protocol debugging");
> +
> +/* find and return a pointer to the requested tlv */
> +struct qmi_tlv *qmi_get_tlv(u8 type, u8 *buf, size_t len)
> +{
> +	u8 *p;
> +	struct qmi_tlv *t;
> +
> +	for (p = buf; p < buf + len; p += t->len + sizeof(struct qmi_tlv)) {
> +		t = (struct qmi_tlv *)p;
> +		if (t->type == type)
> +			return (p + t->len <= buf + len) ? t : NULL;
> +	}
> +	return NULL;
> +}
> +
> +
> +/* TLV 0x02 is status: return a negative QMI error code or 0 if OK */
> +int qmi_verify_status_tlv(u8 *buf, size_t len)
> +{
> +	struct qmi_tlv *tlv = (void *)qmi_get_tlv(0x02, buf, len);
> +	struct qmi_tlv_response_data *r = (void *)tlv->bytes;
> +
> +	if (!tlv || tlv->len != 2 * sizeof(__le16))
> +		return -1; /* QMI_ERR_MALFORMED_MSG */
> +
> +	return r->error ? -r->code : 0;
> +}
> +
> +static char *decode_trans_flags(const u8 flags)
> +{
> +	switch (flags) {
> +	case 0: return "request";
> +	case 2:	return "response";
> +	case 4:	return "indication";
> +	case 6:	return "reserved";
> +	default: return "invalid";
> +	}
> +}
> +
> +static char *qmi_system(u8 system)
> +{
> +	switch (system) {
> +	case 0:	return "QMI_CTL";
> +	case 1:	return "QMI_WDS";
> +	case 2:	return "QMI_DMS";
> +	default: return "(unknown)";
> +	}
> +}
> +
> +
> +/* debug print TLVs from a QMI message */
> +static void qmi_tlv_dump(struct qmi_state *qmi, u8 *data, size_t len, u8 system, __le16 msgid)
> +{
> +	u8 *p;
> +	struct qmi_tlv *t;
> +	char linebuf[100];
> +
> +	for (p = data; p < data + len; p += t->len + sizeof(struct qmi_tlv)) {
> +		t = (struct qmi_tlv *)p;
> +
> +		/* mark an empty string */
> +		linebuf[0] = 0;
> +		if (t->type == 0x02) { /* status response */
> +			struct qmi_tlv_response_data *r = (void *)t->bytes;
> +			snprintf(linebuf, sizeof(linebuf), "%s (0x%04x)",
> +				r->error ? "FAILED" : "SUCCESS", r->code);
> +		} else {
> +			switch (system) {
> +			case QMI_WDS:
> +				switch (msgid) {
> +				case 0x0001:
> +					switch (t->type) {
> +					case 0x16:
> +						snprintf(linebuf, sizeof(linebuf), "tx: %d bps, rx: %d bps",
> +							*(__le32 *)t->bytes, *(__le32 *)(t->bytes + 4));
> +					}
> +				}
> +				break;
> +			case QMI_DMS:
> +				switch (msgid) {
> +				case 0x002b:
> +					if (t->type == 0x11 || t->type == 0x12)
> +						snprintf(linebuf, sizeof(linebuf),
> +							"PIN%d status=%d, %d verify retries left, %d unblock retries left",
> +							t->type & 0x3, t->bytes[0], t->bytes[1], t->bytes[2]);
> +				}
> +			}
> +		}
> +
> +		/* do a default byte dump if the linebuf is empty */
> +		if (linebuf[0] == 0)
> +			hex_dump_to_buffer(t->bytes, t->len, 16, 1, linebuf, sizeof(linebuf), true);
> +
> +		dev_info(&qmi->dev->dev, "[0x%02x] (%d) %s\n", t->type, t->len, linebuf);
> +	}
> +}
> +
> +/* verify QMUX message header and return pointer to the (first) message if OK */
> +static struct qmi_msg *qmi_qmux_verify(u8 *data, size_t len)
> +{
> +	struct qmux *h =  (void *)data;
> +	struct qmi_msg *m;
> +	ssize_t hsize = sizeof(struct qmux);
> +
> +	if (len < sizeof(struct qmux) || /* short packet */
> +		h->tf != 0x01 || h->len != (len - 1)) /* invalid QMUX packet! */
> +		return NULL;
> +
> +	/* tid has a different size for QMI_CTL for some fucking stupid reason */
> +	if (h->service == QMI_CTL)
> +		hsize--;
> +
> +	m = (struct qmi_msg *)(data + hsize);
> +
> +	/* insanity checking before any further dereferencing... */
> +	if (hsize + sizeof(struct qmi_msg) + m->len > len)
> +		return NULL;
> +
> +	return m;
> +}
> +
> +/* debug print a QMUX packet */
> +static void qmi_dump_qmux(struct qmi_state *qmi, u8 *data, size_t len)
> +{
> +	struct qmux *h =  (void *)data;
> +	struct qmi_msg *m;
> +
> +	m = qmi_qmux_verify(data, len);
> +	if (!m) {
> +		dev_info(&qmi->dev->dev, "invalid QMUX packet (%zu bytes)\n", len);
> +		return;
> +	}
> +
> +	/* dump parts of the QMUX header and the message ID */
> +	dev_info(&qmi->dev->dev, "%s %s: msg 0x%04x (len %d) from \"%s\" with cid=0x%02x%s and TLVs:\n",
> +		qmi_system(h->service),
> +		decode_trans_flags(h->service == QMI_CTL ? h->flags << 1 : h->flags),
> +		m->msgid,
> +		h->len,
> +		h->ctrl & 0x80 ? "service" : "control point",
> +		h->qmicid, h->qmicid == 0xff ? " (broadcast)" : "");
> +
> +	/* dump the TLVs */
> +	qmi_tlv_dump(qmi, m->tlv, m->len, h->service, m->msgid);
> +}
> +
> +static int qmi_dms_parse(struct qmi_state *qmi, struct qmi_msg *m)
> +{
> +	int status;
> +	struct qmi_tlv *tlv;
> +
> +	if (!m)
> +		return -1;
> +
> +	/* check and save status TLV */
> +	status = qmi_verify_status_tlv(m->tlv, m->len);
> +
> +	/* no QMI_DMS messages requires parsing unless status is OK */
> +	if (status < 0)
> +		return status;
> +
> +	switch (m->msgid) {
> +	case 0x002b: /* QMI_DMS_UIM_GET_PIN_STATUS */
> +		if (status == 0) {
> +			/* PIN1 status */
> +			tlv = qmi_get_tlv(0x11, m->tlv, m->len);
> +			if (tlv && tlv->len == 3) {
> +				switch (tlv->bytes[0]) {
> +				case 2: /* PIN enabled, verified */
> +				case 3: /* PIN disabled */
> +					qmi->flags |= QMI_FLAG_PINOK;
> +				}
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int qmi_ctl_parse(struct qmi_state *qmi, struct qmi_msg *m)
> +{
> +	int status;
> +	struct qmi_tlv *tlv;
> +
> +	if (!m)
> +		return -1;
> +
> +	/* check and save status TLV */
> +	status = qmi_verify_status_tlv(m->tlv, m->len);
> +
> +	if (status < 0)
> +		return status;
> +
> +	/* TLVs are message dependent */
> +	switch (m->msgid) {
> +	case 0x0022: /* allocate cid */
> +		/* TLV 0x01 is system + cid */
> +		tlv = qmi_get_tlv(0x01, m->tlv, m->len);
> +
> +		/* can only save CID if there is a slot for the subsystem */
> +		if (tlv && tlv->len == 2 && tlv->bytes[0] < sizeof(qmi->cid))
> +			qmi->cid[tlv->bytes[0]] = tlv->bytes[1];
> +		else
> +			return -1;
> +	}
> +
> +	return status;
> +}
> +
> +static int qmi_wds_parse(struct qmi_state *qmi, struct qmi_msg *m)
> +{
> +	int status;
> +	struct qmi_tlv *tlv;
> +
> +	if (!m)
> +		return -1;
> +
> +	/* check and save status TLV */
> +	status = qmi_verify_status_tlv(m->tlv, m->len);
> +
> +	if (m->msgid == 0x0020 && -status == 0x001A) { /* QMI_ERR_NO_EFFECT */
> +		memset(qmi->handle, 0xff, sizeof(qmi->handle)); /* global handle */
> +		return 0;
> +	}
> +
> +	if (status < 0)
> +		return status;
> +
> +	switch (m->msgid) {
> +	case 0x0020: /* QMI_WDS_START_NETWORK_INTERFACE */
> +		/* TLV 0x01 is a 4 byte packet data handle we need to keep */
> +		tlv = qmi_get_tlv(0x01, m->tlv, m->len);
> +		if (tlv && tlv->len == sizeof(qmi->handle))
> +			memcpy(qmi->handle, tlv->bytes, sizeof(qmi->handle));
> +
> +		break;
> +
> +	case 0x0022: /* QMI_WDS_GET_PKT_SRVC_STATUS */
> +		/* TLV 0x01 is a 1 byte connection status */
> +		tlv = qmi_get_tlv(0x01, m->tlv, m->len);
> +		if (tlv && tlv->len == 1)
> +			qmi->wds_status = tlv->bytes[0];
> +
> +	}
> +
> +	return status;
> +}
> +
> +static void qmi_read_callback(struct urb *urb)
> +{
> +	struct qmi_state *qmi = (void *)urb->context;
> +
> +	if (urb->status == 0) {
> +		struct qmux *h = (void *)urb->transfer_buffer;
> +		struct qmi_msg *m = qmi_qmux_verify(urb->transfer_buffer, urb->actual_length);
> +
> +		switch (h->service) {
> +		case QMI_CTL:
> +			qmi_ctl_parse(qmi, m);
> +			break;
> +		case QMI_WDS:
> +			qmi_wds_parse(qmi, m);
> +			break;
> +		case QMI_DMS:
> +			qmi_dms_parse(qmi, m);
> +		}
> +
> +		if (qmi_debug)
> +			qmi_dump_qmux(qmi, urb->transfer_buffer, urb->actual_length);
> +	}
> +}
> +
> +static int qmi_recv_sync(struct qmi_state *qmi)
> +{
> +	int status;
> +
> +	status = usb_control_msg(qmi->dev,
> +			       usb_rcvctrlpipe(qmi->dev, 0),
> +			       USB_CDC_GET_ENCAPSULATED_RESPONSE, USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
> +			       0, qmi->intfnr,
> +			       qmi->rcvbuf, QMI_BUFLEN, 1000);
> +
> +	if (status > 0 && qmi_debug)
> +		qmi_dump_qmux(qmi, qmi->rcvbuf, status);
> +
> +	return status;
> +}
> +
> +static int qmi_send_sync(struct qmi_state *qmi, unsigned char *msg, size_t len)
> +{
> +	int status;
> +
> +	status = usb_control_msg(qmi->dev,
> +				usb_sndctrlpipe(qmi->dev, 0),
> +				USB_CDC_SEND_ENCAPSULATED_COMMAND, USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
> +				0, qmi->intfnr,
> +				msg, len, 1000);
> +
> +	if (qmi_debug) /* NOTE: regardless of status! */
> +		qmi_dump_qmux(qmi, msg, len);
> +
> +	return status;
> +}
> +
> +/* send message and wait for the reply - returns pointer to reply message */
> +struct qmi_msg *qmi_send_and_wait_for_ack(struct qmi_state *qmi, unsigned char *data, size_t len, int timeout)
> +{
> +	int ret;
> +	u8 system;
> +	__le16 msgid;
> +	struct qmi_msg *msg = qmi_qmux_verify(data, len);
> +	struct qmux *h = (void *)data;
> +
> +	/* should never happen? */
> +	if (!msg)
> +		return NULL;
> +	msgid = msg->msgid;
> +	system = h->service;
> +
> +	/* flush any pending messages  */
> +	do {
> +		ret = qmi_recv_sync(qmi);
> +	} while (ret > 0);
> +
> +	/* send our message */
> +	ret = qmi_send_sync(qmi, data, len);
> +	if (ret != len)
> +		return NULL;
> +
> +	/* wait a while for the (correct) reply */
> +	do {
> +		ret = qmi_recv_sync(qmi);
> +		msg = qmi_qmux_verify(qmi->rcvbuf, ret);
> +		if (msg) {
> +			h = (struct qmux *)qmi->rcvbuf;
> +			if (msg->msgid == msgid && h->service == system) /* found it */
> +				return msg;
> +
> +		}
> +		if (timeout--)
> +			msleep(100);
> +	} while (timeout > 0);
> +
> +	/* no reply */
> +	return NULL;
> +}
> +
> +/* add a TLV to an existing QMUX */
> +static size_t qmi_add_tlv(u8 *buf, size_t buflen, u8 type, const u8 *tlvdata, size_t datalen)
> +{
> +	struct qmux *h =  (void *)buf;
> +	struct qmi_msg *m = (void *)(buf + sizeof(struct qmux) - (h->service == QMI_CTL));
> +	struct qmi_tlv *tlv = (void *)(buf + h->len + 1);
> +	size_t tlvlen = sizeof(struct qmi_tlv) + datalen;
> +
> +	if (buflen < h->len + tlvlen - 1)
> +		return -1;
> +
> +	tlv->type = type;
> +	tlv->len = datalen;
> +	memcpy(tlv->bytes, tlvdata, datalen);
> +	h->len += tlvlen;
> +	m->len += tlvlen;
> +	return h->len + 1;
> +}
> +
> +/* get a new transaction id */
> +static __le16 new_tid(void)
> +{
> +	static __le16 tid;
> +	return ++tid;
> +}
> +
> +/* assemble a complete QMUX packet with one QMI message */
> +static size_t qmi_create_msg(char *buf, size_t buflen, u8 cid, u8 system, __le16 msgid)
> +{
> +	struct qmux *h =  (void *)buf;
> +	struct qmi_msg *m;
> +	size_t hsize = sizeof(struct qmux);
> +
> +	/* this is weird, but it seems as though QMI_CTL uses 1 byte
> +	   transaction ids while the other systems use 2 bytes as specified
> +	   in the standard */
> +	if (system == QMI_CTL)
> +		hsize--;
> +
> +	/* sanity check */
> +	if (buflen < hsize + sizeof(struct qmi_msg))
> +		return -1;
> +
> +	h->tf = 1;     /* always 1 */
> +	h->ctrl = 0;   /* 0 - control point */
> +	h->service = system;
> +	h->qmicid = cid;
> +	h->flags = 0;  /* 0 - request */
> +
> +	/* see comment above */
> +	if (system == QMI_CTL)
> +		h->tid.b = new_tid() & 0xff;
> +	else
> +		h->tid.w = new_tid();
> +
> +	m = (struct qmi_msg *)(buf + hsize);
> +	m->msgid = msgid;
> +
> +	h->len = hsize + sizeof(struct qmi_msg)  - 1;
> +	m->len = 0;
> +	return h->len + 1;
> +}
> +
> +/* QMI_CTL msg 0x0022 is "request cid", TLV 0x01 is system */
> +static int qmi_ctl_request_cid(struct qmi_state *qmi, u8 system)
> +{
> +	size_t len;
> +	u8 tlvdata[1] = { system };
> +	char buf[32];
> +
> +	/* return immediately if a CID is already allocated */
> +	if (qmi->cid[system])
> +		return qmi->cid[system];
> +
> +	/* else request a new one */
> +	len = qmi_create_msg(buf, sizeof(buf), 0, QMI_CTL, 0x0022);
> +	len = qmi_add_tlv(buf, sizeof(buf), 0x01, tlvdata, sizeof(tlvdata));
> +
> +	/* send message */
> +	return qmi_ctl_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
> +}
> +
> +/* QMI_WDS msg 0x0001 is "" */
> +static int qmi_wds_report(struct qmi_state *qmi)
> +{
> +	size_t len;
> +	u8 cid = qmi->cid[QMI_WDS];
> +	u8 tlvdata[1] = { 1 };	/* generic "enable" */
> +	char buf[32];
> +
> +	/* enable connection status reports */
> +	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_WDS, 0x0001);
> +	len = qmi_add_tlv(buf, sizeof(buf), 0x10, tlvdata, sizeof(tlvdata));	/* current channel rate */
> +	len = qmi_add_tlv(buf, sizeof(buf), 0x15, tlvdata, sizeof(tlvdata));	/* current data bearer tech */
> +
> +	/* send message */
> +	return qmi_wds_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
> +}
> +
> +/* QMI_WDS msg 0x0020 is "" */
> +static int qmi_wds_start(struct qmi_state *qmi)
> +{
> +	size_t len;
> +	u8 cid = qmi->cid[QMI_WDS];
> +	char buf[32];
> +
> +	if (!cid)
> +		return -1;
> +
> +	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_WDS, 0x0020);
> +
> +	/* send message */
> +	return qmi_wds_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
> +}
> +
> +/* QMI_WDS msg 0x0021 is "" */
> +static int qmi_wds_stop(struct qmi_state *qmi)
> +{
> +	size_t len;
> +	u8 cid = qmi->cid[QMI_WDS];
> +	char buf[32];
> +
> +	if (!cid)
> +		return -1;
> +
> +	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_WDS, 0x0021);
> +	len = qmi_add_tlv(buf, sizeof(buf), 0x01, qmi->handle, sizeof(qmi->handle));	 /* packet data handle */
> +
> +	return qmi_wds_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
> +}
> +
> +/* QMI_WDS msg 0x0022 is "" */
> +static int qmi_wds_status(struct qmi_state *qmi)
> +{
> +	int ret;
> +	size_t len;
> +	u8 cid = qmi->cid[QMI_WDS];
> +	char buf[32];
> +
> +	if (!cid)
> +		return -1;
> +
> +	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_WDS, 0x0022);
> +
> +	ret = qmi_wds_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
> +	if (ret < 0)
> +		return ret;
> +
> +	return qmi->wds_status;
> +}
> +
> +/* QMI_CTL msg 0x0023 is "release cid", TLV 0x01 is system + cid */
> +static int qmi_ctl_release_cid(struct qmi_state *qmi, u8 system)
> +{
> +	char buf[32];
> +	size_t len = qmi_create_msg(buf, sizeof(buf), 0, QMI_CTL, 0x0023);
> +
> +	if (system < sizeof(qmi->cid)) {
> +		u8 tlvdata[2] = { system, qmi->cid[system] };
> +		len = qmi_add_tlv(buf, sizeof(buf), 0x01, tlvdata, sizeof(tlvdata));
> +
> +	} else {
> +		return -1;
> +	}
> +
> +	/* no need to parse result - cant reuse CID in any case */
> +	qmi_send_and_wait_for_ack(qmi, buf, len, 30);
> +	qmi->cid[system] = 0;
> +
> +	return 1;
> +}
> +
> +static int qmi_dms_verify_pin(struct qmi_state *qmi)
> +{
> +	int ret;
> +	size_t len;
> +	u8 cid = qmi->cid[QMI_DMS];
> +	char buf[32];
> +
> +	if (!cid)
> +		return -1;
> +
> +	len = qmi_create_msg(buf, sizeof(buf), cid, QMI_DMS, 0x002b);
> +	ret = qmi_dms_parse(qmi, qmi_send_and_wait_for_ack(qmi, buf, len, 30));
> +	return qmi->flags && QMI_FLAG_PINOK;
> +}
> +
> +
> +/* ----- exported API ----- */
> +
> +int qmi_reset(struct qmi_state *qmi)
> +{
> +	/* NOTE:  We do NOT clear the allocated CIDs! */
> +	qmi->state = QMI_STATE_INIT;
> +	qmi->wds_status = 0;
> +	qmi->flags = 0;
> +	return 1;
> +}
> +
> +int qmi_disconnect(struct qmi_state *qmi)
> +{
> +	qmi->flags &= ~QMI_FLAG_RECV;         /* disable async receiving */
> +	qmi_wds_stop(qmi);
> +	qmi_wds_status(qmi);
> +	qmi->state = QMI_STATE_DOWN;
> +	return 1;
> +}
> +
> +/* receive a QMUX can be called in interrupt context! */
> +int qmi_recv(struct qmi_state *qmi)
> +{
> +	if (qmi->flags & QMI_FLAG_RECV) /* async receiving enabled? */
> +		return usb_submit_urb(qmi->urb, GFP_ATOMIC);
> +	else
> +		return 0;
> +}
> +
> +/*
> + * return current connection state with side effects: will run through
> + *  the states from INIT to CONN if possible
> + */
> +int qmi_conn_state(struct qmi_state *qmi)
> +{
> +	int ret;
> +
> +	dev_dbg(&qmi->dev->dev, ".state=%d, .cid[QMI_WDS]=0x%02x, .cid[QMI_DMS]=0x%02x, .flags=0x%08lx, .handle=0x%08x\n",
> +		qmi->state,
> +		qmi->cid[QMI_WDS],
> +		qmi->cid[QMI_DMS],
> +		qmi->flags,
> +		*(__le32 *)qmi->handle);
> +
> +	switch (qmi->state) {
> +	/* these states don't trigger any action */
> +	case QMI_STATE_DOWN:
> +		return 1;
> +	case QMI_STATE_UP:
> +		qmi->flags |= QMI_FLAG_RECV;  /* enable async receiving  */
> +		return 0;
> +	case QMI_STATE_ERR:
> +		return -1;
> +
> +	case QMI_STATE_INIT:
> +		qmi->flags &= ~QMI_FLAG_RECV;  /* disable async receiving */
> +
> +		/* request CIDs */
> +		if (qmi_ctl_request_cid(qmi, QMI_WDS) < 0 || qmi_ctl_request_cid(qmi, QMI_DMS) < 0) {
> +			qmi->state = QMI_STATE_ERR; /* FATAL - no way out except for unplugging the device */
> +			return -1;
> +		}
> +
> +		/* stay in this state until PIN code is entered */
> +		if (!qmi_dms_verify_pin(qmi)) {
> +			dev_notice(&qmi->dev->dev, "please enter PIN code using AT+CPIN=\"xxxx\" on a ttyUSB0 device\n");
> +			return 1;
> +		}
> +		/* check status */
> +		switch (qmi_wds_status(qmi)) {
> +		case 1: /* DISCONNECTED - attempt to connect */
> +			ret = qmi_wds_start(qmi);
> +			if (ret < 0) {
> +				dev_notice(&qmi->dev->dev, "connection failed: 0x%04x\n", -ret);
> +				break;
> +			}
> +
> +			/* fallthrough */
> +		case 2: /* CONNECTED - note that */
> +			qmi_wds_report(qmi); /* enable status reports */
> +			qmi->state = QMI_STATE_UP;
> +
> +		/* do nothing for the other states */
> +		}
> +		break;
> +
> +	default:
> +		dev_dbg(&qmi->dev->dev, "%s() unknown state=%d\n", __func__, qmi->state);
> +	}
> +	return 1;  /* not connected yet - */
> +}
> +
> +
> +/* allocate and initiate a QMI state device */
> +struct qmi_state *qmi_init(struct usb_interface *intf)
> +{
> +	struct qmi_state *qmi = kmalloc(sizeof(struct qmi_state), GFP_KERNEL);
> +
> +	if (!qmi)
> +		return NULL;
> +
> +	memset(qmi, 0, sizeof(*qmi));
> +
> +	/*
> +	 * keep track of the device receiving the control messages and the
> +	 * number of the CDC (like) control interface which is our target.
> +	 * Note that the interface might be disguised as vendor specific,
> +	 * and be a combined CDC control/data interface
> +	 */
> +	qmi->dev = interface_to_usbdev(intf);
> +	qmi->intfnr = intf->cur_altsetting->desc.bInterfaceNumber;
> +	/* FIXME: it would be useful to verify that this interface actually talks CDC */
> +
> +	/* create async receive URB */
> +	qmi->urb = usb_alloc_urb(0, GFP_KERNEL);
> +	if (!qmi->urb) {
> +		kfree(qmi);
> +		return NULL;
> +	}
> +
> +	/* pre-allocate buffer for receiving */
> +	qmi->rcvbuf = kmalloc(QMI_BUFLEN, GFP_KERNEL);
> +
> +	if (!qmi->rcvbuf) {
> +		usb_free_urb(qmi->urb);
> +		kfree(qmi);
> +		return NULL;
> +	}
> +
> +	/* usb control setup */
> +	qmi->setup.bRequestType = USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE;
> +	qmi->setup.bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
> +	qmi->setup.wValue = 0; /* zero */
> +	qmi->setup.wIndex = qmi->intfnr;
> +	qmi->setup.wLength = QMI_BUFLEN;
> +
> +	/* prepare the async receive URB */
> +	usb_fill_control_urb(qmi->urb, qmi->dev,
> +			usb_rcvctrlpipe(qmi->dev, 0),
> +			(char *)&qmi->setup,
> +			qmi->rcvbuf,
> +			QMI_BUFLEN,
> +			qmi_read_callback, qmi);
> +
> +	return qmi;
> +}
> +
> +/* disable and free a QMI state device */
> +int qmi_exit(struct qmi_state *qmi)
> +{
> +	int i;
> +
> +	/* release all CIDs - may need to sleep */
> +	for (i = 1; i < sizeof(qmi->cid); i++)
> +		qmi_ctl_release_cid(qmi, i);
> +
> +	/* free all buffers */
> +	kfree(qmi->rcvbuf);
> +
> +	/* free URBs */
> +	usb_free_urb(qmi->urb);
> +
> +	kfree(qmi);
> +
> +	return 0;
> +}
> diff --git a/drivers/net/usb/qmi.h b/drivers/net/usb/qmi.h
> new file mode 100644
> index 0000000..bff94f1
> --- /dev/null
> +++ b/drivers/net/usb/qmi.h
> @@ -0,0 +1,101 @@
> +/*
> + * Copyright (c) 2011  Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
> + * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + */
> +
> +/*
> + * Dealing with devices using Qualcomm Messaging Interface (QMI) for
> + * configuration.  Full documentation of the protocol can be obtained
> + * from http://developer.qualcomm.com/
> + *
> + * Some of this code may be inspired by code from the Qualcomm Gobi
> + * 2000 and Gobi 3000 drivers, available at http://www.codeaurora.org/
> + */
> +
> +#define QMI_BUFLEN 128
> +
> +enum qmi_device_state {
> +	QMI_STATE_INIT = 0, /* allocate CIDs, verify pin code */
> +	QMI_STATE_DOWN,    /* connection is down */
> +	QMI_STATE_UP,      /* connection is up */
> +	QMI_STATE_ERR,     /* fatal and final error state, e.g. no CID available - no way out of here! */
> +};
> +
> +/* the different QMI subsystems we are using */
> +enum qmi_subsystems {
> +	QMI_CTL = 0,
> +	QMI_WDS,
> +	QMI_DMS,
> +	QMI_SYSMAX,
> +};
> +
> +#define QMI_FLAG_RECV		0x00000001
> +#define QMI_FLAG_PINOK		0x00000002
> +
> +struct qmi_state {
> +	struct usb_device *dev;
> +	struct urb *urb;		/* receive urb */
> +	unsigned char *rcvbuf;		/* pre-allocated receive buffer */
> +	struct usb_ctrlrequest setup;	/* the receive setup - 8 bytes */
> +	unsigned long flags;		/* used for en/dis-abling functionality on the fly */
> +	u8 state;			/* for connection state machine */
> +	u8 wds_status;			/* current value for QMI_WDS message 0x0022 or 0 if uninitialized */
> +	__le16 intfnr;			/* keeping track of the interface we are referring to */
> +	u8 handle[4];			/* connection handle needed for disconnect */
> +	u8 cid[QMI_SYSMAX];		/* keeping track of cid per subsystem */
> +};
> +
> +/* combined QMUX and SDU */
> +struct qmux {
> +	u8 tf;		/* always 1 */
> +	__le16 len;	/* excluding tf */
> +	u8 ctrl;	/* b7: sendertype 1 => service, 0 => control point */
> +	u8 service;	/* 0 => QMI_CTL, 1 => QMI_WDS, .. */
> +	u8 qmicid;	/* client id or 0xff for broadcast */
> +	u8 flags;	/* always 0 for req */
> +	union {
> +		__le16 w;  /* each control point maintains a transaction id counter - non-zero */
> +		u8 b;	/* system QMI_CTL uses one byte transaction ids! */
> +	} tid;
> +	u8 msg[];	/* one or more messages */
> +} __packed;
> +
> +struct qmi_msg {
> +	__le16 msgid;
> +	__le16 len;
> +	u8 tlv[];	/* zero or more tlvs */
> +} __packed;
> +
> +struct qmi_tlv {
> +	u8 type;
> +	__le16 len;
> +	u8 bytes[];
> +} __packed;
> +
> +struct qmi_tlv_response_data {
> +	__le16 error;
> +	__le16 code;
> +} __packed;
> +
> +
> +/* reset state to INIT */
> +extern int qmi_reset(struct qmi_state *qmi);
> +
> +/* disconnect WWAN connection */
> +extern int qmi_disconnect(struct qmi_state *qmi);
> +
> +/* must be called whenever USB_CDC_NOTIFY_RESPONSE_AVAILABLE is received */
> +extern int qmi_recv(struct qmi_state *qmi);
> +
> +/* called periodically, whenever a state transition is wanted */
> +extern int qmi_conn_state(struct qmi_state *qmi);
> +
> +/* initialize */
> +extern struct qmi_state *qmi_init(struct usb_interface *intf);
> +
> +/* clean up */
> +extern int qmi_exit(struct qmi_state *qmi);
> diff --git a/drivers/net/usb/qmi_wwan_core.c b/drivers/net/usb/qmi_wwan_core.c
> new file mode 100644
> index 0000000..bf25e33
> --- /dev/null
> +++ b/drivers/net/usb/qmi_wwan_core.c
> @@ -0,0 +1,206 @@
> +/*
> + * Copyright (c) 2011  Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/ethtool.h>
> +#include <linux/mii.h>
> +#include <linux/usb.h>
> +#include <linux/usb/cdc.h>
> +#include <linux/usb/usbnet.h>
> +#include "qmi.h"
> +
> +/* overloading the cdc_state structure */
> +struct qmi_wwan_state {
> +	struct qmi_state		*qmi;
> +	struct usb_cdc_union_desc	*u;
> +	struct usb_cdc_ether_desc	*ether;
> +	struct usb_interface		*control;
> +	struct usb_interface		*data;
> +};
> +
> +
> +static void qmi_wwan_cdc_status(struct usbnet *dev, struct urb *urb)
> +{
> +	struct usb_cdc_notification	*event;
> +	struct qmi_wwan_state *info = (void *)&dev->data;
> +
> +	if (urb->actual_length < sizeof *event) {
> +		netdev_dbg(dev->net, "short status urb rcvd: %d bytes\n", urb->actual_length);
> +		return;
> +	}
> +
> +	event = urb->transfer_buffer;
> +	switch (event->bNotificationType) {
> +	case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
> +		qmi_recv(info->qmi);	/* request response */
> +		break;
> +	default:
> +		/* reuse usbnet() for handling other messages */
> +		usbnet_cdc_status(dev, urb);
> +	}
> +}
> +
> +static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *intf)
> +{
> +	int status;
> +	struct qmi_wwan_state *info = (void *)&dev->data;
> +
> +	status = usbnet_generic_cdc_bind(dev, intf);
> +	if (status < 0)
> +		return status;
> +
> +	/* initialize QMI data */
> +	info->qmi = qmi_init(intf);
> +	if (!info->qmi) {
> +		usbnet_cdc_unbind(dev, intf);
> +		return -1;
> +	}
> +	return 0;
> +}
> +
> +static int qmi_wwan_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
> +{
> +	int status;
> +	struct qmi_wwan_state *info = (void *)&dev->data;
> +
> +	memset(info, 0, sizeof(*info));
> +	status = usbnet_get_endpoints(dev, intf);
> +	if (status < 0)
> +		return status;
> +
> +	/* initialize QMI data */
> +	info->qmi = qmi_init(intf);
> +	if (!info->qmi)
> +		return -1;
> +
> +	info->control = intf;
> +	return 0;
> +}
> +
> +static void qmi_wwan_cdc_unbind(struct usbnet *dev, struct usb_interface *intf)
> +{
> +	struct qmi_wwan_state *info = (void *)&dev->data;
> +
> +	/* release our private structure */
> +	qmi_exit(info->qmi);
> +	info->qmi = NULL;
> +
> +	/* disconnect */
> +	usbnet_cdc_unbind(dev, intf);
> +}
> +
> +static int qmi_wwan_reset(struct usbnet *dev)
> +{
> +	struct qmi_wwan_state *info = (void *)&dev->data;
> +
> +	qmi_reset(info->qmi);
> +	return 1;
> +}
> +
> +static int qmi_wwan_stop(struct usbnet *dev)
> +{
> +	struct qmi_wwan_state *info = (void *)&dev->data;
> +
> +	qmi_disconnect(info->qmi);
> +	return 1;
> +}
> +
> +/* abusing check_connect for triggering QMI state transitions */
> +static int qmi_wwan_check_connect(struct usbnet *dev)
> +{
> +	struct qmi_wwan_state *info = (void *)&dev->data;
> +	return qmi_conn_state(info->qmi);
> +}
> +
> +static const struct driver_info	qmi_wwan_ecm_info = {
> +	.description =	"QMI speaking CDC ECM like wwan device",
> +	.flags =	 FLAG_WWAN,
> +	.bind =		 qmi_wwan_cdc_ecmlike_bind,
> +	.unbind =	 qmi_wwan_cdc_unbind,
> +	.status =	 qmi_wwan_cdc_status,
> +	.check_connect = qmi_wwan_check_connect,
> +	.stop =          qmi_wwan_stop,
> +	.reset =         qmi_wwan_reset,
> +};
> +
> +static const struct driver_info	qmi_wwan_info = {
> +	.description =	"QMI speaking wwan device",
> +	.flags =	 FLAG_WWAN,
> +	.bind =		 qmi_wwan_cdc_bind,
> +	.unbind =	 qmi_wwan_cdc_unbind,
> +	.status =	 qmi_wwan_cdc_status,
> +	.check_connect = qmi_wwan_check_connect,
> +	.stop =          qmi_wwan_stop,
> +	.reset =         qmi_wwan_reset,
> +};
> +
> +#define HUAWEI_VENDOR_ID	0x12D1
> +
> +static const struct usb_device_id products[] = {
> +{
> +	/* Qmi_Wwan E392, E398, ++? */
> +	.match_flags    =   USB_DEVICE_ID_MATCH_VENDOR
> +		 | USB_DEVICE_ID_MATCH_INT_INFO,
> +	.idVendor               = HUAWEI_VENDOR_ID,
> +	.bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
> +	.bInterfaceSubClass	= 1,
> +	.bInterfaceProtocol	= 9,
> +	.driver_info = (unsigned long)&qmi_wwan_ecm_info,
> +}, {
> +	/* Qmi_Wwan device id 1413 ++? */
> +	.match_flags    =   USB_DEVICE_ID_MATCH_VENDOR
> +		 | USB_DEVICE_ID_MATCH_INT_INFO,
> +	.idVendor               = HUAWEI_VENDOR_ID,
> +	.bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
> +	.bInterfaceSubClass	= 6,
> +	.bInterfaceProtocol	= 255,
> +	.driver_info = (unsigned long)&qmi_wwan_ecm_info,
> +}, {
> +	/* Qmi_Wwan E392, E398, ++? "Windows mode" */
> +	.match_flags    =   USB_DEVICE_ID_MATCH_VENDOR
> +		 | USB_DEVICE_ID_MATCH_INT_INFO,
> +	.idVendor               = HUAWEI_VENDOR_ID,
> +	.bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
> +	.bInterfaceSubClass	= 1,
> +	.bInterfaceProtocol	= 17,
> +	.driver_info = (unsigned long)&qmi_wwan_info,
> +},
> +{ },		/* END */
> +};
> +MODULE_DEVICE_TABLE(usb, products);
> +
> +static struct usb_driver qmi_wwan_driver = {
> +	.name =		"qmi_wwan",
> +	.id_table =	products,
> +	.probe =	usbnet_probe,
> +	.disconnect =	usbnet_disconnect,
> +	.suspend =	usbnet_suspend,
> +	.resume =	usbnet_resume,
> +	.reset_resume =	usbnet_resume,
> +	.supports_autosuspend = 1,
> +};
> +
> +static int __init qmi_wwan_init(void)
> +{
> +	/* we remap struct (cdc_state) so we should be compatible */
> +	BUILD_BUG_ON(sizeof(struct cdc_state) != sizeof(struct qmi_wwan_state));
> +
> +	return usb_register(&qmi_wwan_driver);
> +}
> +module_init(qmi_wwan_init);
> +
> +static void __exit qmi_wwan_exit(void)
> +{
> +	usb_deregister(&qmi_wwan_driver);
> +}
> +module_exit(qmi_wwan_exit);
> +
> +MODULE_AUTHOR("Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>");
> +MODULE_DESCRIPTION("Qualcomm Messaging Interface (QMI) WWAN driver");
> +MODULE_LICENSE("GPL");



--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
       [not found]         ` <1323882501.2077.3.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>
@ 2011-12-15 10:02           ` Bjørn Mork
  2011-12-15 17:52             ` Dan Williams
  0 siblings, 1 reply; 16+ messages in thread
From: Bjørn Mork @ 2011-12-15 10:02 UTC (permalink / raw
  To: Dan Williams
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA

Dan Williams <dcbw-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> writes:

> So I thought the protocol acronym stood for "Qualcomm MSM/Modem
> Interface"  (see
> https://www.codeaurora.org/gitweb/quic/le/?p=kernel/msm.git;a=commitdiff;h=5f6f87b51184e13b6c493012de787895d5d18765)

That's probably correct.  Don't know where I got the messaging part
from.  Should be corrected....

> In any case, great work here.  But I'm a bit concerned about how all
> this should fit together.  QMI is a huge protocol that does everything a
> modem would ever want to do, and we'd likely want to be able to speak
> QMI from userspace to the modem too so that modem managers can expose
> the full functionality of the modems.  For example Gobi modems have a
> very minimal AT command set and most of the functionality is exposed
> over QMI or DM.  That's why the Qualcomm GobiNet driver also
> exposed /dev/qmi for userspace access.

Yes, I believe some interface should be exported.  But my primary goal
was to make something that would just work as an ethernet device with a
minimum of external userspace dependencies.  And this modem seems to
have a fairly complete set of AT commands anyway.  Ideally I would want
to stick

 auto wwan0
 iface wwan0 inet dhcp

in my /etc/network/interface and not need any application for that to
work.  But I realize that I must enter a SIM PIN1 code first (unless
disabled), and that most users will want to configure a specific APN
(although the null APN "" most likely will work just fine).

But I agree that eventually the full QMI protocol should be made
available to userspace for other uses.  That should be fairly easy to do
if you just proxy the commands.  But I'm worring about the interface.
Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?
Wouldn't something like netlink have been more suitable?  How about
security and the ability to control privileges on a command to command
basis?  And are there users?  Note that even the Windows application
supplied with the modem uses AT commands for SMS sending/receiving etc.
The snoop I did showed a very minimal set of QMI commands.  These were
all Windows used for startup of the network interface (partly decoded by
me):


.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x01
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x02
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x03
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x04
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x05
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x06
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x07
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x08
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x09
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x000b
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x02 response
.tid = 0x00
.msgid = 0x0027
.len = 000000

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x0a
.msgid = 0x0021
.len = 0x0004
[0x01] (1)  ff	.

.tf = 0x01
.len = 0x0057
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x01 invalid
.tid = 0x0a
.msgid = 0x0021
.len = 0x004c
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (66)  0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 d1 0e 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00	..................................................................

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x0b
.msgid = 0x0022
.len = 0x0004
[0x01] (1)  02	.

.tf = 0x01
.len = 0x0017
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x01 invalid
.tid = 0x0b
.msgid = 0x0022
.len = 0x000c
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (2)  02 01	..

.tf = 0x01
.len = 0x000c
.ctrl = 0x00 control point
.service = 0x02
.qmicid = 0x01
.flags = 0x00 request
.tid = 0x0001
.msgid = 0x0021
.len = 000000

.tf = 0x01
.len = 0x002b
.ctrl = 0x80 service
.service = 0x02
.qmicid = 0x01
.flags = 0x02 response
.tid = 0x0001
.msgid = 0x0021
.len = 0x001f
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (21)  51 55 41 4c 43 4f 4d 4d 20 49 4e 43 4f 52 50 4f 52 41 54 45 44	QUALCOMM INCORPORATED

.tf = 0x01
.len = 0x000c
.ctrl = 0x00 control point
.service = 0x02
.qmicid = 0x01
.flags = 0x00 request
.tid = 0x0002
.msgid = 0x0024
.len = 000000

.tf = 0x01
.len = 0x0013
.ctrl = 0x80 service
.service = 0x02
.qmicid = 0x01
.flags = 0x02 response
.tid = 0x0002
.msgid = 0x0024
.len = 0x0007
[0x02] (4) FAILED (0x0025) QMI_ERR_UIM_NOT_INITIALIZED

.tf = 0x01
.len = 0x000c
.ctrl = 0x00 control point
.service = 0x02
.qmicid = 0x01
.flags = 0x00 request
.tid = 0x0003
.msgid = 0x0025
.len = 000000

.tf = 0x01
.len = 0x0029
.ctrl = 0x80 service
.service = 0x02
.qmicid = 0x01
.flags = 0x02 response
.tid = 0x0003
.msgid = 0x0025
.len = 0x001d
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x10] (1)  30	0
[0x11] (15)  38 36 30 39 39 39 30 30 30 30 32 33 37 30 37	860999000023707

.tf = 0x01
.len = 0x0010
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x0c
.msgid = 0x0023
.len = 0x0005
[0x01] (2)  02 01	..

.tf = 0x01
.len = 0x0017
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x01 invalid
.tid = 0x0c
.msgid = 0x0023
.len = 0x000c
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (2)  02 01	..

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x0d
.msgid = 0x0022
.len = 0x0004
[0x01] (1)  01	.

.tf = 0x01
.len = 0x0017
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x01 invalid
.tid = 0x0d
.msgid = 0x0022
.len = 0x000c
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (2)  01 01	..

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x0e
.msgid = 0x0020
.len = 0x0004
[0x01] (1)  00	.

.tf = 0x01
.len = 0x0017
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x01 invalid
.tid = 0x0e
.msgid = 0x0020
.len = 0x000c
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (2)  00 00	..

.tf = 0x01
.len = 0x000b
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x02 response
.tid = 0x00
.msgid = 0x0027
.len = 000000

.tf = 0x01
.len = 0x000b
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x02 response
.tid = 0x00
.msgid = 0x0027
.len = 000000

.tf = 0x01
.len = 0x000b
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x02 response
.tid = 0x00
.msgid = 0x0027
.len = 000000

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x0f
.msgid = 0x0022
.len = 0x0004
[0x01] (1)  02	.

.tf = 0x01
.len = 0x0017
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x01 invalid
.tid = 0x0f
.msgid = 0x0022
.len = 0x000c
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (2)  02 02	..

.tf = 0x01
.len = 0x000c
.ctrl = 0x00 control point
.service = 0x02
.qmicid = 0x02
.flags = 0x00 request
.tid = 0x0001
.msgid = 0x0022
.len = 000000

.tf = 0x01
.len = 0x0017
.ctrl = 0x80 service
.service = 0x02
.qmicid = 0x02
.flags = 0x02 response
.tid = 0x0001
.msgid = 0x0022
.len = 0x000b
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (1)  30	0

.tf = 0x01
.len = 0x000f
.ctrl = 0x00 control point
.service = 0x00
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x10
.msgid = 0x0022
.len = 0x0004
[0x01] (1)  01	.

.tf = 0x01
.len = 0x0017
.ctrl = 0x80 service
.service = 0x00
.qmicid = 0x00
.flags = 0x01 invalid
.tid = 0x10
.msgid = 0x0022
.len = 0x000c
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (2)  01 02	..

.tf = 0x01
.len = 0x000c
.ctrl = 0x00 control point
.service = 0x01
.qmicid = 0x02
.flags = 0x00 request
.tid = 0x0002
.msgid = 0x0022
.len = 000000

.tf = 0x01
.len = 0x0017
.ctrl = 0x80 service
.service = 0x01
.qmicid = 0x02
.flags = 0x02 response
.tid = 0x0002
.msgid = 0x0022
.len = 0x000b
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (1)  01	.



> Second, does the modem actually respond to DHCP over the ECM interface?

Yes.  And IMHO that's the only sensible thing to do.  Cannot be very
difficult to implement that feature in the firmware.  It's not like you
need to write a dhcp server.  You know you have only a single client on
the other end of a point-to-point link, and the address configuration is
given.  And all broadcast/multicast requires special treatment anyway.
I actually thought about implementing the DHCP functionality in the
driver before I knew that the firmware already provided this.

FWIW, the Windows driver implementation also depends on DHCP for address
configuration .

> If you take a look at the Android rmnet code (which is similar to your
> driver) you'll see they extract the IP address and DNS details from the
> QMI response.  Do we need to do that here too?

Based on the above, I don't think so.  

And if you still want to extract address details without using DHCP,
then that information is available via the AT^DHCP? command anyway.  You
don't need QMI for that part.



Bjørn
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
  2011-12-15 10:02           ` Bjørn Mork
@ 2011-12-15 17:52             ` Dan Williams
       [not found]               ` <1323971530.23419.13.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>
  0 siblings, 1 reply; 16+ messages in thread
From: Dan Williams @ 2011-12-15 17:52 UTC (permalink / raw
  To: Bjørn Mork; +Cc: netdev, linux-usb

On Thu, 2011-12-15 at 11:02 +0100, Bjørn Mork wrote:
> Dan Williams <dcbw@redhat.com> writes:
> 
> > So I thought the protocol acronym stood for "Qualcomm MSM/Modem
> > Interface"  (see
> > https://www.codeaurora.org/gitweb/quic/le/?p=kernel/msm.git;a=commitdiff;h=5f6f87b51184e13b6c493012de787895d5d18765)
> 
> That's probably correct.  Don't know where I got the messaging part
> from.  Should be corrected....
> 
> > In any case, great work here.  But I'm a bit concerned about how all
> > this should fit together.  QMI is a huge protocol that does everything a
> > modem would ever want to do, and we'd likely want to be able to speak
> > QMI from userspace to the modem too so that modem managers can expose
> > the full functionality of the modems.  For example Gobi modems have a
> > very minimal AT command set and most of the functionality is exposed
> > over QMI or DM.  That's why the Qualcomm GobiNet driver also
> > exposed /dev/qmi for userspace access.
> 
> Yes, I believe some interface should be exported.  But my primary goal
> was to make something that would just work as an ethernet device with a
> minimum of external userspace dependencies.  And this modem seems to
> have a fairly complete set of AT commands anyway.  Ideally I would want
> to stick
> 
>  auto wwan0
>  iface wwan0 inet dhcp
> 
> in my /etc/network/interface and not need any application for that to
> work.  But I realize that I must enter a SIM PIN1 code first (unless
> disabled), and that most users will want to configure a specific APN
> (although the null APN "" most likely will work just fine).
> 
> But I agree that eventually the full QMI protocol should be made
> available to userspace for other uses.  That should be fairly easy to do
> if you just proxy the commands.  But I'm worring about the interface.
> Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?

It would have to be /dev/qmiX (in case you have more than one
QMI-capable card in the system) and it would also have to have the right
sysfs entries so that we could match the qmiX entry up with it's parent
USB interface.  Not entirely sure how to do that.

> Wouldn't something like netlink have been more suitable?  How about
> security and the ability to control privileges on a command to command
> basis?  And are there users?  Note that even the Windows application
> supplied with the modem uses AT commands for SMS sending/receiving etc.
> The snoop I did showed a very minimal set of QMI commands.  These were
> all Windows used for startup of the network interface (partly decoded by
> me):

Huawei writes custom firmware for their dongles.  Gobi devices and other
devices that talk QMI don't  necessarily have such a full quite of AT
commands, yet they all talk the same QMI protocol.  It makes sense to
have a generic driver for this if we can.  That probably means a QMI
core (like you've got with the qmi_wwan stuff) and device-specific
drives.  The Huawei device would use the ECM-like stuff while the Gobi
bits would implement what gobi_net does.  They might even be almost the
same, I haven't looked in a while.  But they are similar enough that
they should be sharing most of the code.

But it gets more complicated.  We also have Novatel and Sierra devices
that are driven by different drivers (option for Novatel, and sierra for
Sierra) and both these vendors have Gobi-based devices that also speak
QMI, but have custom Ethernet interfaces too (sierra_net for example).

The point being that we need some mechanism for exposing QMI on all
these devices, but some of them (ie Huawei) also need to use QMI to make
the Ethernet bits happen.  Some don't.

[snip]

> 
> > Second, does the modem actually respond to DHCP over the ECM interface?
> 
> Yes.  And IMHO that's the only sensible thing to do.  Cannot be very
> difficult to implement that feature in the firmware.  It's not like you

Not quite, Option's method is just fine too (provide an AT command that
prints the IP and DNS information that you then assign to the
interface).  The devices are a lot more complicated than just doing
DHCP, and you're lucky that it works as easily as it does with the
Huawei part :)  Doesn't mean it should necessarily work that way all the
time.

But companies don't necessarily like adding features to firmware.
That's a lot harder to change than features in the driver.  I worked
with Sierra a couple years ago to get the sierra_net driver accepted to
the kernel and in the first round the DHCP bits were in the driver.
Which was unacceptable.  They finally did move it to the firmware but
not all vendors are as easy to work with as Sierra was.

> need to write a dhcp server.  You know you have only a single client on
> the other end of a point-to-point link, and the address configuration is
> given.  And all broadcast/multicast requires special treatment anyway.
> I actually thought about implementing the DHCP functionality in the
> driver before I knew that the firmware already provided this.
> 
> FWIW, the Windows driver implementation also depends on DHCP for address
> configuration .

Good to know.

> > If you take a look at the Android rmnet code (which is similar to your
> > driver) you'll see they extract the IP address and DNS details from the
> > QMI response.  Do we need to do that here too?
> 
> Based on the above, I don't think so.  
> 
> And if you still want to extract address details without using DHCP,
> then that information is available via the AT^DHCP? command anyway.  You
> don't need QMI for that part.

Ok, also good to know.  Honestly I don't really understand why everyone
just uses cdc-ether or cdc-eem these days but apparently they don't.

Dan

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
       [not found]               ` <1323971530.23419.13.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>
@ 2011-12-16 16:03                 ` Bjørn Mork
       [not found]                   ` <874nx0bj1d.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>
  0 siblings, 1 reply; 16+ messages in thread
From: Bjørn Mork @ 2011-12-16 16:03 UTC (permalink / raw
  To: Dan Williams
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA

[-- Attachment #1: Type: text/plain, Size: 4024 bytes --]

Dan Williams <dcbw-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> writes:
> On Thu, 2011-12-15 at 11:02 +0100, Bjørn Mork wrote:
>
> But I agree that eventually the full QMI protocol should be made
>> available to userspace for other uses.  That should be fairly easy to do
>> if you just proxy the commands.  But I'm worring about the interface.
>> Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?
>
> It would have to be /dev/qmiX (in case you have more than one
> QMI-capable card in the system) and it would also have to have the right
> sysfs entries so that we could match the qmiX entry up with it's parent
> USB interface.  Not entirely sure how to do that.

The idea of creating multiple independent devices and then stitch them
together again using a sysfs API seems a little backwards to me.  How
about just use ioctls and forward both request and reply?  That won't
support unsolicited notifications, but otherwise it should be enough to
be useful.

The attached patch implements straight QMI forwarding.  Note that it is
only intended as a demo, and *not* a submission.  I have not yet decided
whether this is a good idea or not, and I assume that the API should
receive some more thought and blessings from others than me before
anything like this can be submitted.

It should maybe even be extended to something a little less
driver/device specific.  I believe a common WWAN API for settings things
like PIN code and APN would be very useful, in the same way the wireless
API has made WLAN usage possible without driver specific tools.


But anyway, the demo does work.  Using it to send this reqest for
firmware revision from userspace:

0000  01 0c 00 00 02 00 00 01  00 23 00 00 00

partly decoded as:

.tf = 0x01
.len = 0x000c
.ctrl = 0x00 control point
.service = 0x02
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x0001
.msgid = 0x0023
.len = 000000


I get the expected reply:

0000  01 4c 00 80 02 00 02 01  00 23 00 40 00 02 04 00
0010  00 00 00 00 01 36 00 4d  39 32 30 30 42 2d 53 43
0020  41 51 44 42 5a 44 2d 33  2e 30 2e 33 35 30 30 32
0030  35 54 20 20 31 20 20 5b  41 75 67 20 31 31 20 32
0040  30 31 31 20 30 32 3a 30  30 3a 30 30 5d

partly decoded as:

.tf = 0x01
.len = 0x004c
.ctrl = 0x80 service
.service = 0x02
.qmicid = 0x00
.flags = 0x02 response
.tid = 0x0001
.msgid = 0x0023
.len = 0x0040
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (54) 4d 39 32 30 30 42 2d 53 43 41 51 44 42 5a 44 2d 33 2e 30 2e 33 35 30 30 32 35 54 20 20 31 20 20 5b 41 75 67 20 31 31 20 32 30 31 31 20 30 32 3a 30 30 3a 30 30 5d   M9200B-SCAQDBZD-3.0.350025T  1  [Aug 11 2011 02:00:00]


This is while having an open connection and sending traffic over it (not
that that should matter, but anyway..)

> Huawei writes custom firmware for their dongles. 

Somehow I find that hard to believe.  I can believe that they *build* a
custom firmware, enabling a vendor specific set of options, setting
their own USB descriptors etc.  And maybe even write some vendor
specific feature.  But I would be surprised if most of their firmware
code didn't come directly from the chipset vendor example code.

And the same goes for every other dongle maker.

> Gobi devices and other
> devices that talk QMI don't  necessarily have such a full quite of AT
> commands, yet they all talk the same QMI protocol.  It makes sense to
> have a generic driver for this if we can.  That probably means a QMI
> core (like you've got with the qmi_wwan stuff) and device-specific
> drives.  The Huawei device would use the ECM-like stuff while the Gobi
> bits would implement what gobi_net does.  They might even be almost the
> same, I haven't looked in a while.  But they are similar enough that
> they should be sharing most of the code.

Yes, I absolutely agree. That's why I tried to keep the qmi specific
code as driver agnostic as possible.  Well, I could probably have done
better, but it's a start..



Bjørn


[-- Attachment #2: 0001-qmi_wwan-allow-QMI-proxying-via-netdev-ioctl.patch --]
[-- Type: text/x-diff, Size: 7869 bytes --]

>From 250a8a1214ffb80bfc93e7d93f01796564dabd24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bjorn-yOkvZcmFvRU@public.gmane.org>
Date: Fri, 16 Dec 2011 16:31:43 +0100
Subject: [PATCH] qmi_wwan: allow QMI proxying via netdev ioctl
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Proxy QMI requests received via ioctl to embedded CDC commands, and
return the embedded CDC responses.  Will not allow QMI_CTL commands.
Shared subsystem client IDs are allocated on-demand by the driver.

Signed-off-by: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
---
 drivers/net/usb/qmi.c           |  118 +++++++++++++++++++++++++++++++++++++++
 drivers/net/usb/qmi.h           |    6 ++
 drivers/net/usb/qmi_wwan_core.c |   60 ++++++++++++++++++++
 3 files changed, 184 insertions(+), 0 deletions(-)

diff --git a/drivers/net/usb/qmi.c b/drivers/net/usb/qmi.c
index 78269a3..b3c022b 100644
--- a/drivers/net/usb/qmi.c
+++ b/drivers/net/usb/qmi.c
@@ -459,6 +459,10 @@ static int qmi_ctl_request_cid(struct qmi_state *qmi, u8 system)
 	u8 tlvdata[1] = { system };
 	char buf[32];
 
+	/* fail if we have no slot for the cid */
+	if (system >= sizeof(qmi->cid))
+		return -1;
+
 	/* return immediately if a CID is already allocated */
 	if (qmi->cid[system])
 		return qmi->cid[system];
@@ -579,6 +583,120 @@ static int qmi_dms_verify_pin(struct qmi_state *qmi)
 
 /* ----- exported API ----- */
 
+/* proxy QMI requests, using our own client and transaction IDs */
+int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen)
+{
+	char *buf;
+	struct qmux *h;
+	struct qmi_msg *m;
+	size_t len;
+	u8 ucid;
+	__le16 utid, tid;
+	unsigned long flags = qmi->flags;
+	int ret = -EFAULT;
+	int timeout = 30;
+
+	/* we don't allow QMI_CTL, so the minimum QMI message size is: */
+	if (buflen < sizeof(*buf) + sizeof(*m))
+                return -EINVAL;
+
+
+	buf = kmalloc(buflen, GFP_ATOMIC);
+	if (!buf)
+		return -ENOMEM;
+
+	if (copy_from_user(buf, arg, buflen))
+		goto error;
+
+	/* verify that what we got is a valid QMI message and that we have a CID for the system */
+	h = (struct qmux *)buf;
+	len = h->len + 1; /* total QMUX length including the tf byte */
+
+
+
+	/* sanity.  Note that we disallow the QMI_CTL subsystem */
+	if (h->tf != 0x01 || len > buflen || h->service == QMI_CTL) {
+		ret = -EINVAL;
+		goto error;
+	}
+
+	m = (struct qmi_msg *)h->msg;
+
+	/* require an exact length match */
+	if (sizeof(*h) + sizeof(*m) + m->len != len) {
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* attempt to get a client ID for the requested subsystem */
+	if (qmi_ctl_request_cid(qmi, h->service) < 0)
+		goto error;
+
+	/* use our private cid and tid, saving the user's so that we can restore them on return */
+	ucid = h->qmicid;
+	utid = h->tid.w;
+	tid = new_tid();
+	h->qmicid = qmi->cid[h->service];
+	h->tid.w = tid;
+
+	/* turn off async reads and send the request */
+	qmi->flags &= ~QMI_FLAG_RECV;
+	if (usb_control_msg(qmi->dev, usb_sndctrlpipe(qmi->dev, 0),
+				USB_CDC_SEND_ENCAPSULATED_COMMAND,
+				USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
+				0, qmi->intfnr, buf, len, 1000) != len)
+		goto error;
+
+	/* read the reply into the same buffer */
+
+	/* wait a while for the (correct) reply */
+	do {
+		ret = usb_control_msg(qmi->dev, usb_rcvctrlpipe(qmi->dev, 0),
+			       USB_CDC_GET_ENCAPSULATED_RESPONSE, USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
+			       0, qmi->intfnr, buf, buflen, 1000);
+		if (ret < 0)
+			goto error;
+		
+		m = qmi_qmux_verify(buf, ret);
+		if (m && h->tid.w == tid) /* got it */
+			break;
+
+		/* handle other packets returned while waiting for the correct one */
+		if (ret && m) {
+			switch (h->service) {
+			case QMI_CTL:
+				qmi_ctl_parse(qmi, m);
+				break;
+			case QMI_WDS:
+				qmi_wds_parse(qmi, m);
+				break;
+			case QMI_DMS:
+				qmi_dms_parse(qmi, m);
+			}
+			if (qmi_debug)
+				qmi_dump_qmux(qmi, buf, h->len + 1);
+		}
+		msleep(100);
+	} while (timeout-- > 0);
+	if (timeout == 0)
+		goto error;
+
+	/* restore the users input to hide our internal id's */
+	h->qmicid = ucid;
+	h->tid.w = utid;
+
+	/* return the reply to the user */
+	if (copy_to_user(arg, buf, h->len + 1))
+		goto error;
+
+	ret = 0;
+
+error:
+	qmi->flags = flags;
+	kfree(buf);
+	return ret;
+}
+
 int qmi_reset(struct qmi_state *qmi)
 {
 	/* NOTE:  We do NOT clear the allocated CIDs! */
diff --git a/drivers/net/usb/qmi.h b/drivers/net/usb/qmi.h
index 53ebafe..bf9c8fd 100644
--- a/drivers/net/usb/qmi.h
+++ b/drivers/net/usb/qmi.h
@@ -82,6 +82,12 @@ struct qmi_tlv_response_data {
 } __packed;
 
 
+/* <linux/if.h> extension */
+#define IF_IFACE_WWAN 0x1042
+
+/* proxy QMI requests via ioctl */
+extern int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen);
+
 /* reset state to INIT */
 extern int qmi_reset(struct qmi_state *qmi);
 
diff --git a/drivers/net/usb/qmi_wwan_core.c b/drivers/net/usb/qmi_wwan_core.c
index 6d96465..79c3b1a 100644
--- a/drivers/net/usb/qmi_wwan_core.c
+++ b/drivers/net/usb/qmi_wwan_core.c
@@ -8,6 +8,7 @@
 
 #include <linux/module.h>
 #include <linux/netdevice.h>
+#include <linux/etherdevice.h>
 #include <linux/ethtool.h>
 #include <linux/mii.h>
 #include <linux/usb.h>
@@ -46,6 +47,57 @@ static void qmi_wwan_cdc_status(struct usbnet *dev, struct urb *urb)
 	}
 }
 
+static int qmi_wwan_ioctl(struct net_device *net, struct ifreq *ifr, int cmd)
+{
+        struct usbnet *dev = netdev_priv(net);
+	struct if_settings *settings = &ifr->ifr_settings;
+	struct qmi_wwan_state *info = (void *)&dev->data;
+
+	switch (cmd) {
+	case SIOCDEVPRIVATE + 0: /* get something to verify that the private ioctl protocol */
+		/* FIMXE: implement this! */
+		return 0;
+
+	case SIOCDEVPRIVATE + 1: /* set SIM PIN */
+		/* FIMXE: implement this! */
+		return 0;
+
+	case SIOCDEVPRIVATE + 2: /* set APN */
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		/* cannot change APN if interface is up */
+		if (net->flags & IFF_UP)
+			return -EBUSY;
+
+		/* FIMXE: implement this! */
+		return 0;
+
+	case SIOCDEVPRIVATE + 3: /* proxy QMI */
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+		if (settings->type != IF_IFACE_WWAN)
+			return -EINVAL;
+		netdev_dbg(net, "proxying userspace QMI request\n");
+		return qmi_ioctl_proxy(info->qmi, settings->ifs_ifsu.fr, settings->size);
+		
+	default:
+		netdev_dbg(net, "ioctl 0x%08x\n", cmd);
+	}
+        return -EINVAL;
+}
+
+/* same as usbnet_netdev_ops but ioctl added */
+static const struct net_device_ops qmi_wwan_netdev_ops = {
+        .ndo_open               = usbnet_open,
+        .ndo_stop               = usbnet_stop,
+        .ndo_start_xmit         = usbnet_start_xmit,
+        .ndo_tx_timeout         = usbnet_tx_timeout,
+        .ndo_set_mac_address    = eth_mac_addr,
+        .ndo_validate_addr      = eth_validate_addr,
+        .ndo_do_ioctl           = qmi_wwan_ioctl,
+};
+
 static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *intf)
 {
 	int status;
@@ -61,6 +113,10 @@ static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *i
 		usbnet_cdc_unbind(dev, intf);
 		return -1;
 	}
+
+	/* override default usbnet ops so that we can handle ioctls */
+        dev->net->netdev_ops = &qmi_wwan_netdev_ops;
+
 	return 0;
 }
 
@@ -80,6 +136,10 @@ static int qmi_wwan_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
 		return -1;
 
 	info->control = intf;
+
+	/* override default usbnet ops so that we can handle ioctls */
+        dev->net->netdev_ops = &qmi_wwan_netdev_ops;
+
 	return 0;
 }
 
-- 
1.7.7.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
       [not found]                   ` <874nx0bj1d.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>
@ 2011-12-16 17:17                     ` Dan Williams
       [not found]                       ` <1324055842.17587.18.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>
  0 siblings, 1 reply; 16+ messages in thread
From: Dan Williams @ 2011-12-16 17:17 UTC (permalink / raw
  To: Bjørn Mork
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA

On Fri, 2011-12-16 at 17:03 +0100, Bjørn Mork wrote:
> Dan Williams <dcbw-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> writes:
> > On Thu, 2011-12-15 at 11:02 +0100, Bjørn Mork wrote:
> >
> > But I agree that eventually the full QMI protocol should be made
> >> available to userspace for other uses.  That should be fairly easy to do
> >> if you just proxy the commands.  But I'm worring about the interface.
> >> Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?
> >
> > It would have to be /dev/qmiX (in case you have more than one
> > QMI-capable card in the system) and it would also have to have the right
> > sysfs entries so that we could match the qmiX entry up with it's parent
> > USB interface.  Not entirely sure how to do that.
> 
> The idea of creating multiple independent devices and then stitch them
> together again using a sysfs API seems a little backwards to me.  How
> about just use ioctls and forward both request and reply?  That won't
> support unsolicited notifications, but otherwise it should be enough to
> be useful.

Typically adding more ioctls isn't well received.  Plus I don't think
there's a huge reason to do so, because it's really just a stream
protocol that's easily handled from a file descriptor.  ioctls add
unecessary structure.  Netlink might be a better approach if we want
something there, but any time you think of adding an ioctl you need to
start questioning that thought :)  I just don't think ioctls are really
required in this instance.

And we don't really have to stitch devices together; the QMI device is
really just a file descriptor with read/write operations.  It's not any
different than a serial device like ttyUSB0.  And that fits just fine
into the sysfs hierarchy just like the USB interfaces from the modem do,
and the ethernet device from the modem does.  In sysfs they all have a
common ancestor: the USB device they are provided by. That allows
connection  managers to find out that ttyUSB0 and ttyUSB1 and usb0 are
all owned by the same device, and that say usb0 is the network device
that must be set up using ttyUSB0.  This is what allows hardware
autodetection, otherwise users have to sit around editing files in /etc
to tell the machine things it should already know.

> The attached patch implements straight QMI forwarding.  Note that it is
> only intended as a demo, and *not* a submission.  I have not yet decided
> whether this is a good idea or not, and I assume that the API should
> receive some more thought and blessings from others than me before
> anything like this can be submitted.
> 
> It should maybe even be extended to something a little less
> driver/device specific.  I believe a common WWAN API for settings things
> like PIN code and APN would be very useful, in the same way the wireless
> API has made WLAN usage possible without driver specific tools.

Unfortunately there's really too much variation in WWAN modems to make
this happen in the kernel.  It's really best left to userspace.  Plus,
there are a *ton* of quirks for different devices.  On some devices you
can send a command on any AT port, on others it has to be a specific AT
port.  AT parsing isn't something we should be doing in the kernel, and
most modems use AT at this point.  Others use QMI, some QCDM, some CnS,
some WMC, etc.  A common kernel-side WWAN API is just not going to
happen.  But in userspace we have various projects like ModemManager
that try to provide that generic API and abstract the differences
between modems.  That's my primary interest in this.

> 
> But anyway, the demo does work.  Using it to send this reqest for
> firmware revision from userspace:
> 
> 0000  01 0c 00 00 02 00 00 01  00 23 00 00 00
> 
> partly decoded as:
> 
> .tf = 0x01
> .len = 0x000c
> .ctrl = 0x00 control point
> .service = 0x02
> .qmicid = 0x00
> .flags = 0x00 request
> .tid = 0x0001
> .msgid = 0x0023
> .len = 000000
> 
> 
> I get the expected reply:
> 
> 0000  01 4c 00 80 02 00 02 01  00 23 00 40 00 02 04 00
> 0010  00 00 00 00 01 36 00 4d  39 32 30 30 42 2d 53 43
> 0020  41 51 44 42 5a 44 2d 33  2e 30 2e 33 35 30 30 32
> 0030  35 54 20 20 31 20 20 5b  41 75 67 20 31 31 20 32
> 0040  30 31 31 20 30 32 3a 30  30 3a 30 30 5d
> 
> partly decoded as:
> 
> .tf = 0x01
> .len = 0x004c
> .ctrl = 0x80 service
> .service = 0x02
> .qmicid = 0x00
> .flags = 0x02 response
> .tid = 0x0001
> .msgid = 0x0023
> .len = 0x0040
> [0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
> [0x01] (54) 4d 39 32 30 30 42 2d 53 43 41 51 44 42 5a 44 2d 33 2e 30 2e 33 35 30 30 32 35 54 20 20 31 20 20 5b 41 75 67 20 31 31 20 32 30 31 31 20 30 32 3a 30 30 3a 30 30 5d   M9200B-SCAQDBZD-3.0.350025T  1  [Aug 11 2011 02:00:00]
> 
> 
> This is while having an open connection and sending traffic over it (not
> that that should matter, but anyway..)
> 
> > Huawei writes custom firmware for their dongles. 
> 
> Somehow I find that hard to believe.  I can believe that they *build* a
> custom firmware, enabling a vendor specific set of options, setting
> their own USB descriptors etc.  And maybe even write some vendor
> specific feature.  But I would be surprised if most of their firmware
> code didn't come directly from the chipset vendor example code.

Yeah, I wasn't specific enough.  Everyone who uses a Qualcomm chipset
and licenses the firmware sources can modify it.  Some vendors just use
the straight Qualcomm firmware (mostly no-name Asian manufacturers).
But many vendors add their own AT commands, write custom protocols (CnS,
WMC, etc), some add custom network transports (Sierra), etc.  Huawei has
apparnetly decided to change the USB descriptors too.

> And the same goes for every other dongle maker.
> 
> > Gobi devices and other
> > devices that talk QMI don't  necessarily have such a full quite of AT
> > commands, yet they all talk the same QMI protocol.  It makes sense to
> > have a generic driver for this if we can.  That probably means a QMI
> > core (like you've got with the qmi_wwan stuff) and device-specific
> > drives.  The Huawei device would use the ECM-like stuff while the Gobi
> > bits would implement what gobi_net does.  They might even be almost the
> > same, I haven't looked in a while.  But they are similar enough that
> > they should be sharing most of the code.
> 
> Yes, I absolutely agree. That's why I tried to keep the qmi specific
> code as driver agnostic as possible.  Well, I could probably have done
> better, but it's a start..

So the reason gobi_net wasn't accepted was that with Gobi 1000 and 2000
chipsets, firmware needs to be loaded into the chip.  And that firmware
isn't free; you have to get it from the Windows partition of your
machine.  So davem rejected it because there was no way distros could
really test it easily since the firmware isn't freely available.

But now the Gobi 3000 stores firmware onboard, so it's worth another try
to get gobi_net upstreamed.

Dan


> 
> 
> Bjørn
> 
> differences between files attachment
> (0001-qmi_wwan-allow-QMI-proxying-via-netdev-ioctl.patch)
> From 250a8a1214ffb80bfc93e7d93f01796564dabd24 Mon Sep 17 00:00:00 2001
> From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bjorn-yOkvZcmFvRU@public.gmane.org>
> Date: Fri, 16 Dec 2011 16:31:43 +0100
> Subject: [PATCH] qmi_wwan: allow QMI proxying via netdev ioctl
> MIME-Version: 1.0
> Content-Type: text/plain; charset=UTF-8
> Content-Transfer-Encoding: 8bit
> 
> Proxy QMI requests received via ioctl to embedded CDC commands, and
> return the embedded CDC responses.  Will not allow QMI_CTL commands.
> Shared subsystem client IDs are allocated on-demand by the driver.
> 
> Signed-off-by: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
> ---
>  drivers/net/usb/qmi.c           |  118 +++++++++++++++++++++++++++++++++++++++
>  drivers/net/usb/qmi.h           |    6 ++
>  drivers/net/usb/qmi_wwan_core.c |   60 ++++++++++++++++++++
>  3 files changed, 184 insertions(+), 0 deletions(-)
> 
> diff --git a/drivers/net/usb/qmi.c b/drivers/net/usb/qmi.c
> index 78269a3..b3c022b 100644
> --- a/drivers/net/usb/qmi.c
> +++ b/drivers/net/usb/qmi.c
> @@ -459,6 +459,10 @@ static int qmi_ctl_request_cid(struct qmi_state *qmi, u8 system)
>  	u8 tlvdata[1] = { system };
>  	char buf[32];
>  
> +	/* fail if we have no slot for the cid */
> +	if (system >= sizeof(qmi->cid))
> +		return -1;
> +
>  	/* return immediately if a CID is already allocated */
>  	if (qmi->cid[system])
>  		return qmi->cid[system];
> @@ -579,6 +583,120 @@ static int qmi_dms_verify_pin(struct qmi_state *qmi)
>  
>  /* ----- exported API ----- */
>  
> +/* proxy QMI requests, using our own client and transaction IDs */
> +int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen)
> +{
> +	char *buf;
> +	struct qmux *h;
> +	struct qmi_msg *m;
> +	size_t len;
> +	u8 ucid;
> +	__le16 utid, tid;
> +	unsigned long flags = qmi->flags;
> +	int ret = -EFAULT;
> +	int timeout = 30;
> +
> +	/* we don't allow QMI_CTL, so the minimum QMI message size is: */
> +	if (buflen < sizeof(*buf) + sizeof(*m))
> +                return -EINVAL;
> +
> +
> +	buf = kmalloc(buflen, GFP_ATOMIC);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	if (copy_from_user(buf, arg, buflen))
> +		goto error;
> +
> +	/* verify that what we got is a valid QMI message and that we have a CID for the system */
> +	h = (struct qmux *)buf;
> +	len = h->len + 1; /* total QMUX length including the tf byte */
> +
> +
> +
> +	/* sanity.  Note that we disallow the QMI_CTL subsystem */
> +	if (h->tf != 0x01 || len > buflen || h->service == QMI_CTL) {
> +		ret = -EINVAL;
> +		goto error;
> +	}
> +
> +	m = (struct qmi_msg *)h->msg;
> +
> +	/* require an exact length match */
> +	if (sizeof(*h) + sizeof(*m) + m->len != len) {
> +		ret = -EINVAL;
> +		goto error;
> +	}
> +
> +	/* attempt to get a client ID for the requested subsystem */
> +	if (qmi_ctl_request_cid(qmi, h->service) < 0)
> +		goto error;
> +
> +	/* use our private cid and tid, saving the user's so that we can restore them on return */
> +	ucid = h->qmicid;
> +	utid = h->tid.w;
> +	tid = new_tid();
> +	h->qmicid = qmi->cid[h->service];
> +	h->tid.w = tid;
> +
> +	/* turn off async reads and send the request */
> +	qmi->flags &= ~QMI_FLAG_RECV;
> +	if (usb_control_msg(qmi->dev, usb_sndctrlpipe(qmi->dev, 0),
> +				USB_CDC_SEND_ENCAPSULATED_COMMAND,
> +				USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
> +				0, qmi->intfnr, buf, len, 1000) != len)
> +		goto error;
> +
> +	/* read the reply into the same buffer */
> +
> +	/* wait a while for the (correct) reply */
> +	do {
> +		ret = usb_control_msg(qmi->dev, usb_rcvctrlpipe(qmi->dev, 0),
> +			       USB_CDC_GET_ENCAPSULATED_RESPONSE, USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
> +			       0, qmi->intfnr, buf, buflen, 1000);
> +		if (ret < 0)
> +			goto error;
> +		
> +		m = qmi_qmux_verify(buf, ret);
> +		if (m && h->tid.w == tid) /* got it */
> +			break;
> +
> +		/* handle other packets returned while waiting for the correct one */
> +		if (ret && m) {
> +			switch (h->service) {
> +			case QMI_CTL:
> +				qmi_ctl_parse(qmi, m);
> +				break;
> +			case QMI_WDS:
> +				qmi_wds_parse(qmi, m);
> +				break;
> +			case QMI_DMS:
> +				qmi_dms_parse(qmi, m);
> +			}
> +			if (qmi_debug)
> +				qmi_dump_qmux(qmi, buf, h->len + 1);
> +		}
> +		msleep(100);
> +	} while (timeout-- > 0);
> +	if (timeout == 0)
> +		goto error;
> +
> +	/* restore the users input to hide our internal id's */
> +	h->qmicid = ucid;
> +	h->tid.w = utid;
> +
> +	/* return the reply to the user */
> +	if (copy_to_user(arg, buf, h->len + 1))
> +		goto error;
> +
> +	ret = 0;
> +
> +error:
> +	qmi->flags = flags;
> +	kfree(buf);
> +	return ret;
> +}
> +
>  int qmi_reset(struct qmi_state *qmi)
>  {
>  	/* NOTE:  We do NOT clear the allocated CIDs! */
> diff --git a/drivers/net/usb/qmi.h b/drivers/net/usb/qmi.h
> index 53ebafe..bf9c8fd 100644
> --- a/drivers/net/usb/qmi.h
> +++ b/drivers/net/usb/qmi.h
> @@ -82,6 +82,12 @@ struct qmi_tlv_response_data {
>  } __packed;
>  
> 
> +/* <linux/if.h> extension */
> +#define IF_IFACE_WWAN 0x1042
> +
> +/* proxy QMI requests via ioctl */
> +extern int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen);
> +
>  /* reset state to INIT */
>  extern int qmi_reset(struct qmi_state *qmi);
>  
> diff --git a/drivers/net/usb/qmi_wwan_core.c b/drivers/net/usb/qmi_wwan_core.c
> index 6d96465..79c3b1a 100644
> --- a/drivers/net/usb/qmi_wwan_core.c
> +++ b/drivers/net/usb/qmi_wwan_core.c
> @@ -8,6 +8,7 @@
>  
>  #include <linux/module.h>
>  #include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
>  #include <linux/ethtool.h>
>  #include <linux/mii.h>
>  #include <linux/usb.h>
> @@ -46,6 +47,57 @@ static void qmi_wwan_cdc_status(struct usbnet *dev, struct urb *urb)
>  	}
>  }
>  
> +static int qmi_wwan_ioctl(struct net_device *net, struct ifreq *ifr, int cmd)
> +{
> +        struct usbnet *dev = netdev_priv(net);
> +	struct if_settings *settings = &ifr->ifr_settings;
> +	struct qmi_wwan_state *info = (void *)&dev->data;
> +
> +	switch (cmd) {
> +	case SIOCDEVPRIVATE + 0: /* get something to verify that the private ioctl protocol */
> +		/* FIMXE: implement this! */
> +		return 0;
> +
> +	case SIOCDEVPRIVATE + 1: /* set SIM PIN */
> +		/* FIMXE: implement this! */
> +		return 0;
> +
> +	case SIOCDEVPRIVATE + 2: /* set APN */
> +		if (!capable(CAP_NET_ADMIN))
> +			return -EPERM;
> +
> +		/* cannot change APN if interface is up */
> +		if (net->flags & IFF_UP)
> +			return -EBUSY;
> +
> +		/* FIMXE: implement this! */
> +		return 0;
> +
> +	case SIOCDEVPRIVATE + 3: /* proxy QMI */
> +		if (!capable(CAP_NET_ADMIN))
> +			return -EPERM;
> +		if (settings->type != IF_IFACE_WWAN)
> +			return -EINVAL;
> +		netdev_dbg(net, "proxying userspace QMI request\n");
> +		return qmi_ioctl_proxy(info->qmi, settings->ifs_ifsu.fr, settings->size);
> +		
> +	default:
> +		netdev_dbg(net, "ioctl 0x%08x\n", cmd);
> +	}
> +        return -EINVAL;
> +}
> +
> +/* same as usbnet_netdev_ops but ioctl added */
> +static const struct net_device_ops qmi_wwan_netdev_ops = {
> +        .ndo_open               = usbnet_open,
> +        .ndo_stop               = usbnet_stop,
> +        .ndo_start_xmit         = usbnet_start_xmit,
> +        .ndo_tx_timeout         = usbnet_tx_timeout,
> +        .ndo_set_mac_address    = eth_mac_addr,
> +        .ndo_validate_addr      = eth_validate_addr,
> +        .ndo_do_ioctl           = qmi_wwan_ioctl,
> +};
> +
>  static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *intf)
>  {
>  	int status;
> @@ -61,6 +113,10 @@ static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *i
>  		usbnet_cdc_unbind(dev, intf);
>  		return -1;
>  	}
> +
> +	/* override default usbnet ops so that we can handle ioctls */
> +        dev->net->netdev_ops = &qmi_wwan_netdev_ops;
> +
>  	return 0;
>  }
>  
> @@ -80,6 +136,10 @@ static int qmi_wwan_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
>  		return -1;
>  
>  	info->control = intf;
> +
> +	/* override default usbnet ops so that we can handle ioctls */
> +        dev->net->netdev_ops = &qmi_wwan_netdev_ops;
> +
>  	return 0;
>  }
>  


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
       [not found]                       ` <1324055842.17587.18.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>
@ 2012-01-04 20:57                         ` Dan Williams
  2012-01-05  8:58                           ` Bjørn Mork
  0 siblings, 1 reply; 16+ messages in thread
From: Dan Williams @ 2012-01-04 20:57 UTC (permalink / raw
  To: Bjørn Mork
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA

On Fri, 2011-12-16 at 11:17 -0600, Dan Williams wrote:
> On Fri, 2011-12-16 at 17:03 +0100, Bjørn Mork wrote:
> > Dan Williams <dcbw-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> writes:
> > > On Thu, 2011-12-15 at 11:02 +0100, Bjørn Mork wrote:
> > >
> > > But I agree that eventually the full QMI protocol should be made
> > >> available to userspace for other uses.  That should be fairly easy to do
> > >> if you just proxy the commands.  But I'm worring about the interface.
> > >> Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?
> > >
> > > It would have to be /dev/qmiX (in case you have more than one
> > > QMI-capable card in the system) and it would also have to have the right
> > > sysfs entries so that we could match the qmiX entry up with it's parent
> > > USB interface.  Not entirely sure how to do that.
> > 
> > The idea of creating multiple independent devices and then stitch them
> > together again using a sysfs API seems a little backwards to me.  How
> > about just use ioctls and forward both request and reply?  That won't
> > support unsolicited notifications, but otherwise it should be enough to
> > be useful.
> 
> Typically adding more ioctls isn't well received.  Plus I don't think
> there's a huge reason to do so, because it's really just a stream
> protocol that's easily handled from a file descriptor.  ioctls add
> unecessary structure.  Netlink might be a better approach if we want
> something there, but any time you think of adding an ioctl you need to
> start questioning that thought :)  I just don't think ioctls are really
> required in this instance.
> 
> And we don't really have to stitch devices together; the QMI device is
> really just a file descriptor with read/write operations.  It's not any
> different than a serial device like ttyUSB0.  And that fits just fine
> into the sysfs hierarchy just like the USB interfaces from the modem do,
> and the ethernet device from the modem does.  In sysfs they all have a
> common ancestor: the USB device they are provided by. That allows
> connection  managers to find out that ttyUSB0 and ttyUSB1 and usb0 are
> all owned by the same device, and that say usb0 is the network device
> that must be set up using ttyUSB0.  This is what allows hardware
> autodetection, otherwise users have to sit around editing files in /etc
> to tell the machine things it should already know.
> 
> > The attached patch implements straight QMI forwarding.  Note that it is
> > only intended as a demo, and *not* a submission.  I have not yet decided
> > whether this is a good idea or not, and I assume that the API should
> > receive some more thought and blessings from others than me before
> > anything like this can be submitted.
> > 
> > It should maybe even be extended to something a little less
> > driver/device specific.  I believe a common WWAN API for settings things
> > like PIN code and APN would be very useful, in the same way the wireless
> > API has made WLAN usage possible without driver specific tools.
> 
> Unfortunately there's really too much variation in WWAN modems to make
> this happen in the kernel.  It's really best left to userspace.  Plus,
> there are a *ton* of quirks for different devices.  On some devices you
> can send a command on any AT port, on others it has to be a specific AT
> port.  AT parsing isn't something we should be doing in the kernel, and
> most modems use AT at this point.  Others use QMI, some QCDM, some CnS,
> some WMC, etc.  A common kernel-side WWAN API is just not going to
> happen.  But in userspace we have various projects like ModemManager
> that try to provide that generic API and abstract the differences
> between modems.  That's my primary interest in this.
> 
> > 
> > But anyway, the demo does work.  Using it to send this reqest for
> > firmware revision from userspace:
> > 
> > 0000  01 0c 00 00 02 00 00 01  00 23 00 00 00
> > 
> > partly decoded as:
> > 
> > .tf = 0x01
> > .len = 0x000c
> > .ctrl = 0x00 control point
> > .service = 0x02
> > .qmicid = 0x00
> > .flags = 0x00 request
> > .tid = 0x0001
> > .msgid = 0x0023
> > .len = 000000
> > 
> > 
> > I get the expected reply:
> > 
> > 0000  01 4c 00 80 02 00 02 01  00 23 00 40 00 02 04 00
> > 0010  00 00 00 00 01 36 00 4d  39 32 30 30 42 2d 53 43
> > 0020  41 51 44 42 5a 44 2d 33  2e 30 2e 33 35 30 30 32
> > 0030  35 54 20 20 31 20 20 5b  41 75 67 20 31 31 20 32
> > 0040  30 31 31 20 30 32 3a 30  30 3a 30 30 5d
> > 
> > partly decoded as:
> > 
> > .tf = 0x01
> > .len = 0x004c
> > .ctrl = 0x80 service
> > .service = 0x02
> > .qmicid = 0x00
> > .flags = 0x02 response
> > .tid = 0x0001
> > .msgid = 0x0023
> > .len = 0x0040
> > [0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
> > [0x01] (54) 4d 39 32 30 30 42 2d 53 43 41 51 44 42 5a 44 2d 33 2e 30 2e 33 35 30 30 32 35 54 20 20 31 20 20 5b 41 75 67 20 31 31 20 32 30 31 31 20 30 32 3a 30 30 3a 30 30 5d   M9200B-SCAQDBZD-3.0.350025T  1  [Aug 11 2011 02:00:00]
> > 
> > 
> > This is while having an open connection and sending traffic over it (not
> > that that should matter, but anyway..)
> > 
> > > Huawei writes custom firmware for their dongles. 
> > 
> > Somehow I find that hard to believe.  I can believe that they *build* a
> > custom firmware, enabling a vendor specific set of options, setting
> > their own USB descriptors etc.  And maybe even write some vendor
> > specific feature.  But I would be surprised if most of their firmware
> > code didn't come directly from the chipset vendor example code.
> 
> Yeah, I wasn't specific enough.  Everyone who uses a Qualcomm chipset
> and licenses the firmware sources can modify it.  Some vendors just use
> the straight Qualcomm firmware (mostly no-name Asian manufacturers).
> But many vendors add their own AT commands, write custom protocols (CnS,
> WMC, etc), some add custom network transports (Sierra), etc.  Huawei has
> apparnetly decided to change the USB descriptors too.
> 
> > And the same goes for every other dongle maker.
> > 
> > > Gobi devices and other
> > > devices that talk QMI don't  necessarily have such a full quite of AT
> > > commands, yet they all talk the same QMI protocol.  It makes sense to
> > > have a generic driver for this if we can.  That probably means a QMI
> > > core (like you've got with the qmi_wwan stuff) and device-specific
> > > drives.  The Huawei device would use the ECM-like stuff while the Gobi
> > > bits would implement what gobi_net does.  They might even be almost the
> > > same, I haven't looked in a while.  But they are similar enough that
> > > they should be sharing most of the code.
> > 
> > Yes, I absolutely agree. That's why I tried to keep the qmi specific
> > code as driver agnostic as possible.  Well, I could probably have done
> > better, but it's a start..
> 
> So the reason gobi_net wasn't accepted was that with Gobi 1000 and 2000
> chipsets, firmware needs to be loaded into the chip.  And that firmware
> isn't free; you have to get it from the Windows partition of your
> machine.  So davem rejected it because there was no way distros could
> really test it easily since the firmware isn't freely available.
> 
> But now the Gobi 3000 stores firmware onboard, so it's worth another try
> to get gobi_net upstreamed.

I spent some time with the Pantech UML290 (a dual-mode CDMA/LTE device
based off MSM9xxx) and its network port is basically the same as the
Huawei and the Gobi devices; it uses control transfers for the QMI
commands and bulk transfers for the network data.  Interestingly, the
UML290 uses the "rawip" mode instead of 802.3 mode, so the incoming and
outgoing data is simply raw IP packets.  The IPv4 and IPv6 addresses of
the interface are determined either through QMI requests (IPv4) or
through WMC requests on a separate port (IPv6).  I'm hopeful that we
could switch the device over to 802.3 mode by just sending the right QMI
TLV as part of the CTL/Set Data Format command (0x26) using TLV 0x01
(CTL/Set Data Format Request/Format) and TLV 0x10 (CTL/Set Data Format
Request/Protocol).  But I'm not sure, I haven't tried it yet, and it's
obviously not something that Pantech is actually testing otherwise the
Windows driver would use 802.3 mode.

But what this says to me is that we do need a generic QMI driver for
devices that handles the network traffic and also QMI passthrough.  And
then separate interface drivers that handle the USB-level quirks like
your Huawei driver.  We already have the "gobi_net" driver that Elly
Jones from Google cleaned up back in 2009, but it's not split out like
you've done here and I haven't looked into what changes would need to be
done for that.

I'm still somewhat uncomfortable with the amount of QMI logic in this
driver though, given that for the most part, we try to keep this sort of
stuff out of the kernel and in userland.  It also gives the wrong
impression that the interface can actually be used like a normal
interface, where for the most part it cannot unless there is additional
control logic in userspace.  I don't view it as any different from
AT-based WWAN devices in this regard and I think treating it differently
from them is not the right approach.

Dan


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol
  2012-01-04 20:57                         ` Dan Williams
@ 2012-01-05  8:58                           ` Bjørn Mork
  0 siblings, 0 replies; 16+ messages in thread
From: Bjørn Mork @ 2012-01-05  8:58 UTC (permalink / raw
  To: Dan Williams; +Cc: netdev, linux-usb

Dan Williams <dcbw@redhat.com> writes:

> I spent some time with the Pantech UML290 (a dual-mode CDMA/LTE device
> based off MSM9xxx) and its network port is basically the same as the
> Huawei and the Gobi devices; it uses control transfers for the QMI
> commands and bulk transfers for the network data.  Interestingly, the
> UML290 uses the "rawip" mode instead of 802.3 mode, so the incoming and
> outgoing data is simply raw IP packets.

Interesting.  I assume that's the mode I have been seeing under certain
circumstances too.  But I've not yet discovered how to enable/disable
it.

> The IPv4 and IPv6 addresses of
> the interface are determined either through QMI requests (IPv4) or
> through WMC requests on a separate port (IPv6).  I'm hopeful that we
> could switch the device over to 802.3 mode by just sending the right QMI
> TLV as part of the CTL/Set Data Format command (0x26) using TLV 0x01
> (CTL/Set Data Format Request/Format) and TLV 0x10 (CTL/Set Data Format
> Request/Protocol).  But I'm not sure, I haven't tried it yet, and it's
> obviously not something that Pantech is actually testing otherwise the
> Windows driver would use 802.3 mode.

So the Windows driver uses this mode?  Then it could be something the
vendor chooses when building the firmware, and therefore not runtime
configurable at all.

> But what this says to me is that we do need a generic QMI driver for
> devices that handles the network traffic and also QMI passthrough.  And
> then separate interface drivers that handle the USB-level quirks like
> your Huawei driver.  We already have the "gobi_net" driver that Elly
> Jones from Google cleaned up back in 2009, but it's not split out like
> you've done here and I haven't looked into what changes would need to be
> done for that.

I have barely started looking at this, but christmas and a beach
vacation have been far more interesting lately :-)

> I'm still somewhat uncomfortable with the amount of QMI logic in this
> driver though, given that for the most part, we try to keep this sort of
> stuff out of the kernel and in userland.  It also gives the wrong
> impression that the interface can actually be used like a normal
> interface, where for the most part it cannot unless there is additional
> control logic in userspace.  I don't view it as any different from
> AT-based WWAN devices in this regard and I think treating it differently
> from them is not the right approach.

Yes, agree fully that my first draft had too much QMI logic built-in.  I
do want to keep the bare minimum to make the driver work out-of-the-box
with "ip link set dev wwanX up", but nothing more than that.

I started thinking about just exporting the raw embedded protocol as a
char device, making the exported device completely protocol agnostic
(just a wrapper around USB_CDC_SEND_ENCAPSULATED_COMMAND/
USB_CDC_GET_ENCAPSULATED_RESPONSE, ignoring the contents).  But this
would not allow multiplexing more than one client.   Therefore I added a
bit of QMI logic to allow multiple simultaneous clients, multiplexing by
the transaction ID.  This allows the wwan driver to act as a QMI client
and at the same time have multiple clients using the char device
interface.

I have a working demo of this, but the current state of the code is so
bad that I'm hesitating posting it publicly. It needs a lot of cleanup,
and I want to split out the remaining QMI parts from what's now become a
generic CDC encapsulated command/response interface.



Bjørn

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2012-01-05  8:58 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-12-13  4:33 [PATCH 0/3] Adding a new driver for Huawei E392 WWAN modem Bjorn Mork
     [not found] ` <1323750784-32608-1-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
2011-12-13  4:33   ` [PATCH 1/3] option: Removing one bogus and adding some new Huawei combinations Bjorn Mork
2011-12-13 17:16     ` Greg KH
2011-12-13  4:33   ` [PATCH 2/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific Bjorn Mork
     [not found]     ` <1323750784-32608-3-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
2011-12-13  4:54       ` Joe Perches
2011-12-13  4:59         ` Bjørn Mork
     [not found]           ` <87vcplcbj3.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>
2011-12-13 10:39             ` Sergei Shtylyov
2011-12-13  4:33   ` [PATCH 3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol Bjorn Mork
     [not found]     ` <1323750784-32608-4-git-send-email-bjorn-yOkvZcmFvRU@public.gmane.org>
2011-12-14 17:08       ` Dan Williams
     [not found]         ` <1323882501.2077.3.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>
2011-12-15 10:02           ` Bjørn Mork
2011-12-15 17:52             ` Dan Williams
     [not found]               ` <1323971530.23419.13.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>
2011-12-16 16:03                 ` Bjørn Mork
     [not found]                   ` <874nx0bj1d.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>
2011-12-16 17:17                     ` Dan Williams
     [not found]                       ` <1324055842.17587.18.camel-wKZy7rqYPVb5EHUCmHmTqw@public.gmane.org>
2012-01-04 20:57                         ` Dan Williams
2012-01-05  8:58                           ` Bjørn Mork
2011-12-13  9:02 ` [PATCH 0/3] Adding a new driver for Huawei E392 WWAN modem Bjørn Mork

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).