aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWolfgang Wiedmeyer <wolfgit@wiedmeyer.de>2017-02-05 21:44:51 +0100
committerWolfgang Wiedmeyer <wolfgit@wiedmeyer.de>2017-06-23 20:49:08 +0200
commitc91da92ef2983ab3d50f5d6e5cbaf993fece6339 (patch)
tree4eeb8952954fcdef9b21c14bc60473806a69415b
parenta46b825a2f8cabebfa2bdc44342c676c0a620905 (diff)
downloadkernel_samsung_smdk4412-c91da92ef2983ab3d50f5d6e5cbaf993fece6339.zip
kernel_samsung_smdk4412-c91da92ef2983ab3d50f5d6e5cbaf993fece6339.tar.gz
kernel_samsung_smdk4412-c91da92ef2983ab3d50f5d6e5cbaf993fece6339.tar.bz2
drivers: Backport cdc-wdm and new qmi_wwan
Backport is based on Aleksander Morgado's backport for OpenWrt to the 3.2 kernel[1], so it's a backport of a backport. I added the device IDs for MDM9x15. [1] https://gitorious.org/lanedo/linux-stable?p=lanedo:linux-stable.git;a=shortlog;h=refs/heads/v3.2.5-qmi Signed-off-by: Wolfgang Wiedmeyer <wolfgit@wiedmeyer.de>
-rw-r--r--drivers/net/usb/Kconfig21
-rw-r--r--drivers/net/usb/Makefile1
-rw-r--r--drivers/net/usb/qmi_wwan.c479
-rw-r--r--drivers/usb/class/cdc-wdm.c403
-rw-r--r--drivers/usb/core/usb.h13
-rw-r--r--include/linux/usb.h13
-rw-r--r--include/linux/usb/cdc-wdm.h19
7 files changed, 781 insertions, 168 deletions
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 4e40468..497f73a 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -395,6 +395,27 @@ config USB_NET_KALMIA
To compile this driver as a module, choose M here: the
module will be called kalmia.
+config USB_NET_QMI_WWAN
+ tristate "QMI WWAN driver for Qualcomm MSM based 3G and LTE modems"
+ depends on USB_USBNET
+ help
+ Support WWAN LTE/3G devices based on Qualcomm Mobile Data Modem
+ (MDM) chipsets. Examples of such devices are
+ * Huawei E392/E398
+
+ This driver will only drive the ethernet part of the chips.
+ The devices require additional configuration to be usable.
+ Multiple management interfaces with linux drivers are
+ available:
+
+ * option: AT commands on /dev/ttyUSBx
+ * cdc-wdm: Qualcomm MSM Interface (QMI) protocol on /dev/cdc-wdmx
+
+ A modem manager with support for QMI is recommended.
+
+ To compile this driver as a module, choose M here: the
+ module will be called qmi_wwan.
+
config USB_HSO
tristate "Option USB High Speed Mobile Devices"
depends on USB && RFKILL
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index 84228c1..336449b 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -29,5 +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
rmnet_usb-y := rmnet_usb_ctrl.o rmnet_usb_data.o
obj-$(CONFIG_MSM_RMNET_USB) += rmnet_usb.o
diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
new file mode 100644
index 0000000..7156bc7
--- /dev/null
+++ b/drivers/net/usb/qmi_wwan.c
@@ -0,0 +1,479 @@
+/*
+ * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no>
+ *
+ * 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 <linux/usb/cdc-wdm.h>
+
+/* The name of the CDC Device Management driver */
+#define DM_DRIVER "cdc_wdm"
+
+/*
+ * This driver supports wwan (3G/LTE/?) devices using a vendor
+ * specific management protocol called Qualcomm MSM Interface (QMI) -
+ * in addition to the more common AT commands over serial interface
+ * management
+ *
+ * QMI is wrapped in CDC, using CDC encapsulated commands on the
+ * control ("master") interface of a two-interface CDC Union
+ * resembling standard CDC ECM. The devices do not use the control
+ * interface for any other CDC messages. Most likely because the
+ * management protocol is used in place of the standard CDC
+ * notifications NOTIFY_NETWORK_CONNECTION and NOTIFY_SPEED_CHANGE
+ *
+ * Handling a protocol like QMI is out of the scope for any driver.
+ * It can be exported as a character device using the cdc-wdm driver,
+ * which will enable userspace applications ("modem managers") to
+ * handle it. This may be required to use the network interface
+ * provided by the driver.
+ *
+ * These devices may alternatively/additionally be configured using AT
+ * commands on any of the serial interfaces driven by the option driver
+ *
+ * This driver binds only to the data ("slave") interface to enable
+ * the cdc-wdm driver to bind to the control interface. It still
+ * parses the CDC functional descriptors on the control interface to
+ * a) verify that this is indeed a handled interface (CDC Union
+ * header lists it as slave)
+ * b) get MAC address and other ethernet config from the CDC Ethernet
+ * header
+ * c) enable user bind requests against the control interface, which
+ * is the common way to bind to CDC Ethernet Control Model type
+ * interfaces
+ * d) provide a hint to the user about which interface is the
+ * corresponding management interface
+ */
+
+static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+ int status = -1;
+ struct usb_interface *control = NULL;
+ u8 *buf = intf->cur_altsetting->extra;
+ int len = intf->cur_altsetting->extralen;
+ struct usb_interface_descriptor *desc = &intf->cur_altsetting->desc;
+ struct usb_cdc_union_desc *cdc_union = NULL;
+ struct usb_cdc_ether_desc *cdc_ether = NULL;
+ u32 required = 1 << USB_CDC_HEADER_TYPE | 1 << USB_CDC_UNION_TYPE;
+ u32 found = 0;
+ atomic_t *pmcount = (void *)&dev->data[1];
+
+ atomic_set(pmcount, 0);
+
+ /*
+ * assume a data interface has no additional descriptors and
+ * that the control and data interface are numbered
+ * consecutively - this holds for the Huawei device at least
+ */
+ if (len == 0 && desc->bInterfaceNumber > 0) {
+ control = usb_ifnum_to_if(dev->udev, desc->bInterfaceNumber - 1);
+ if (!control)
+ goto err;
+
+ buf = control->cur_altsetting->extra;
+ len = control->cur_altsetting->extralen;
+ dev_dbg(&intf->dev, "guessing \"control\" => %s, \"data\" => this\n",
+ dev_name(&control->dev));
+ }
+
+ while (len > 3) {
+ struct usb_descriptor_header *h = (void *)buf;
+
+ /* ignore any misplaced descriptors */
+ if (h->bDescriptorType != USB_DT_CS_INTERFACE)
+ goto next_desc;
+
+ /* buf[2] is CDC descriptor subtype */
+ switch (buf[2]) {
+ case USB_CDC_HEADER_TYPE:
+ if (found & 1 << USB_CDC_HEADER_TYPE) {
+ dev_dbg(&intf->dev, "extra CDC header\n");
+ goto err;
+ }
+ if (h->bLength != sizeof(struct usb_cdc_header_desc)) {
+ dev_dbg(&intf->dev, "CDC header len %u\n", h->bLength);
+ goto err;
+ }
+ break;
+ case USB_CDC_UNION_TYPE:
+ if (found & 1 << USB_CDC_UNION_TYPE) {
+ dev_dbg(&intf->dev, "extra CDC union\n");
+ goto err;
+ }
+ if (h->bLength != sizeof(struct usb_cdc_union_desc)) {
+ dev_dbg(&intf->dev, "CDC union len %u\n", h->bLength);
+ goto err;
+ }
+ cdc_union = (struct usb_cdc_union_desc *)buf;
+ break;
+ case USB_CDC_ETHERNET_TYPE:
+ if (found & 1 << USB_CDC_ETHERNET_TYPE) {
+ dev_dbg(&intf->dev, "extra CDC ether\n");
+ goto err;
+ }
+ if (h->bLength != sizeof(struct usb_cdc_ether_desc)) {
+ dev_dbg(&intf->dev, "CDC ether len %u\n", h->bLength);
+ goto err;
+ }
+ cdc_ether = (struct usb_cdc_ether_desc *)buf;
+ break;
+ }
+
+ /*
+ * Remember which CDC functional descriptors we've seen. Works
+ * for all types we care about, of which USB_CDC_ETHERNET_TYPE
+ * (0x0f) is the highest numbered
+ */
+ if (buf[2] < 32)
+ found |= 1 << buf[2];
+
+next_desc:
+ len -= h->bLength;
+ buf += h->bLength;
+ }
+
+ /* did we find all the required ones? */
+ if ((found & required) != required) {
+ dev_err(&intf->dev, "CDC functional descriptors missing\n");
+ goto err;
+ }
+
+ /* give the user a helpful hint if trying to bind to the wrong interface */
+ if (cdc_union && desc->bInterfaceNumber == cdc_union->bMasterInterface0) {
+ dev_err(&intf->dev, "leaving \"control\" interface for " DM_DRIVER " - try binding to %s instead!\n",
+ dev_name(&usb_ifnum_to_if(dev->udev, cdc_union->bSlaveInterface0)->dev));
+ goto err;
+ }
+
+ /* errors aren't fatal - we can live with the dynamic address */
+ if (cdc_ether) {
+ dev->hard_mtu = le16_to_cpu(cdc_ether->wMaxSegmentSize);
+ usbnet_get_ethernet_addr(dev, cdc_ether->iMACAddress);
+ }
+
+ /* success! point the user to the management interface */
+ if (control)
+ dev_info(&intf->dev, "Use \"" DM_DRIVER "\" for QMI interface %s\n",
+ dev_name(&control->dev));
+
+ /* XXX: add a sysfs symlink somewhere to help management applications find it? */
+
+ /* collect bulk endpoints now that we know intf == "data" interface */
+ status = usbnet_get_endpoints(dev, intf);
+
+err:
+ return status;
+}
+
+/* using a counter to merge subdriver requests with our own into a combined state */
+static int qmi_wwan_manage_power(struct usbnet *dev, int on)
+{
+ atomic_t *pmcount = (void *)&dev->data[1];
+ int rv = 0;
+
+ dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, atomic_read(pmcount), on);
+
+ if ((on && atomic_add_return(1, pmcount) == 1) || (!on && atomic_dec_and_test(pmcount))) {
+ /* need autopm_get/put here to ensure the usbcore sees the new value */
+ rv = usb_autopm_get_interface(dev->intf);
+ if (rv < 0)
+ goto err;
+ dev->intf->needs_remote_wakeup = on;
+ usb_autopm_put_interface(dev->intf);
+ }
+err:
+ return rv;
+}
+
+static int qmi_wwan_cdc_wdm_manage_power(struct usb_interface *intf, int on)
+{
+ struct usbnet *dev = usb_get_intfdata(intf);
+ return qmi_wwan_manage_power(dev, on);
+}
+
+/* Some devices combine the "control" and "data" functions into a
+ * single interface with all three endpoints: interrupt + bulk in and
+ * out
+ *
+ * Setting up cdc-wdm as a subdriver owning the interrupt endpoint
+ * will let it provide userspace access to the encapsulated QMI
+ * protocol without interfering with the usbnet operations.
+ */
+static int qmi_wwan_bind_shared(struct usbnet *dev, struct usb_interface *intf)
+{
+ int rv;
+ struct usb_driver *subdriver = NULL;
+ atomic_t *pmcount = (void *)&dev->data[1];
+
+ /* ZTE makes devices where the interface descriptors and endpoint
+ * configurations of two or more interfaces are identical, even
+ * though the functions are completely different. If set, then
+ * driver_info->data is a bitmap of acceptable interface numbers
+ * allowing us to bind to one such interface without binding to
+ * all of them
+ */
+ if (dev->driver_info->data &&
+ !test_bit(intf->cur_altsetting->desc.bInterfaceNumber, &dev->driver_info->data)) {
+ dev_info(&intf->dev, "not on our whitelist - ignored");
+ rv = -ENODEV;
+ goto err;
+ }
+
+ atomic_set(pmcount, 0);
+
+ /* collect all three endpoints */
+ rv = usbnet_get_endpoints(dev, intf);
+ if (rv < 0)
+ goto err;
+
+ /* require interrupt endpoint for subdriver */
+ if (!dev->status) {
+ rv = -EINVAL;
+ goto err;
+ }
+
+ subdriver = usb_cdc_wdm_register(intf, &dev->status->desc, 512, &qmi_wwan_cdc_wdm_manage_power);
+ if (IS_ERR(subdriver)) {
+ rv = PTR_ERR(subdriver);
+ goto err;
+ }
+
+ /* can't let usbnet use the interrupt endpoint */
+ dev->status = NULL;
+
+ /* save subdriver struct for suspend/resume wrappers */
+ dev->data[0] = (unsigned long)subdriver;
+
+err:
+ return rv;
+}
+
+/* Gobi devices uses identical class/protocol codes for all interfaces regardless
+ * of function. Some of these are CDC ACM like and have the exact same endpoints
+ * we are looking for. This leaves two possible strategies for identifying the
+ * correct interface:
+ * a) hardcoding interface number, or
+ * b) use the fact that the wwan interface is the only one lacking additional
+ * (CDC functional) descriptors
+ *
+ * Let's see if we can get away with the generic b) solution.
+ */
+static int qmi_wwan_bind_gobi(struct usbnet *dev, struct usb_interface *intf)
+{
+ int rv = -EINVAL;
+
+ /* ignore any interface with additional descriptors */
+ if (intf->cur_altsetting->extralen)
+ goto err;
+
+ rv = qmi_wwan_bind_shared(dev, intf);
+err:
+ return rv;
+}
+
+static void qmi_wwan_unbind_shared(struct usbnet *dev, struct usb_interface *intf)
+{
+ struct usb_driver *subdriver = (void *)dev->data[0];
+
+ if (subdriver && subdriver->disconnect)
+ subdriver->disconnect(intf);
+
+ dev->data[0] = (unsigned long)NULL;
+}
+
+/* suspend/resume wrappers calling both usbnet and the cdc-wdm
+ * subdriver if present.
+ *
+ * NOTE: cdc-wdm also supports pre/post_reset, but we cannot provide
+ * wrappers for those without adding usbnet reset support first.
+ */
+static int qmi_wwan_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct usbnet *dev = usb_get_intfdata(intf);
+ struct usb_driver *subdriver = (void *)dev->data[0];
+ int ret;
+
+ ret = usbnet_suspend(intf, message);
+ if (ret < 0)
+ goto err;
+
+ if (subdriver && subdriver->suspend)
+ ret = subdriver->suspend(intf, message);
+ if (ret < 0)
+ usbnet_resume(intf);
+err:
+ return ret;
+}
+
+static int qmi_wwan_resume(struct usb_interface *intf)
+{
+ struct usbnet *dev = usb_get_intfdata(intf);
+ struct usb_driver *subdriver = (void *)dev->data[0];
+ int ret = 0;
+
+ if (subdriver && subdriver->resume)
+ ret = subdriver->resume(intf);
+ if (ret < 0)
+ goto err;
+ ret = usbnet_resume(intf);
+ if (ret < 0 && subdriver && subdriver->resume && subdriver->suspend)
+ subdriver->suspend(intf, PMSG_SUSPEND);
+err:
+ return ret;
+}
+
+
+static const struct driver_info qmi_wwan_info = {
+ .description = "QMI speaking wwan device",
+ .flags = FLAG_WWAN,
+ .bind = qmi_wwan_bind,
+ .manage_power = qmi_wwan_manage_power,
+};
+
+static const struct driver_info qmi_wwan_shared = {
+ .description = "QMI speaking wwan device with combined interface",
+ .flags = FLAG_WWAN,
+ .bind = qmi_wwan_bind_shared,
+ .unbind = qmi_wwan_unbind_shared,
+ .manage_power = qmi_wwan_manage_power,
+};
+
+static const struct driver_info qmi_wwan_gobi = {
+ .description = "Qualcomm Gobi wwan/QMI device",
+ .flags = FLAG_WWAN,
+ .bind = qmi_wwan_bind_gobi,
+ .unbind = qmi_wwan_unbind_shared,
+ .manage_power = qmi_wwan_manage_power,
+};
+
+/* ZTE suck at making USB descriptors */
+static const struct driver_info qmi_wwan_force_int4 = {
+ .description = "Qualcomm Gobi wwan/QMI device",
+ .flags = FLAG_WWAN,
+ .bind = qmi_wwan_bind_gobi,
+ .unbind = qmi_wwan_unbind_shared,
+ .manage_power = qmi_wwan_manage_power,
+ .data = BIT(4), /* interface whitelist bitmap */
+};
+
+
+#define HUAWEI_VENDOR_ID 0x12D1
+#define QMI_GOBI_DEVICE(vend, prod) \
+ USB_DEVICE(vend, prod), \
+ .driver_info = (unsigned long)&qmi_wwan_gobi
+
+static const struct usb_device_id products[] = {
+ { /* Huawei E392, E398 and possibly others sharing both device id and more... */
+ .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 = 8, /* NOTE: This is the *slave* interface of the CDC Union! */
+ .driver_info = (unsigned long)&qmi_wwan_info,
+ },
+ { /* Huawei E392, E398 and possibly others in "Windows mode"
+ * using a combined control and data interface without any CDC
+ * functional descriptors
+ */
+ .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_shared,
+ },
+ { /* Pantech UML290 */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x106c,
+ .idProduct = 0x3718,
+ .bInterfaceClass = 0xff,
+ .bInterfaceSubClass = 0xf0,
+ .bInterfaceProtocol = 0xff,
+ .driver_info = (unsigned long)&qmi_wwan_shared,
+ },
+ { /* ZTE MF820D */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x19d2,
+ .idProduct = 0x0167,
+ .bInterfaceClass = 0xff,
+ .bInterfaceSubClass = 0xff,
+ .bInterfaceProtocol = 0xff,
+ .driver_info = (unsigned long)&qmi_wwan_force_int4,
+ },
+ {QMI_GOBI_DEVICE(0x05c6, 0x9212)}, /* Acer Gobi Modem Device */
+ {QMI_GOBI_DEVICE(0x03f0, 0x1f1d)}, /* HP un2400 Gobi Modem Device */
+ {QMI_GOBI_DEVICE(0x03f0, 0x371d)}, /* HP un2430 Mobile Broadband Module */
+ {QMI_GOBI_DEVICE(0x04da, 0x250d)}, /* Panasonic Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x413c, 0x8172)}, /* Dell Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x1410, 0xa001)}, /* Novatel Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x0b05, 0x1776)}, /* Asus Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x19d2, 0xfff3)}, /* ONDA Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9001)}, /* Generic Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9002)}, /* Generic Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9202)}, /* Generic Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9203)}, /* Generic Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9222)}, /* Generic Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9009)}, /* Generic Gobi Modem device */
+ {QMI_GOBI_DEVICE(0x413c, 0x8186)}, /* Dell Gobi 2000 Modem device (N0218, VU936) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x920b)}, /* Generic Gobi 2000 Modem device */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9225)}, /* Sony Gobi 2000 Modem device (N0279, VU730) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9245)}, /* Samsung Gobi 2000 Modem device (VL176) */
+ {QMI_GOBI_DEVICE(0x03f0, 0x251d)}, /* HP Gobi 2000 Modem device (VP412) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9265)}, /* Asus Gobi 2000 Modem device (VR305) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9235)}, /* Top Global Gobi 2000 Modem device (VR306) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9275)}, /* iRex Technologies Gobi 2000 Modem device (VR307) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9001)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9002)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9003)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9004)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9005)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9006)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9007)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9008)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9009)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x900a)}, /* Sierra Wireless Gobi 2000 Modem device (VT773) */
+ {QMI_GOBI_DEVICE(0x1199, 0x9011)}, /* Sierra Wireless Gobi 2000 Modem device (MC8305) */
+ {QMI_GOBI_DEVICE(0x16d8, 0x8002)}, /* CMDTech Gobi 2000 Modem device (VU922) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9205)}, /* Gobi 2000 Modem device */
+ {QMI_GOBI_DEVICE(0x1199, 0x9013)}, /* Sierra Wireless Gobi 3000 Modem device (MC8355) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x904c)}, /* MDM9x15 */
+ { } /* 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 = qmi_wwan_suspend,
+ .resume = qmi_wwan_resume,
+ .reset_resume = qmi_wwan_resume,
+ .supports_autosuspend = 1,
+};
+
+static int __init qmi_wwan_init(void)
+{
+ 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@mork.no>");
+MODULE_DESCRIPTION("Qualcomm MSM Interface (QMI) WWAN driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
index 0b85e2c..142b426 100644
--- a/drivers/usb/class/cdc-wdm.c
+++ b/drivers/usb/class/cdc-wdm.c
@@ -23,6 +23,7 @@
#include <linux/usb/cdc.h>
#include <asm/byteorder.h>
#include <asm/unaligned.h>
+#include <linux/usb/cdc-wdm.h>
/*
* Version Information
@@ -31,6 +32,8 @@
#define DRIVER_AUTHOR "Oliver Neukum"
#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
+#define HUAWEI_VENDOR_ID 0x12D1
+
static const struct usb_device_id wdm_ids[] = {
{
.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
@@ -38,6 +41,21 @@ static const struct usb_device_id wdm_ids[] = {
.bInterfaceClass = USB_CLASS_COMM,
.bInterfaceSubClass = USB_CDC_SUBCLASS_DMM
},
+ {
+ /*
+ * Huawei E392, E398 and possibly other Qualcomm based modems
+ * embed the Qualcomm QMI protocol inside CDC on CDC ECM like
+ * control interfaces. Userspace access to this is required
+ * to configure the accompanying data interface
+ */
+ .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, /* NOTE: CDC ECM control interface! */
+ },
+ {USB_DEVICE(0x05c6, 0x904c)}, /* MDM9x15 */
{ }
};
@@ -54,7 +72,7 @@ MODULE_DEVICE_TABLE (usb, wdm_ids);
#define WDM_POLL_RUNNING 6
#define WDM_RESPONDING 7
#define WDM_SUSPENDING 8
-#define WDM_OVERFLOW 10
+#define WDM_RESETTING 9
#define WDM_MAX 16
@@ -62,6 +80,8 @@ MODULE_DEVICE_TABLE (usb, wdm_ids);
#define WDM_DEFAULT_BUFSIZE 256
static DEFINE_MUTEX(wdm_mutex);
+static DEFINE_SPINLOCK(wdm_device_list_lock);
+static LIST_HEAD(wdm_device_list);
/* --- method tables --- */
@@ -83,7 +103,6 @@ struct wdm_device {
u16 bufsize;
u16 wMaxCommand;
u16 wMaxPacketSize;
- u16 bMaxPacketSize0;
__le16 inum;
int reslength;
int length;
@@ -97,10 +116,40 @@ struct wdm_device {
struct work_struct rxwork;
int werr;
int rerr;
+
+ struct list_head device_list;
+ int (*manage_power)(struct usb_interface *, int);
};
static struct usb_driver wdm_driver;
+/* return intfdata if we own the interface, else look up intf in the list */
+static struct wdm_device *wdm_find_device(struct usb_interface *intf)
+{
+ struct wdm_device *desc = NULL;
+
+ spin_lock(&wdm_device_list_lock);
+ list_for_each_entry(desc, &wdm_device_list, device_list)
+ if (desc->intf == intf)
+ break;
+ spin_unlock(&wdm_device_list_lock);
+
+ return desc;
+}
+
+static struct wdm_device *wdm_find_device_by_minor(int minor)
+{
+ struct wdm_device *desc = NULL;
+
+ spin_lock(&wdm_device_list_lock);
+ list_for_each_entry(desc, &wdm_device_list, device_list)
+ if (desc->intf->minor == minor)
+ break;
+ spin_unlock(&wdm_device_list_lock);
+
+ return desc;
+}
+
/* --- callbacks --- */
static void wdm_out_callback(struct urb *urb)
{
@@ -119,7 +168,6 @@ static void wdm_in_callback(struct urb *urb)
{
struct wdm_device *desc = urb->context;
int status = urb->status;
- int length = urb->actual_length;
spin_lock(&desc->iuspin);
clear_bit(WDM_RESPONDING, &desc->flags);
@@ -150,17 +198,9 @@ static void wdm_in_callback(struct urb *urb)
}
desc->rerr = status;
- if (length + desc->length > desc->wMaxCommand) {
- /* The buffer would overflow */
- set_bit(WDM_OVERFLOW, &desc->flags);
- } else {
- /* we may already be in overflow */
- if (!test_bit(WDM_OVERFLOW, &desc->flags)) {
- memmove(desc->ubuf + desc->length, desc->inbuf, length);
- desc->length += length;
- desc->reslength = length;
- }
- }
+ desc->reslength = urb->actual_length;
+ memmove(desc->ubuf + desc->length, desc->inbuf, desc->reslength);
+ desc->length += desc->reslength;
skip_error:
wake_up(&desc->wait);
@@ -171,14 +211,11 @@ skip_error:
static void wdm_int_callback(struct urb *urb)
{
int rv = 0;
- int responding;
int status = urb->status;
struct wdm_device *desc;
- struct usb_ctrlrequest *req;
struct usb_cdc_notification *dr;
desc = urb->context;
- req = desc->irq;
dr = (struct usb_cdc_notification *)desc->sbuf;
if (status) {
@@ -225,28 +262,10 @@ static void wdm_int_callback(struct urb *urb)
goto exit;
}
- req->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
- req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
- req->wValue = 0;
- req->wIndex = desc->inum;
- req->wLength = cpu_to_le16(desc->wMaxCommand);
-
- usb_fill_control_urb(
- desc->response,
- interface_to_usbdev(desc->intf),
- /* using common endpoint 0 */
- usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0),
- (unsigned char *)req,
- desc->inbuf,
- desc->wMaxCommand,
- wdm_in_callback,
- desc
- );
- desc->response->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
spin_lock(&desc->iuspin);
clear_bit(WDM_READ, &desc->flags);
- responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
- if (!responding && !test_bit(WDM_DISCONNECTING, &desc->flags)
+ set_bit(WDM_RESPONDING, &desc->flags);
+ if (!test_bit(WDM_DISCONNECTING, &desc->flags)
&& !test_bit(WDM_SUSPENDING, &desc->flags)) {
rv = usb_submit_urb(desc->response, GFP_ATOMIC);
dev_dbg(&desc->intf->dev, "%s: usb_submit_urb %d",
@@ -291,14 +310,8 @@ static void free_urbs(struct wdm_device *desc)
static void cleanup(struct wdm_device *desc)
{
- usb_free_coherent(interface_to_usbdev(desc->intf),
- desc->wMaxPacketSize,
- desc->sbuf,
- desc->validity->transfer_dma);
- usb_free_coherent(interface_to_usbdev(desc->intf),
- desc->bMaxPacketSize0,
- desc->inbuf,
- desc->response->transfer_dma);
+ kfree(desc->sbuf);
+ kfree(desc->inbuf);
kfree(desc->orq);
kfree(desc->irq);
kfree(desc->ubuf);
@@ -354,6 +367,7 @@ static ssize_t wdm_write
r = usb_autopm_get_interface(desc->intf);
if (r < 0) {
kfree(buf);
+ rv = usb_translate_errors(r);
goto outnp;
}
@@ -363,8 +377,13 @@ static ssize_t wdm_write
else
if (test_bit(WDM_IN_USE, &desc->flags))
r = -EAGAIN;
+
+ if (test_bit(WDM_RESETTING, &desc->flags))
+ r = -EIO;
+
if (r < 0) {
kfree(buf);
+ rv = r;
goto out;
}
@@ -396,6 +415,7 @@ static ssize_t wdm_write
desc->outbuf = NULL;
clear_bit(WDM_IN_USE, &desc->flags);
dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv);
+ rv = usb_translate_errors(rv);
} else {
dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d",
req->wIndex);
@@ -428,11 +448,6 @@ retry:
rv = -ENODEV;
goto err;
}
- if (test_bit(WDM_OVERFLOW, &desc->flags)) {
- clear_bit(WDM_OVERFLOW, &desc->flags);
- rv = -ENOBUFS;
- goto err;
- }
i++;
if (file->f_flags & O_NONBLOCK) {
if (!test_bit(WDM_READ, &desc->flags)) {
@@ -450,6 +465,10 @@ retry:
rv = -ENODEV;
goto err;
}
+ if (test_bit(WDM_RESETTING, &desc->flags)) {
+ rv = -EIO;
+ goto err;
+ }
usb_mark_last_busy(interface_to_usbdev(desc->intf));
if (rv < 0) {
rv = -ERESTARTSYS;
@@ -472,10 +491,7 @@ retry:
spin_unlock_irq(&desc->iuspin);
goto retry;
}
-
if (!desc->reslength) { /* zero length read */
- dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__);
- clear_bit(WDM_READ, &desc->flags);
spin_unlock_irq(&desc->iuspin);
goto retry;
}
@@ -515,11 +531,13 @@ static int wdm_flush(struct file *file, fl_owner_t id)
struct wdm_device *desc = file->private_data;
wait_event(desc->wait, !test_bit(WDM_IN_USE, &desc->flags));
- if (desc->werr < 0)
+
+ /* cannot dereference desc->intf if WDM_DISCONNECTING */
+ if (desc->werr < 0 && !test_bit(WDM_DISCONNECTING, &desc->flags))
dev_err(&desc->intf->dev, "Error in flush path: %d\n",
desc->werr);
- return desc->werr;
+ return usb_translate_errors(desc->werr);
}
static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait)
@@ -556,11 +574,11 @@ static int wdm_open(struct inode *inode, struct file *file)
struct wdm_device *desc;
mutex_lock(&wdm_mutex);
- intf = usb_find_interface(&wdm_driver, minor);
- if (!intf)
+ desc = wdm_find_device_by_minor(minor);
+ if (!desc)
goto out;
- desc = usb_get_intfdata(intf);
+ intf = desc->intf;
if (test_bit(WDM_DISCONNECTING, &desc->flags))
goto out;
file->private_data = desc;
@@ -570,7 +588,6 @@ static int wdm_open(struct inode *inode, struct file *file)
dev_err(&desc->intf->dev, "Error autopm - %d\n", rv);
goto out;
}
- intf->needs_remote_wakeup = 1;
/* using write lock to protect desc->count */
mutex_lock(&desc->wlock);
@@ -582,11 +599,14 @@ static int wdm_open(struct inode *inode, struct file *file)
desc->count--;
dev_err(&desc->intf->dev,
"Error submitting int urb - %d\n", rv);
+ rv = usb_translate_errors(rv);
}
} else {
rv = 0;
}
mutex_unlock(&desc->wlock);
+ if (desc->count == 1)
+ desc->manage_power(intf, 1);
usb_autopm_put_interface(desc->intf);
out:
mutex_unlock(&wdm_mutex);
@@ -605,10 +625,15 @@ static int wdm_release(struct inode *inode, struct file *file)
mutex_unlock(&desc->wlock);
if (!desc->count) {
- dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
- kill_urbs(desc);
- if (!test_bit(WDM_DISCONNECTING, &desc->flags))
- desc->intf->needs_remote_wakeup = 0;
+ if (!test_bit(WDM_DISCONNECTING, &desc->flags)) {
+ dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
+ kill_urbs(desc);
+ desc->manage_power(desc->intf, 0);
+ } else {
+ /* must avoid dev_printk here as desc->intf is invalid */
+ pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__);
+ cleanup(desc);
+ }
}
mutex_unlock(&wdm_mutex);
return 0;
@@ -636,20 +661,16 @@ static void wdm_rxwork(struct work_struct *work)
{
struct wdm_device *desc = container_of(work, struct wdm_device, rxwork);
unsigned long flags;
- int rv = 0;
- int responding;
+ int rv;
spin_lock_irqsave(&desc->iuspin, flags);
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
spin_unlock_irqrestore(&desc->iuspin, flags);
} else {
- responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
spin_unlock_irqrestore(&desc->iuspin, flags);
- if (!responding)
- rv = usb_submit_urb(desc->response, GFP_KERNEL);
+ rv = usb_submit_urb(desc->response, GFP_KERNEL);
if (rv < 0 && rv != -EPERM) {
spin_lock_irqsave(&desc->iuspin, flags);
- clear_bit(WDM_RESPONDING, &desc->flags);
if (!test_bit(WDM_DISCONNECTING, &desc->flags))
schedule_work(&desc->rxwork);
spin_unlock_irqrestore(&desc->iuspin, flags);
@@ -659,71 +680,31 @@ static void wdm_rxwork(struct work_struct *work)
/* --- hotplug --- */
-static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
+static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep,
+ u16 bufsize, int (*manage_power)(struct usb_interface *, int))
{
- int rv = -EINVAL;
- struct usb_device *udev = interface_to_usbdev(intf);
+ int rv = -ENOMEM;
struct wdm_device *desc;
- struct usb_host_interface *iface;
- struct usb_endpoint_descriptor *ep;
- struct usb_cdc_dmm_desc *dmhd;
- u8 *buffer = intf->altsetting->extra;
- int buflen = intf->altsetting->extralen;
- u16 maxcom = WDM_DEFAULT_BUFSIZE;
-
- if (!buffer)
- goto out;
-
- while (buflen > 2) {
- if (buffer [1] != USB_DT_CS_INTERFACE) {
- dev_err(&intf->dev, "skipping garbage\n");
- goto next_desc;
- }
- switch (buffer [2]) {
- case USB_CDC_HEADER_TYPE:
- break;
- case USB_CDC_DMM_TYPE:
- dmhd = (struct usb_cdc_dmm_desc *)buffer;
- maxcom = le16_to_cpu(dmhd->wMaxCommand);
- dev_dbg(&intf->dev,
- "Finding maximum buffer length: %d", maxcom);
- break;
- default:
- dev_err(&intf->dev,
- "Ignoring extra header, type %d, length %d\n",
- buffer[2], buffer[0]);
- break;
- }
-next_desc:
- buflen -= buffer[0];
- buffer += buffer[0];
- }
-
- rv = -ENOMEM;
desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL);
if (!desc)
goto out;
+ INIT_LIST_HEAD(&desc->device_list);
mutex_init(&desc->rlock);
mutex_init(&desc->wlock);
spin_lock_init(&desc->iuspin);
init_waitqueue_head(&desc->wait);
- desc->wMaxCommand = maxcom;
+ desc->wMaxCommand = bufsize;
/* this will be expanded and needed in hardware endianness */
desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber);
desc->intf = intf;
INIT_WORK(&desc->rxwork, wdm_rxwork);
rv = -EINVAL;
- iface = intf->cur_altsetting;
- if (iface->desc.bNumEndpoints != 1)
- goto err;
- ep = &iface->endpoint[0].desc;
- if (!ep || !usb_endpoint_is_int_in(ep))
+ if (!usb_endpoint_is_int_in(ep))
goto err;
desc->wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize);
- desc->bMaxPacketSize0 = udev->descriptor.bMaxPacketSize0;
desc->orq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
if (!desc->orq)
@@ -748,19 +729,13 @@ next_desc:
if (!desc->ubuf)
goto err;
- desc->sbuf = usb_alloc_coherent(interface_to_usbdev(intf),
- desc->wMaxPacketSize,
- GFP_KERNEL,
- &desc->validity->transfer_dma);
+ desc->sbuf = kmalloc(desc->wMaxPacketSize, GFP_KERNEL);
if (!desc->sbuf)
goto err;
- desc->inbuf = usb_alloc_coherent(interface_to_usbdev(intf),
- desc->wMaxCommand,
- GFP_KERNEL,
- &desc->response->transfer_dma);
+ desc->inbuf = kmalloc(desc->wMaxCommand, GFP_KERNEL);
if (!desc->inbuf)
- goto err2;
+ goto err;
usb_fill_int_urb(
desc->validity,
@@ -772,45 +747,152 @@ next_desc:
desc,
ep->bInterval
);
- desc->validity->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
- usb_set_intfdata(intf, desc);
+ desc->irq->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
+ desc->irq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
+ desc->irq->wValue = 0;
+ desc->irq->wIndex = desc->inum;
+ desc->irq->wLength = cpu_to_le16(desc->wMaxCommand);
+
+ usb_fill_control_urb(
+ desc->response,
+ interface_to_usbdev(intf),
+ /* using common endpoint 0 */
+ usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0),
+ (unsigned char *)desc->irq,
+ desc->inbuf,
+ desc->wMaxCommand,
+ wdm_in_callback,
+ desc
+ );
+
+ desc->manage_power = manage_power;
+
+ spin_lock(&wdm_device_list_lock);
+ list_add(&desc->device_list, &wdm_device_list);
+ spin_unlock(&wdm_device_list_lock);
+
rv = usb_register_dev(intf, &wdm_class);
if (rv < 0)
- goto err3;
+ goto err;
else
- dev_info(&intf->dev, "cdc-wdm%d: USB WDM device\n",
- intf->minor - WDM_MINOR_BASE);
+ dev_info(&intf->dev, "%s: USB WDM device\n", dev_name(intf->usb_dev));
out:
return rv;
-err3:
- usb_set_intfdata(intf, NULL);
- usb_free_coherent(interface_to_usbdev(desc->intf),
- desc->bMaxPacketSize0,
- desc->inbuf,
- desc->response->transfer_dma);
-err2:
- usb_free_coherent(interface_to_usbdev(desc->intf),
- desc->wMaxPacketSize,
- desc->sbuf,
- desc->validity->transfer_dma);
err:
- free_urbs(desc);
- kfree(desc->ubuf);
- kfree(desc->orq);
- kfree(desc->irq);
- kfree(desc);
+ spin_lock(&wdm_device_list_lock);
+ list_del(&desc->device_list);
+ spin_unlock(&wdm_device_list_lock);
+ cleanup(desc);
+ return rv;
+}
+
+static int wdm_manage_power(struct usb_interface *intf, int on)
+{
+ /* need autopm_get/put here to ensure the usbcore sees the new value */
+ int rv = usb_autopm_get_interface(intf);
+ if (rv < 0)
+ goto err;
+
+ intf->needs_remote_wakeup = on;
+ usb_autopm_put_interface(intf);
+err:
+ return rv;
+}
+
+static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ int rv = -EINVAL;
+ struct usb_host_interface *iface;
+ struct usb_endpoint_descriptor *ep;
+ struct usb_cdc_dmm_desc *dmhd;
+ u8 *buffer = intf->altsetting->extra;
+ int buflen = intf->altsetting->extralen;
+ u16 maxcom = WDM_DEFAULT_BUFSIZE;
+
+ if (!buffer)
+ goto err;
+ while (buflen > 2) {
+ if (buffer[1] != USB_DT_CS_INTERFACE) {
+ dev_err(&intf->dev, "skipping garbage\n");
+ goto next_desc;
+ }
+
+ switch (buffer[2]) {
+ case USB_CDC_HEADER_TYPE:
+ break;
+ case USB_CDC_DMM_TYPE:
+ dmhd = (struct usb_cdc_dmm_desc *)buffer;
+ maxcom = le16_to_cpu(dmhd->wMaxCommand);
+ dev_dbg(&intf->dev,
+ "Finding maximum buffer length: %d", maxcom);
+ break;
+ default:
+ dev_err(&intf->dev,
+ "Ignoring extra header, type %d, length %d\n",
+ buffer[2], buffer[0]);
+ break;
+ }
+next_desc:
+ buflen -= buffer[0];
+ buffer += buffer[0];
+ }
+
+ iface = intf->cur_altsetting;
+ if (iface->desc.bNumEndpoints != 1)
+ goto err;
+ ep = &iface->endpoint[0].desc;
+
+ rv = wdm_create(intf, ep, maxcom, &wdm_manage_power);
+
+err:
return rv;
}
+/**
+ * usb_cdc_wdm_register - register a WDM subdriver
+ * @intf: usb interface the subdriver will associate with
+ * @ep: interrupt endpoint to monitor for notifications
+ * @bufsize: maximum message size to support for read/write
+ *
+ * Create WDM usb class character device and associate it with intf
+ * without binding, allowing another driver to manage the interface.
+ *
+ * The subdriver will manage the given interrupt endpoint exclusively
+ * and will issue control requests referring to the given intf. It
+ * will otherwise avoid interferring, and in particular not do
+ * usb_set_intfdata/usb_get_intfdata on intf.
+ *
+ * The return value is a pointer to the subdriver's struct usb_driver.
+ * The registering driver is responsible for calling this subdriver's
+ * disconnect, suspend, resume, pre_reset and post_reset methods from
+ * its own.
+ */
+struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf,
+ struct usb_endpoint_descriptor *ep,
+ int bufsize,
+ int (*manage_power)(struct usb_interface *, int))
+{
+ int rv = -EINVAL;
+
+ rv = wdm_create(intf, ep, bufsize, manage_power);
+ if (rv < 0)
+ goto err;
+
+ return &wdm_driver;
+err:
+ return ERR_PTR(rv);
+}
+EXPORT_SYMBOL(usb_cdc_wdm_register);
+
static void wdm_disconnect(struct usb_interface *intf)
{
struct wdm_device *desc;
unsigned long flags;
usb_deregister_dev(intf, &wdm_class);
+ desc = wdm_find_device(intf);
mutex_lock(&wdm_mutex);
- desc = usb_get_intfdata(intf);
/* the spinlock makes sure no new urbs are generated in the callbacks */
spin_lock_irqsave(&desc->iuspin, flags);
@@ -826,15 +908,23 @@ static void wdm_disconnect(struct usb_interface *intf)
cancel_work_sync(&desc->rxwork);
mutex_unlock(&desc->wlock);
mutex_unlock(&desc->rlock);
+
+ /* the desc->intf pointer used as list key is now invalid */
+ spin_lock(&wdm_device_list_lock);
+ list_del(&desc->device_list);
+ spin_unlock(&wdm_device_list_lock);
+
if (!desc->count)
cleanup(desc);
+ else
+ dev_dbg(&intf->dev, "%s: %d open files - postponing cleanup\n", __func__, desc->count);
mutex_unlock(&wdm_mutex);
}
#ifdef CONFIG_PM
static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
{
- struct wdm_device *desc = usb_get_intfdata(intf);
+ struct wdm_device *desc = wdm_find_device(intf);
int rv = 0;
dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor);
@@ -884,7 +974,7 @@ static int recover_from_urb_loss(struct wdm_device *desc)
#ifdef CONFIG_PM
static int wdm_resume(struct usb_interface *intf)
{
- struct wdm_device *desc = usb_get_intfdata(intf);
+ struct wdm_device *desc = wdm_find_device(intf);
int rv;
dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor);
@@ -898,11 +988,7 @@ static int wdm_resume(struct usb_interface *intf)
static int wdm_pre_reset(struct usb_interface *intf)
{
- struct wdm_device *desc = usb_get_intfdata(intf);
-
- mutex_lock(&desc->rlock);
- mutex_lock(&desc->wlock);
- kill_urbs(desc);
+ struct wdm_device *desc = wdm_find_device(intf);
/*
* we notify everybody using poll of
@@ -911,18 +997,25 @@ static int wdm_pre_reset(struct usb_interface *intf)
* message from the device is lost
*/
spin_lock_irq(&desc->iuspin);
+ set_bit(WDM_RESETTING, &desc->flags); /* inform read/write */
+ set_bit(WDM_READ, &desc->flags); /* unblock read */
+ clear_bit(WDM_IN_USE, &desc->flags); /* unblock write */
desc->rerr = -EINTR;
spin_unlock_irq(&desc->iuspin);
wake_up_all(&desc->wait);
+ mutex_lock(&desc->rlock);
+ mutex_lock(&desc->wlock);
+ kill_urbs(desc);
+ cancel_work_sync(&desc->rxwork);
return 0;
}
static int wdm_post_reset(struct usb_interface *intf)
{
- struct wdm_device *desc = usb_get_intfdata(intf);
+ struct wdm_device *desc = wdm_find_device(intf);
int rv;
- clear_bit(WDM_OVERFLOW, &desc->flags);
+ clear_bit(WDM_RESETTING, &desc->flags);
rv = recover_from_urb_loss(desc);
mutex_unlock(&desc->wlock);
mutex_unlock(&desc->rlock);
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index d44d4b7..2f6c142 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -125,19 +125,6 @@ static inline int is_usb_device_driver(struct device_driver *drv)
for_devices;
}
-/* translate USB error codes to codes user space understands */
-static inline int usb_translate_errors(int error_code)
-{
- switch (error_code) {
- case 0:
- case -ENOMEM:
- case -ENODEV:
- return error_code;
- default:
- return -EIO;
- }
-}
-
/* for labeling diagnostics */
extern const char *usbcore_name;
diff --git a/include/linux/usb.h b/include/linux/usb.h
index eecfc67e..b639d84 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -1605,6 +1605,19 @@ usb_maxpacket(struct usb_device *udev, int pipe, int is_out)
/* ----------------------------------------------------------------------- */
+/* translate USB error codes to codes user space understands */
+static inline int usb_translate_errors(int error_code)
+{
+ switch (error_code) {
+ case 0:
+ case -ENOMEM:
+ case -ENODEV:
+ return error_code;
+ default:
+ return -EIO;
+ }
+}
+
/* Events from the usb core */
#define USB_DEVICE_ADD 0x0001
#define USB_DEVICE_REMOVE 0x0002
diff --git a/include/linux/usb/cdc-wdm.h b/include/linux/usb/cdc-wdm.h
new file mode 100644
index 0000000..719c332
--- /dev/null
+++ b/include/linux/usb/cdc-wdm.h
@@ -0,0 +1,19 @@
+/*
+ * USB CDC Device Management subdriver
+ *
+ * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no>
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_USB_CDC_WDM_H
+#define __LINUX_USB_CDC_WDM_H
+
+extern struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf,
+ struct usb_endpoint_descriptor *ep,
+ int bufsize,
+ int (*manage_power)(struct usb_interface *, int));
+
+#endif /* __LINUX_USB_CDC_WDM_H */