* [PATCH v2 0/3] Adding new drivers for Qualcomm MSM based LTE modems
@ 2012-01-15 6:40 Bjørn Mork
[not found] ` <cover.1326607234.git.bjorn-yOkvZcmFvRU@public.gmane.org>
0 siblings, 1 reply; 8+ messages in thread
From: Bjørn Mork @ 2012-01-15 6:40 UTC (permalink / raw)
To: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA
Cc: Bjørn Mork, Dan Williams, Thomas Schäfer
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 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 2 adds a new "subdriver" which exports the CDC encapsulated
command interface as a character device, and also to the calling
driver.
patch 3 adds the wwan driver, which uses the "subdriver" to configure
the device via the Qualcomm MSM Interface (QMI) protocol encapsulated
in CDC control commands. The actual network device is a minimalistic
usbnet minidriver.
Bjørn Mork (3):
cdc_ether: allow vendor specific data interface if control interface
is vendor specific
net: usb: cdc_enc: New driver exposing USB CDC encapsulated protocols
net: usb: qmi_wwan: New driver for QMI WAN devices
drivers/net/usb/Kconfig | 28 +++
drivers/net/usb/Makefile | 3 +
drivers/net/usb/cdc_enc.c | 452 +++++++++++++++++++++++++++++++++++++
drivers/net/usb/cdc_enc.h | 77 +++++++
drivers/net/usb/cdc_ether.c | 6 +-
drivers/net/usb/qmi_proto.c | 475 +++++++++++++++++++++++++++++++++++++++
drivers/net/usb/qmi_proto.h | 98 ++++++++
drivers/net/usb/qmi_wwan_core.c | 343 ++++++++++++++++++++++++++++
8 files changed, 1481 insertions(+), 1 deletions(-)
create mode 100644 drivers/net/usb/cdc_enc.c
create mode 100644 drivers/net/usb/cdc_enc.h
create mode 100644 drivers/net/usb/qmi_proto.c
create mode 100644 drivers/net/usb/qmi_proto.h
create mode 100644 drivers/net/usb/qmi_wwan_core.c
--
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 [flat|nested] 8+ messages in thread
* [PATCH v2 1/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific
[not found] ` <cover.1326607234.git.bjorn-yOkvZcmFvRU@public.gmane.org>
@ 2012-01-15 6:40 ` Bjørn Mork
2012-01-15 6:40 ` [PATCH v2 2/3] net: usb: cdc_enc: New driver exposing USB CDC encapsulated protocols Bjørn Mork
2012-01-15 6:40 ` [PATCH v2 3/3] net: usb: qmi_wwan: New driver for QMI WAN devices Bjørn Mork
2 siblings, 0 replies; 8+ messages in thread
From: Bjørn Mork @ 2012-01-15 6:40 UTC (permalink / raw)
To: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA
Cc: Bjørn Mork, Dan Williams, Thomas Schäfer, Joe Perches
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>
---
Changes from v1: Removed an unintentional debug message change. Thanks to
Joe Perches for pointing that out
drivers/net/usb/cdc_ether.c | 6 +++++-
1 files changed, 5 insertions(+), 1 deletions(-)
diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c
index 41a61ef..a14a4a3 100644
--- a/drivers/net/usb/cdc_ether.c
+++ b/drivers/net/usb/cdc_ether.c
@@ -211,7 +211,11 @@ 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) {
+ 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, "slave 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] 8+ messages in thread
* [PATCH v2 2/3] net: usb: cdc_enc: New driver exposing USB CDC encapsulated protocols
[not found] ` <cover.1326607234.git.bjorn-yOkvZcmFvRU@public.gmane.org>
2012-01-15 6:40 ` [PATCH v2 1/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific Bjørn Mork
@ 2012-01-15 6:40 ` Bjørn Mork
[not found] ` <d11888532ee381806b35974e71af4c0122255c05.1326607234.git.bjorn-yOkvZcmFvRU@public.gmane.org>
2012-01-15 6:40 ` [PATCH v2 3/3] net: usb: qmi_wwan: New driver for QMI WAN devices Bjørn Mork
2 siblings, 1 reply; 8+ messages in thread
From: Bjørn Mork @ 2012-01-15 6:40 UTC (permalink / raw)
To: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA
Cc: Bjørn Mork, Dan Williams, Thomas Schäfer
USB CDC Ethernet devices may encapsulate vendor specific protocols inside
USB_CDC_SEND_ENCAPSULATED_COMMAND +USB_CDC_GET_ENCAPSULATED_RESPONSE
control messages. Devices based on Qualcomm MSM
chipsets are known to use this for exporting the Qualcomm
MSM Interface (QMI) protocol. Examples of such devices are
the Huawei E392 and E398 LTE modems.
This is only a subdriver which needs to be linked from a real CDC
ethernet driver like the QMI WWAN driver. The basic idea is that
the USB ethernet driver will know what protocol, if any, the
devices supports cia CDC encapsulated commands. It calls this
driver to register it, naming the protocol, while this driver
is completely protocol agnostic.
The encapsulated protocol is exported as a character device, and
the protocol name is made available to userspace as a sysfs
attribute.
Signed-off-by: Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>
---
Changes from v1: Completely rewritten and this generic part has been
split out as a separate subdriver. It currently needs to be called
from another usbnet based driver, and will abuse the status
interrupt part of that for its own needs.
drivers/net/usb/Kconfig | 14 ++
drivers/net/usb/Makefile | 1 +
drivers/net/usb/cdc_enc.c | 452 +++++++++++++++++++++++++++++++++++++++++++++
drivers/net/usb/cdc_enc.h | 77 ++++++++
4 files changed, 544 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/usb/cdc_enc.c
create mode 100644 drivers/net/usb/cdc_enc.h
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 2335761..1425b08 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -461,4 +461,18 @@ config USB_VL600
http://ubuntuforums.org/showpost.php?p=10589647&postcount=17
+config USB_NET_CDC_ENC
+ tristate "CDC encapsulated command helper"
+ help
+ USB CDC Ethernet devices may encapsulate vendor specific
+ protocols inside CDC commands. Devices based on Qualcomm MSM
+ chipsets are known to use this for exporting the Qualcomm
+ MSM Interface (QMI) protocol. Examples of such devices are
+ the Huawei E392 and E398 LTE modems.
+
+ This is only a subdriver which needs to be linked from a
+ real CDC Ethernet driver like the QMI WWAN driver.
+
+ The encapsulated protocol is exported as a character device.
+
endmenu
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index c203fa2..1edfc1b 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -29,4 +29,5 @@ 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_CDC_ENC) += cdc_enc.o
diff --git a/drivers/net/usb/cdc_enc.c b/drivers/net/usb/cdc_enc.c
new file mode 100644
index 0000000..c3bff289
--- /dev/null
+++ b/drivers/net/usb/cdc_enc.c
@@ -0,0 +1,452 @@
+/*
+ * Copyright (c) 2012 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/cdev.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/completion.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include "cdc_enc.h"
+
+/* copy data to the client local buffer */
+static void cdc_enc_copy_to_client(struct cdc_enc_client *client, void *data, size_t len)
+{
+ /* someone is using the RX buffer - ignore data */
+ if (test_and_set_bit(CDC_ENC_CLIENT_BUSY, &client->flags))
+ return;
+
+ /* refusing to overwrite unread data */
+ if (test_and_set_bit(CDC_ENC_CLIENT_RX, &client->flags))
+ goto err_oflow;
+
+ memcpy(client->buf, data, len);
+ client->len = len;
+
+err_oflow:
+ clear_bit(CDC_ENC_CLIENT_BUSY, &client->flags);
+ schedule_work(&client->work);
+}
+
+/* copy data to all clients */
+static void cdc_enc_copy_to_all(struct cdc_enc_state *cdc_enc, void *data, size_t len)
+{
+ struct cdc_enc_client *tmp;
+
+ list_for_each_entry(tmp, &cdc_enc->clients, list)
+ cdc_enc_copy_to_client(tmp, data, len);
+}
+
+/* URB callback, copying common recv buffer to all clients */
+static void cdc_enc_read_callback(struct urb *urb)
+{
+ /* silently ignoring any errors */
+ if (urb->status == 0 && urb->actual_length > 0)
+ cdc_enc_copy_to_all((struct cdc_enc_state *)urb->context, urb->transfer_buffer, urb->actual_length);
+}
+
+/* make sure the interrupt URB is queued by forcibly resubmitting it */
+static void cdc_enc_status_urb(struct cdc_enc_state *cdc_enc, int kmalloc_flags)
+{
+ if (cdc_enc->interrupt)
+ usb_submit_urb(cdc_enc->interrupt, kmalloc_flags);
+}
+
+/* submit a command */
+int cdc_enc_send_sync(struct cdc_enc_client *client, unsigned char *msg, size_t len)
+{
+ int status;
+ struct cdc_enc_state *cdc_enc = client->cdc_enc;
+ struct usb_device *udev = interface_to_usbdev(cdc_enc->intf);
+
+ /* enable status even if networking is down */
+ cdc_enc_status_urb(cdc_enc, GFP_KERNEL);
+
+ status = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ USB_CDC_SEND_ENCAPSULATED_COMMAND, USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
+ 0, cdc_enc->intf->cur_altsetting->desc.bInterfaceNumber,
+ msg, len, 1000);
+
+ /* copy successfully transmitted data to all clients, including ourselves */
+ if (status == len)
+ cdc_enc_copy_to_all(cdc_enc, msg, len);
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(cdc_enc_send_sync);
+
+struct cdc_enc_client *cdc_enc_add_client(struct cdc_enc_state *cdc_enc, work_func_t recv_callback)
+{
+ struct cdc_enc_client *client;
+
+ client = kzalloc(sizeof(struct cdc_enc_client), GFP_KERNEL);
+ if (!client)
+ goto done;
+
+ client->cdc_enc = cdc_enc;
+ init_completion(&client->ready);
+
+ /* setup callback */
+ INIT_WORK(&client->work, recv_callback);
+
+ mutex_lock(&cdc_enc->clients_lock);
+ list_add(&client->list, &cdc_enc->clients);
+ mutex_unlock(&cdc_enc->clients_lock);
+
+done:
+ return client;
+}
+EXPORT_SYMBOL_GPL(cdc_enc_add_client);
+
+void cdc_enc_destroy_client(struct cdc_enc_client *client)
+{
+ struct cdc_enc_state *cdc_enc = client->cdc_enc;
+
+ mutex_lock(&cdc_enc->clients_lock);
+ cancel_work_sync(&client->work);
+ list_del(&client->list);
+ kfree(client);
+ mutex_unlock(&cdc_enc->clients_lock);
+
+ wake_up(&cdc_enc->waitq);
+}
+EXPORT_SYMBOL_GPL(cdc_enc_destroy_client);
+
+static ssize_t cdc_enc_fops_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
+{
+ struct cdc_enc_client *client = file->private_data;
+ int ret = -EFAULT;
+
+ if (!client || !client->cdc_enc)
+ return -ENODEV;
+
+ while (!test_bit(CDC_ENC_CLIENT_RX, &client->flags)) {/* no data */
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ if (wait_for_completion_interruptible(&client->ready) < 0)
+ return -EINTR;
+
+ /* shutdown requested? */
+ if (test_bit(CDC_ENC_CLIENT_SHUTDOWN, &client->flags))
+ return -ENXIO;
+ }
+
+ /* someone else is using our buffer */
+ if (test_and_set_bit(CDC_ENC_CLIENT_BUSY, &client->flags))
+ return -ERESTARTSYS;
+
+ /* must read a complete packet */
+ if (client->len > size || copy_to_user(buf, client->buf, client->len)) {
+ ret = -EFAULT;
+ goto err;
+ }
+ ret = client->len;
+
+err:
+ /* order is important! */
+ clear_bit(CDC_ENC_CLIENT_RX, &client->flags);
+ clear_bit(CDC_ENC_CLIENT_BUSY, &client->flags);
+
+ return ret;
+}
+
+static ssize_t cdc_enc_fops_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
+{
+ struct cdc_enc_client *client = file->private_data;
+ int ret = -EFAULT;
+
+ if (!client || !client->cdc_enc)
+ return -ENODEV;
+
+ /* shutdown requested? */
+ if (test_bit(CDC_ENC_CLIENT_SHUTDOWN, &client->flags))
+ return -ENXIO;
+
+ /* no meaning in attempting to send an incomplete packet */
+ if (size > sizeof(client->tx_buf))
+ return -EFAULT;
+
+ /* are someone else using our buffer? */
+ if (test_and_set_bit(CDC_ENC_CLIENT_TX, &client->flags))
+ return -ERESTARTSYS;
+
+ if (size > sizeof(client->tx_buf) || copy_from_user(client->tx_buf, buf, size))
+ goto err;
+
+ /* send to the device */
+ ret = cdc_enc_send_sync(client, client->tx_buf, size);
+ if (ret < 0)
+ return -EFAULT;
+
+err:
+ clear_bit(CDC_ENC_CLIENT_TX, &client->flags);
+ return ret;
+}
+
+/* receive callback for character device */
+static void cdc_enc_newdata_rcvd(struct work_struct *work)
+{
+ struct cdc_enc_client *client = container_of(work, struct cdc_enc_client, work);
+
+ /* signal new data available to any waiting reader */
+ complete(&client->ready);
+}
+
+static int cdc_enc_fops_open(struct inode *inode, struct file *file)
+{
+ struct cdc_enc_state *cdc_enc;
+ struct cdc_enc_client *client;
+
+ /* associate the file with our backing CDC_ENC device */
+ cdc_enc = container_of(inode->i_cdev, struct cdc_enc_state, cdev);
+ if (!cdc_enc)
+ return -ENODEV;
+
+ /* don't allow interface to sleep while we are using it */
+ usb_autopm_get_interface(cdc_enc->intf);
+
+ /* enable status URB even if networking is down */
+ cdc_enc_status_urb(cdc_enc, GFP_KERNEL);
+
+ /* set up a ring buffer to receive our readable data? */
+ client = cdc_enc_add_client(cdc_enc, cdc_enc_newdata_rcvd);
+
+ if (!client)
+ return -ENOMEM;
+
+ file->private_data = client;
+ return 0;
+}
+
+static int cdc_enc_fops_release(struct inode *inode, struct file *file)
+{
+ struct cdc_enc_client *client = file->private_data;
+ struct cdc_enc_state *cdc_enc = client->cdc_enc;
+
+ if (!client || !cdc_enc)
+ return -ENODEV;
+
+ /* allow interface to sleep again */
+ usb_autopm_put_interface(cdc_enc->intf);
+
+ cdc_enc_destroy_client(client);
+ file->private_data = NULL;
+ return 0;
+}
+
+static const struct file_operations cdc_enc_fops = {
+ .owner = THIS_MODULE,
+ .read = cdc_enc_fops_read,
+ .write = cdc_enc_fops_write,
+ .open = cdc_enc_fops_open,
+ .release = cdc_enc_fops_release,
+ .llseek = noop_llseek,
+};
+
+/* submit read URB */
+int cdc_enc_submit_readurb(struct cdc_enc_state *cdc_enc, int kmalloc_flags)
+{
+ return usb_submit_urb(cdc_enc->urb, kmalloc_flags);
+
+}
+EXPORT_SYMBOL_GPL(cdc_enc_submit_readurb);
+
+/* kill the read URB */
+void cdc_enc_kill_readurb(struct cdc_enc_state *cdc_enc)
+{
+ usb_kill_urb(cdc_enc->urb);
+}
+EXPORT_SYMBOL_GPL(cdc_enc_kill_readurb);
+
+/* call this to enable interrupt processing when netif is down */
+int cdc_enc_set_interrupt(struct cdc_enc_state *cdc_enc, struct urb *urb)
+{
+ /* save URB once */
+ if (urb && !cdc_enc->interrupt) {
+ cdc_enc->interrupt = urb;
+ /* and submit immediately in case we have clients waiting */
+ cdc_enc_status_urb(cdc_enc, GFP_KERNEL);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdc_enc_set_interrupt);
+
+/* global state relating to the character device */
+static struct class *cdc_enc_class; /* registered device class */
+static dev_t cdc_enc_dev0; /* our allocated major/minor range */
+static unsigned long cdc_enc_minor; /* bitmap of minors in use */
+
+static ssize_t protocol_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct cdc_enc_state *cdc_enc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", cdc_enc->protocol ? cdc_enc->protocol : "none");
+}
+static struct device_attribute cdc_enc_dev_attrs[] = {
+ __ATTR_RO(protocol),
+ __ATTR_NULL
+};
+
+/* allocate and initiate a CDC_ENC state device */
+struct cdc_enc_state *cdc_enc_init_one(struct usb_interface *intf, const char *protocol)
+{
+ struct cdc_enc_state *cdc_enc;
+ dev_t devno;
+ int i, ret;
+ struct usb_device *udev = interface_to_usbdev(intf);
+
+ /* find an unused minor */
+ for (i = 0; i < CDC_ENC_MAX_MINOR; i++)
+ if (!test_and_set_bit(i, &cdc_enc_minor))
+ break;
+
+ /* no free devices */
+ if (i == CDC_ENC_MAX_MINOR)
+ goto err_nodev;
+
+ cdc_enc = kzalloc(sizeof(struct cdc_enc_state), GFP_KERNEL);
+ if (!cdc_enc)
+ goto err_nodev;
+
+ /* save protocol name */
+ cdc_enc->protocol = protocol;
+
+ /* make device number */
+ devno = cdc_enc_dev0 + i;
+
+ /*
+ * 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
+ */
+ cdc_enc->intf = intf;
+ /* FIXME: it would be useful to verify that this interface actually talks CDC */
+
+ /* create async receive URB */
+ cdc_enc->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!cdc_enc->urb)
+ goto err_urb;
+
+ /* usb control setup */
+ cdc_enc->setup.bRequestType = USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE;
+ cdc_enc->setup.bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
+ cdc_enc->setup.wValue = 0; /* zero */
+ cdc_enc->setup.wIndex = intf->cur_altsetting->desc.bInterfaceNumber;
+ cdc_enc->setup.wLength = CDC_ENC_BUFLEN;
+
+ /* prepare the async receive URB */
+ usb_fill_control_urb(cdc_enc->urb, udev,
+ usb_rcvctrlpipe(udev, 0),
+ (char *)&cdc_enc->setup,
+ cdc_enc->rcvbuf,
+ CDC_ENC_BUFLEN,
+ cdc_enc_read_callback, cdc_enc);
+
+ init_waitqueue_head(&cdc_enc->waitq);
+
+ /* initialize client list */
+ INIT_LIST_HEAD(&cdc_enc->clients);
+ mutex_init(&cdc_enc->clients_lock);
+
+ /* finally, create the character device, using the interface as a parent dev */
+ cdev_init(&cdc_enc->cdev, &cdc_enc_fops);
+ ret = cdev_add(&cdc_enc->cdev, devno, 1);
+ if (ret < 0)
+ goto err_cdev;
+
+ device_create(cdc_enc_class, &intf->dev, devno, cdc_enc, "cdc-enc%d", i);
+ dev_info(&intf->dev, "attached to cdc-enc%d using protocol '%s'\n", i, protocol);
+
+ /* this must be set later by calling cdc_enc_set_interrupt() */
+ cdc_enc->interrupt = NULL;
+
+
+ return cdc_enc;
+
+err_cdev:
+ usb_free_urb(cdc_enc->urb);
+err_urb:
+ kfree(cdc_enc);
+err_nodev:
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(cdc_enc_init_one);
+
+/* disable and free a CDC_ENC state device */
+int cdc_enc_free_one(struct cdc_enc_state *cdc_enc)
+{
+ struct cdc_enc_client *tmp;
+
+ /* kill any pending recv urb */
+ cdc_enc_kill_readurb(cdc_enc);
+
+ /* wait for all clients to exit first ... */
+ list_for_each_entry(tmp, &cdc_enc->clients, list) {
+ dev_dbg(&cdc_enc->intf->dev, "waiting for client %p to die\n", tmp);
+ set_bit(CDC_ENC_CLIENT_SHUTDOWN, &tmp->flags);
+ complete(&tmp->ready);
+ }
+ wait_event_interruptible(cdc_enc->waitq, list_empty(&cdc_enc->clients));
+
+ /* delete character device */
+ device_destroy(cdc_enc_class, cdc_enc->cdev.dev);
+ cdev_del(&cdc_enc->cdev);
+
+ /* free URB */
+ usb_free_urb(cdc_enc->urb);
+
+ /* mark minor available again */
+ clear_bit(MINOR(cdc_enc->cdev.dev) - MINOR(cdc_enc_dev0), &cdc_enc_minor);
+
+ /* release this slot */
+ kfree(cdc_enc);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdc_enc_free_one);
+
+static int __init cdc_enc_init(void)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&cdc_enc_dev0, 0, CDC_ENC_MAX_MINOR, "cdc_enc");
+ if (ret < 0)
+ goto err_region;
+
+ /* create a chardev class */
+ cdc_enc_class = class_create(THIS_MODULE, "cdc_enc");
+ if (IS_ERR(cdc_enc_class)) {
+ ret = PTR_ERR(cdc_enc_class);
+ goto err_class;
+ }
+ cdc_enc_class->dev_attrs = cdc_enc_dev_attrs;
+
+ return 0;
+
+err_class:
+ unregister_chrdev_region(cdc_enc_dev0, CDC_ENC_MAX_MINOR);
+err_region:
+ return ret;
+}
+module_init(cdc_enc_init);
+
+static void __exit cdc_enc_exit(void)
+{
+ class_destroy(cdc_enc_class);
+ unregister_chrdev_region(cdc_enc_dev0, CDC_ENC_MAX_MINOR);
+}
+module_exit(cdc_enc_exit);
+
+MODULE_AUTHOR("Bjørn Mork <bjorn-yOkvZcmFvRU@public.gmane.org>");
+MODULE_DESCRIPTION("CDC Encapsulated Command Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/usb/cdc_enc.h b/drivers/net/usb/cdc_enc.h
new file mode 100644
index 0000000..44bffff
--- /dev/null
+++ b/drivers/net/usb/cdc_enc.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2012 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.
+ */
+
+#ifndef _CDC_ENC_H_
+#define _CDC_ENC_H_
+
+#include <linux/cdev.h>
+#include <linux/list.h>
+#include <linux/usb.h>
+
+#define CDC_ENC_BUFLEN 512
+#define CDC_ENC_MAX_MINOR 16
+
+enum cdc_enc_client_flag {
+ CDC_ENC_CLIENT_BUSY = 0, /* RX buffer is in use */
+ CDC_ENC_CLIENT_TX, /* TX buffer is in use */
+ CDC_ENC_CLIENT_RX, /* new data available */
+ CDC_ENC_CLIENT_SHUTDOWN, /* shutdown requested */
+};
+
+/* per client data */
+struct cdc_enc_client {
+ unsigned char buf[CDC_ENC_BUFLEN]; /* rx buffer */
+ unsigned char tx_buf[CDC_ENC_BUFLEN]; /* tx buffer */
+ size_t len; /* length of data in rx buffer */
+ unsigned long flags;
+ struct cdc_enc_state *cdc_enc; /* CDC_ENC instance owning the client */
+ struct completion ready;
+ struct list_head list; /* linux/list.h pointers */
+ struct work_struct work; /* task to call when new data is recvd */
+ unsigned long priv; /* client specific data */
+};
+
+/* per CDC_ENC interface state */
+struct cdc_enc_state {
+ struct usb_interface *intf;
+ struct urb *urb; /* receive urb */
+ struct urb *interrupt; /* interrupt urb */
+ unsigned char rcvbuf[CDC_ENC_BUFLEN]; /* receive buffer */
+ struct usb_ctrlrequest setup; /* the receive setup - 8 bytes */
+ struct list_head clients; /* list of clients - first entry is wwan client */
+ struct mutex clients_lock;
+ struct cdev cdev; /* registered character device */
+ const char *protocol;
+ wait_queue_head_t waitq; /* so we can wait for all clients on exit */
+};
+
+/* submit the read URB immediately - can be called in interrupt context */
+extern int cdc_enc_submit_readurb(struct cdc_enc_state *cdc_enc, int kmalloc_flags);
+
+/* kill the read URB */
+extern void cdc_enc_kill_readurb(struct cdc_enc_state *cdc_enc);
+
+/* call this to enable cdc_enc to submit the interrupt URB when it needs to */
+extern int cdc_enc_set_interrupt(struct cdc_enc_state *cdc_enc, struct urb *urb);
+
+/* initialize */
+extern struct cdc_enc_state *cdc_enc_init_one(struct usb_interface *intf, const char *protocol);
+
+/* clean up */
+extern int cdc_enc_free_one(struct cdc_enc_state *cdc_enc);
+
+/* send a synchronous message from client */
+extern int cdc_enc_send_sync(struct cdc_enc_client *client, unsigned char *msg, size_t len);
+
+/* get a new client */
+extern struct cdc_enc_client *cdc_enc_add_client(struct cdc_enc_state *cdc_enc, work_func_t recv_callback);
+
+/* destroy client */
+extern void cdc_enc_destroy_client(struct cdc_enc_client *client);
+
+#endif /* _CDC_ENC_H_ */
--
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] 8+ messages in thread
* [PATCH v2 3/3] net: usb: qmi_wwan: New driver for QMI WAN devices
[not found] ` <cover.1326607234.git.bjorn-yOkvZcmFvRU@public.gmane.org>
2012-01-15 6:40 ` [PATCH v2 1/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific Bjørn Mork
2012-01-15 6:40 ` [PATCH v2 2/3] net: usb: cdc_enc: New driver exposing USB CDC encapsulated protocols Bjørn Mork
@ 2012-01-15 6:40 ` Bjørn Mork
2 siblings, 0 replies; 8+ messages in thread
From: Bjørn Mork @ 2012-01-15 6:40 UTC (permalink / raw)
To: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA
Cc: Bjørn Mork, Dan Williams, Thomas Schäfer
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 MSM Interface (QMI) protocol to the device to enable the
ethernet proxy functionality.
This driver uses the cdc_enc subdriver to get access to the
encapsulated QMI protocol interface, and implements the basic QMI
support needed to bring up and down the interface. There is still
a need to pre-configure some settings using e.g. a modem mananger
application. Most users will want to set an APN and enter a PIN
code, and some may also have to confgure authentication.
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>
---
Changes from v1: Complete rewrite. The QMI protocol specific parts
are split out from the rest of the driver, and all unnecessary QMI
commands has been removed as per comments from Dan Williams. The
full QMI protocol is instead exposed to userspace via a character
device provided by the cdc_enc subdriver.
Some comments regarding the other QMI driver implementations I've
come across:
- Gobi: similar to this driver, but does not use usbnet. Does
expose the QMI protocol via a character device, but requires
the user to send a number of ioctls to use it.
Should be less than problematic to create a compatible
interface if there are any application using it. Are there?
- The Android MSM driver: uses a shared menory interface to talk
to the modem, and does not expose QMI to userspace. Does
have nice QMI message parsing (I wish I had noticed before...)
and some of it could probably be split out in some common part.
But I don't know if it's worth it.
drivers/net/usb/Kconfig | 14 ++
drivers/net/usb/Makefile | 2 +
drivers/net/usb/qmi_proto.c | 475 +++++++++++++++++++++++++++++++++++++++
drivers/net/usb/qmi_proto.h | 98 ++++++++
drivers/net/usb/qmi_wwan_core.c | 343 ++++++++++++++++++++++++++++
5 files changed, 932 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/usb/qmi_proto.c
create mode 100644 drivers/net/usb/qmi_proto.h
create mode 100644 drivers/net/usb/qmi_wwan_core.c
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 1425b08..cc93122 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -475,4 +475,18 @@ config USB_NET_CDC_ENC
The encapsulated protocol is exported as a character device.
+config USB_NET_QMI_WWAN
+ tristate "QMI WWAN driver for Qualcomm MSM based 3G and LTE modems"
+ depends on USB_NET_CDCETHER
+ select USB_NET_CDC_ENC
+ help
+ Support WWAN LTE/3G devices based on MSM chipsets from
+ Qualcomm, using the Qualcomm MSM Interface (QMI) protocol to
+ configure the device.
+
+ Note that it is still necessary to configure parameters like
+ PIN code, APN, username and password using AT commands on a
+ ttyUSBx port. Select the "option" driver to support these.
+
+
endmenu
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index 1edfc1b..644d512 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -30,4 +30,6 @@ 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_CDC_ENC) += cdc_enc.o
+obj-$(CONFIG_USB_NET_QMI_WWAN) += qmi_wwan.o
+qmi_wwan-objs := qmi_wwan_core.o qmi_proto.o
diff --git a/drivers/net/usb/qmi_proto.c b/drivers/net/usb/qmi_proto.c
new file mode 100644
index 0000000..aa0c9b4
--- /dev/null
+++ b/drivers/net/usb/qmi_proto.c
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2012 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 MSM 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 "cdc_enc.h"
+#include "qmi_proto.h"
+
+/* find and return a pointer to the requested tlv */
+static 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 */
+static int qmi_verify_status_tlv(u8 *buf, size_t len)
+{
+ struct qmi_tlv *tlv = qmi_get_tlv(0x02, buf, len);
+ struct qmi_tlv_response_data *r = (void *)tlv->bytes;
+
+ /* OK: indications and requests do not have any status TLV */
+ if (!tlv)
+ return 0;
+
+ if (tlv->len != sizeof(struct qmi_tlv_response_data))
+ return -QMI_ERR_MALFORMED_MSG;
+
+ return r->error ? -r->code : 0;
+}
+
+/* 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_header *h = (void *)data;
+ struct qmi_msg *m = NULL;
+
+ if (len < sizeof(struct qmux_header) || /* short packet */
+ h->tf != 0x01 || h->len != (len - 1)) /* invalid QMUX packet! */
+ goto err;
+
+ /* tid has a different size for QMI_CTL for some fucking stupid reason */
+ if (h->service == QMI_CTL) {
+ struct qmi_ctl *ctl = (void *)data;
+ if (len >= sizeof(struct qmi_ctl) + ctl->m.len)
+ m = &ctl->m;
+ } else {
+ struct qmi_wds *wds = (void *)data;
+ if (len >= sizeof(struct qmi_wds) + wds->m.len)
+ m = &wds->m;
+ }
+
+err:
+ return m;
+}
+
+/* parse a QMI_CTL replies */
+static int qmi_ctl_parse(struct cdc_enc_client *client, struct qmi_msg *m)
+{
+ struct qmi_state *state = (void *)&client->priv;
+ struct qmi_tlv *tlv;
+ int status;
+
+ /* check and save status TLV */
+ status = qmi_verify_status_tlv(m->tlv, m->len);
+
+ /* clear the saved transaction id */
+ state->ctl_tid = 0;
+
+ switch (m->msgid) {
+ case 0x0022: /* CTL_GET_CLIENT_ID_RESP */
+ /* there's no way out of this I think... */
+ if (status < 0) {
+ dev_err(&client->cdc_enc->intf->dev,
+ "Failed to get QMI_WDS client id %#06x\n", -status);
+ state->flags |= QMI_STATE_FLAG_CIDERR;
+ break;
+ }
+
+ /* TLV 0x01 is a 2 byte system + client ID */
+ tlv = qmi_get_tlv(0x01, m->tlv, m->len);
+ if (tlv && tlv->len == 2) {
+ if (tlv->bytes[0] == QMI_WDS)
+ state->wds_cid = tlv->bytes[1];
+ } else {
+ status = -QMI_ERR_MALFORMED_MSG;
+ }
+ break;
+ }
+ return status;
+}
+
+static int qmi_send_msg(struct cdc_enc_client *client, u8 system, __le16 msgid, struct qmi_tlv *tlv);
+
+/* parse a QMI_WDS indications looking connection status updates */
+static int qmi_wds_parse_ind(struct cdc_enc_client *client, struct qmi_msg *m)
+{
+ struct qmi_state *state = (void *)&client->priv;
+ struct qmi_tlv *tlv;
+ int status = 0;
+
+ switch (m->msgid) {
+ case 0x0022: /* QMI_WDS_PKT_SRVC_STATUS_IND */
+
+ /* TLV 0x01 is a 2 byte connection status. Note the
+ * difference from replies: Indications use the second
+ * byte for a "reconfiguration" flag. We ignore that..
+ */
+ tlv = qmi_get_tlv(0x01, m->tlv, m->len);
+ if (tlv && tlv->len == 2) {
+ dev_dbg(&client->cdc_enc->intf->dev,
+ "connstate %#04x => %#04x (flags %04x)\n",
+ state->wds_status, tlv->bytes[0], state->flags);
+
+ state->wds_status = tlv->bytes[0];
+
+ /* this is a bit weird, but it seems that the
+ * firmware *requires* us to request address
+ * configuration before it will forward any
+ * packets. DHCP does this just fine for us on
+ * initial connect, but reconnecting will
+ * cause us to blackhole packets unless we
+ * send this QMI request.
+ *
+ * BUT: If we send this after the initial connection,
+ * before the DHCP client has done it's job, then
+ * we end up with a device sending ethernet header-
+ * less frames. Go figure. Can you spell buggy
+ * frimware?
+ *
+ * So we attemt to send this on reconnects only
+ *
+ * QMI_WDS msg 0x002d is "QMI_WDS_GET_RUNTIME_SETTINGS"
+ */
+ if (state->wds_status == 2) {
+ if (state->flags & QMI_STATE_FLAG_NOTFIRST)
+ qmi_send_msg(client, QMI_WDS, 0x002d, NULL);
+ else
+ state->flags |= QMI_STATE_FLAG_NOTFIRST;
+ }
+ } else {
+ status = -QMI_ERR_MALFORMED_MSG;
+ }
+ break;
+ }
+ return status;
+}
+
+
+/* parse a QMI_WDS replies looking connection status updates */
+static int qmi_wds_parse(struct cdc_enc_client *client, struct qmi_msg *m)
+{
+ struct qmi_state *state = (void *)&client->priv;
+ struct qmi_tlv *tlv;
+ int status;
+
+ /* check and save status TLV */
+ status = qmi_verify_status_tlv(m->tlv, m->len);
+
+ /* note that we continue with per message processing on
+ * errors, to be able to clear wait states etc.
+ */
+ switch (m->msgid) {
+ case 0x0020: /* QMI_WDS_START_NETWORK_INTERFACE_RESP */
+ /* got a reply - clear flag */
+ state->flags &= ~QMI_STATE_FLAG_START;
+ if (status < 0) {
+ __le32 reason = -1;
+
+ /* TLV 0x11 is a 4 byte call end reason type */
+ tlv = qmi_get_tlv(0x11, m->tlv, m->len);
+ if (tlv)
+ reason = *(__le32 *)tlv->bytes;
+
+ dev_info(&client->cdc_enc->intf->dev,
+ "Connection failed with status %#06x and reason %#010x\n",
+ -status, reason);
+ break;
+ }
+
+ /* TLV 0x01 is a 4 byte connection handle */
+ tlv = qmi_get_tlv(0x01, m->tlv, m->len);
+ if (tlv && tlv->len == sizeof(state->wds_handle))
+ memcpy(state->wds_handle, tlv->bytes, sizeof(state->wds_handle));
+ else
+ status = -QMI_ERR_MALFORMED_MSG;
+ break;
+ case 0x0022: /* QMI_WDS_GET_PKT_SRVC_STATUS_RESP */
+ if (status < 0)
+ break;
+
+ /* TLV 0x01 is a 1 byte connection status */
+ tlv = qmi_get_tlv(0x01, m->tlv, m->len);
+ if (tlv && tlv->len == 1)
+ state->wds_status = tlv->bytes[0];
+ else
+ status = -QMI_ERR_MALFORMED_MSG;
+ break;
+ case 0x002d: /* might as well parse this then... */
+ if (status < 0)
+ break;
+
+ /* TLV 0x14 is APN name */
+ tlv = qmi_get_tlv(0x14, m->tlv, m->len);
+ if (!tlv)
+ break;
+
+ /* we "know" the buffer has space for this */
+ tlv->bytes[tlv->len] = 0;
+ dev_info(&client->cdc_enc->intf->dev,
+ "Connected to APN \"%s\"\n", tlv->bytes);
+
+ break;
+ }
+ return status;
+}
+
+/* parse the buffered message and update the saved QMI state if it's for us */
+int qmi_parse_qmux(struct cdc_enc_client *client)
+{
+ struct qmi_ctl *qmux;
+ struct qmi_msg *m;
+ int ret = -1;
+ struct qmi_state *state = (void *)&client->priv;
+
+ /* any unread data available? */
+ if (!test_bit(CDC_ENC_CLIENT_RX, &client->flags))
+ return 0;
+
+ /* someone else is using the buffer... */
+ if (test_and_set_bit(CDC_ENC_CLIENT_BUSY, &client->flags))
+ return -1;
+
+ m = qmi_qmux_verify(client->buf, client->len);
+ if (!m)
+ goto done;
+
+ /* we only use the common header for QMI_WDS so this will work */
+ qmux = (struct qmi_ctl *)client->buf;
+
+ /* verify that the message is for our eyes */
+ if (qmux->h.ctrl != 0x80) /* must be "service" */
+ goto done;
+
+ switch (qmux->h.service) {
+ case QMI_CTL:
+ /* only for us if the transaction ID matches */
+ if (state->ctl_tid == qmux->tid)
+ ret = qmi_ctl_parse(client, m);
+ break;
+ case QMI_WDS:
+ /* for us if it's a broadcast or the client ID matches */
+ if (qmux->h.qmicid == 0xff || qmux->h.qmicid == state->wds_cid) {
+ switch (qmux->h.flags) {
+ case 0x02: /* reply */
+ ret = qmi_wds_parse(client, m);
+ break;
+ case 0x04: /* unsolicited indication */
+ ret = qmi_wds_parse_ind(client, m);
+ break;
+ }
+ }
+ break;
+ }
+
+done:
+ /* order is important! */
+ clear_bit(CDC_ENC_CLIENT_RX, &client->flags);
+ clear_bit(CDC_ENC_CLIENT_BUSY, &client->flags);
+ return ret;
+}
+
+/* Get a new transaction id - yeah, yeah, atomic operations blah, blah, blah
+ * Fact is that we don't really need to care much about collisions, so this
+ * will do
+ */
+static __le16 new_tid(void)
+{
+ static __le16 tid;
+ return ++tid;
+}
+
+/* assemble a QMI_WDS packet */
+static size_t qmi_create_wds_msg(struct cdc_enc_client *client, __le16 msgid, struct qmi_tlv *tlv)
+{
+ struct qmi_wds *wds = (void *)client->tx_buf;
+ struct qmi_state *state = (void *)&client->priv;
+
+ /* cannot send QMI_WDS requests without a client ID */
+ if (!state->wds_cid)
+ return 0;
+
+ memset(wds, 0, sizeof(*wds));
+ wds->h.tf = 1; /* always 1 */
+ wds->h.service = QMI_WDS;
+ wds->h.qmicid = state->wds_cid;
+ wds->h.len = sizeof(*wds) - 1;
+ wds->tid = new_tid();
+ wds->m.msgid = msgid;
+ if (tlv) {
+ ssize_t tlvsize = tlv->len + sizeof(struct qmi_tlv);
+ memcpy(wds->m.tlv, tlv, tlvsize);
+ wds->m.len = tlvsize;
+ wds->h.len += tlvsize;
+ }
+ return wds->h.len + 1;
+}
+
+/* assemble a QMI_CTL packet */
+static size_t qmi_create_ctl_msg(struct cdc_enc_client *client, __le16 msgid, struct qmi_tlv *tlv)
+{
+ struct qmi_ctl *ctl = (void *)client->tx_buf;
+ struct qmi_state *state = (void *)&client->priv;
+
+ /* only allowing one outstanding CTL request */
+ if (state->ctl_tid)
+ return 0;
+
+ memset(ctl, 0, sizeof(*ctl));
+ ctl->h.tf = 1; /* always 1 */
+ ctl->h.len = sizeof(*ctl) - 1;
+ while (ctl->tid == 0 || ctl->tid == 0xff) /* illegal values */
+ ctl->tid = new_tid() & 0xff;
+
+ /* save allocated transaction ID for reply matching */
+ state->ctl_tid = ctl->tid;
+
+ ctl->m.msgid = msgid;
+ if (tlv) {
+ ssize_t tlvsize = tlv->len + sizeof(struct qmi_tlv);
+ memcpy(ctl->m.tlv, tlv, tlvsize);
+ ctl->m.len = tlvsize;
+ ctl->h.len += tlvsize;
+ }
+ return ctl->h.len + 1;
+
+}
+
+/* send a QMI message syncronously */
+static int qmi_send_msg(struct cdc_enc_client *client, u8 system, __le16 msgid, struct qmi_tlv *tlv)
+{
+ int ret = -1;
+ size_t len = 0;
+
+ /* lock client buffer */
+ if (test_and_set_bit(CDC_ENC_CLIENT_TX, &client->flags))
+ goto err_noclear;
+
+ /* create message */
+ switch (system) {
+ case QMI_CTL:
+ len = qmi_create_ctl_msg(client, msgid, tlv);
+ break;
+ case QMI_WDS:
+ len = qmi_create_wds_msg(client, msgid, tlv);
+ break;
+ }
+
+ if (!len)
+ goto err;
+
+ /* send it */
+ ret = cdc_enc_send_sync(client, client->tx_buf, len) - len;
+
+err:
+ clear_bit(CDC_ENC_CLIENT_TX, &client->flags);
+err_noclear:
+ return ret;
+}
+
+/* QMI_CTL msg 0x0022 is "request cid", TLV 0x01 is system */
+int qmi_ctl_request_wds_cid(struct cdc_enc_client *client)
+{
+ static struct qmi_tlv tlvreq = {
+ .type = 0x01,
+ .len = 1,
+ .bytes = { QMI_WDS },
+ };
+ struct qmi_state *state = (void *)&client->priv;
+
+ /* return immediately if a CID is already allocated */
+ if (state->wds_cid)
+ return 0;
+
+ return qmi_send_msg(client, QMI_CTL, 0x0022, &tlvreq);
+}
+
+/* QMI_CTL msg 0x0023 is "release cid", TLV 0x01 is system + cid */
+int qmi_ctl_release_wds_cid(struct cdc_enc_client *client)
+{
+ static struct qmi_tlv tlvreq = {
+ .type = 0x01,
+ .len = 2,
+ .bytes = { QMI_WDS, 0 },
+ };
+ struct qmi_state *state = (void *)&client->priv;
+
+
+ /* return immediately if no CID is allocated */
+ if (!state->wds_cid)
+ return 0;
+
+ tlvreq.bytes[1] = state->wds_cid;
+ state->wds_cid = 0;
+
+ return qmi_send_msg(client, QMI_CTL, 0x0023, &tlvreq);
+}
+
+/* QMI_WDS msg 0x0020 is "QMI_WDS_START_NETWORK_INTERFACE" */
+int qmi_wds_start(struct cdc_enc_client *client)
+{
+ struct qmi_state *state = (void *)&client->priv;
+
+ /* avoid sending multiple start messages on top of each other */
+ if (state->flags & QMI_STATE_FLAG_START)
+ return 0;
+
+ state->flags |= QMI_STATE_FLAG_START;
+ return qmi_send_msg(client, QMI_WDS, 0x0020, NULL);
+}
+
+/* QMI_WDS msg 0x0021 is "QMI_WDS_STOP_NETWORK_INTERFACE" */
+int qmi_wds_stop(struct cdc_enc_client *client)
+{
+ static struct qmi_tlv tlvreq = {
+ .type = 0x01,
+ .len = 4,
+ .bytes = { 0, 0, 0, 0 },
+ };
+ struct qmi_state *state = (void *)&client->priv;
+
+ /* cannot send stop unless we have a handle */
+ if (!*(u32 *)state->wds_handle)
+ return 0;
+
+ memcpy(&tlvreq.bytes, state->wds_handle, sizeof(state->wds_handle));
+ return qmi_send_msg(client, QMI_WDS, 0x0021, &tlvreq);
+}
+
+/* QMI_WDS msg 0x0022 is "QMI_WDS_GET_PKT_SRVC_STATUS" */
+int qmi_wds_status(struct cdc_enc_client *client)
+{
+ return qmi_send_msg(client, QMI_WDS, 0x0022, NULL);
+}
+
+/* reset everything but the allocated QMI_WDS client ID */
+void qmi_state_reset(struct cdc_enc_client *client)
+{
+ struct qmi_state *state = (void *)&client->priv;
+
+ memset(state->wds_handle, 0, sizeof(state->wds_handle));
+ state->ctl_tid = 0;
+ state->wds_status = 0;
+ state->flags = 0;
+}
diff --git a/drivers/net/usb/qmi_proto.h b/drivers/net/usb/qmi_proto.h
new file mode 100644
index 0000000..cf1d96d
--- /dev/null
+++ b/drivers/net/usb/qmi_proto.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2012 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 MSM 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/
+ */
+
+#ifndef _QMI_PROTO_H_
+#define _QMI_PROTO_H_
+
+#include "cdc_enc.h"
+
+/* the different QMI subsystems */
+#define QMI_CTL 0x00
+#define QMI_WDS 0x01
+
+/* Error codes */
+#define QMI_ERR_MALFORMED_MSG 0x0001
+
+/* QMI protocol structures */
+struct qmux_header {
+ 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 */
+} __packed;
+
+struct qmi_msg {
+ __le16 msgid;
+ __le16 len;
+ u8 tlv[]; /* zero or more tlvs */
+} __packed;
+
+struct qmi_ctl {
+ struct qmux_header h;
+ u8 tid; /* system QMI_CTL uses one byte transaction ids! */
+ struct qmi_msg m;
+} __packed;
+
+struct qmi_wds {
+ struct qmux_header h;
+ __le16 tid;
+ struct qmi_msg m;
+} __packed;
+
+struct qmi_tlv {
+ u8 type;
+ __le16 len;
+ u8 bytes[];
+} __packed;
+
+struct qmi_tlv_response_data {
+ __le16 error;
+ __le16 code;
+} __packed;
+
+/* for QMI_WDS state tracking */
+struct qmi_state {
+ u8 wds_handle[4]; /* connection handle */
+ u8 ctl_tid; /* for matching up QMI_CTL replies */
+ u8 wds_cid; /* allocated QMI_WDS client ID */
+ u8 wds_status; /* result of the last QMI_WDS_GET_PKT_SRVC_STATUS message */
+ u8 flags;
+} __packed;
+
+#define QMI_STATE_FLAG_START 0x01 /* START in progress */
+#define QMI_STATE_FLAG_CIDERR 0x02 /* client ID allocation failure */
+#define QMI_STATE_FLAG_NOTFIRST 0x04 /* have been connected before */
+
+/* parsing the buffered QMUX message */
+extern int qmi_parse_qmux(struct cdc_enc_client *client);
+
+/* reset state variables */
+extern void qmi_state_reset(struct cdc_enc_client *client);
+
+/* QMI_CTL commands */
+extern int qmi_ctl_request_wds_cid(struct cdc_enc_client *client);
+extern int qmi_ctl_release_wds_cid(struct cdc_enc_client *client);
+
+/* QMI_WDS commands */
+extern int qmi_wds_start(struct cdc_enc_client *client);
+extern int qmi_wds_stop(struct cdc_enc_client *client);
+extern int qmi_wds_status(struct cdc_enc_client *client);
+
+#endif /* _QMI_PROTO_H_ */
diff --git a/drivers/net/usb/qmi_wwan_core.c b/drivers/net/usb/qmi_wwan_core.c
new file mode 100644
index 0000000..7bcdf78
--- /dev/null
+++ b/drivers/net/usb/qmi_wwan_core.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2012 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 "cdc_enc.h"
+#include "qmi_proto.h"
+
+/*
+ * Overloading the cdc_state structure...
+ *
+ * We use usbnet_cdc_bind() because that suits us well, but it maps
+ * the whole struct usbnet "data" field to it's own struct cdc_state.
+ * The three functional descriptor pointers are however not used after
+ * bind, so we remap the "data" field to our own structure with a
+ * pointer to the cdc_enc subdriver instead of the CDC header descriptor
+ */
+struct qmi_wwan_state {
+ /* replacing "struct usb_cdc_header_desc *header" */
+ struct cdc_enc_client *wwan;
+
+ /* keeping these for now */
+ struct usb_cdc_union_desc *u;
+ struct usb_cdc_ether_desc *ether;
+
+ /* the rest *must* be identical to "struct cdc_state" */
+ struct usb_interface *control;
+ struct usb_interface *data;
+};
+
+/* callback doing the parsing for us */
+static void qmux_received_callback(struct work_struct *work)
+{
+ struct cdc_enc_client *client = container_of(work, struct cdc_enc_client, work);
+
+ qmi_parse_qmux(client);
+}
+
+/* we have two additional requirements for usbnet_cdc_status():
+ * 1) handle USB_CDC_NOTIFY_RESPONSE_AVAILABLE
+ * 2) resubmit interrupt urb even if the ethernet interface is down
+ */
+static void qmi_wwan_cdc_status(struct usbnet *dev, struct urb *urb)
+{
+ struct qmi_wwan_state *info = (void *)&dev->data;
+ struct usb_cdc_notification *event;
+
+ 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:
+ /* the device has QMI data for us => submit read URB */
+ cdc_enc_submit_readurb(info->wwan->cdc_enc, GFP_ATOMIC);
+ break;
+ default:
+ /* let usbnet_cdc_status() handle the other CDC messages */
+ usbnet_cdc_status(dev, urb);
+ }
+
+ /* usbnet won't resubmit unless netif is running */
+ if (!netif_running(dev->net))
+ usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+/* we need to do some work after binding, but possibly before usbnet will
+ * call any of our other methods
+ */
+static struct qmi_wwan_delayed_type {
+ struct delayed_work dwork;
+ struct usbnet *dev;
+} qmi_wwan_delayed;
+
+static void qmi_wwan_deferred_bind(struct work_struct *work)
+{
+ struct qmi_wwan_delayed_type *dtype = container_of(work, struct qmi_wwan_delayed_type, dwork.work);
+ struct qmi_wwan_state *info = (void *)&dtype->dev->data;
+
+ /* if not ready yet, then wait and retry */
+ if (!dtype->dev->interrupt) {
+ schedule_delayed_work(&qmi_wwan_delayed.dwork, 100);
+ return;
+ }
+
+ /* usbnet will happily kill the interrupt status URB when it
+ * doesn't need it. Let cdc_enc submit the URB in cases where
+ * it needs the interrupts. Hack alert!
+ */
+ cdc_enc_set_interrupt(info->wwan->cdc_enc, dtype->dev->interrupt);
+}
+
+static int qmi_wwan_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+ struct qmi_wwan_state *info = (void *)&dev->data;
+ struct cdc_enc_state *cdc_enc;
+ int status;
+
+ status = usbnet_cdc_bind(dev, intf);
+ if (status < 0) {
+ /* Some Huawei devices have multiple sets of
+ * descriptors, selectable by mode switching. Some of
+ * these sets, like the one used by Windows, do not
+ * have the CDC functional descriptors required by
+ * usbnet_cdc_bind(). But they still provide the
+ * exact same functionality on a single combined
+ * interface, with the missing MAC address string
+ * descriptor being the only exception.
+ */
+ dev_info(&intf->dev, "no CDC descriptors - will generate a MAC address\n");
+ status = usbnet_get_endpoints(dev, intf);
+ }
+ if (status < 0)
+ goto err_cdc_bind;
+
+ /* allocate and initiate a QMI device with a client */
+ cdc_enc = cdc_enc_init_one(intf, "qmi");
+ if (!cdc_enc)
+ goto err_cdc_enc;
+
+ /* add the wwan client */
+ info->wwan = cdc_enc_add_client(cdc_enc, qmux_received_callback);
+ if (!info->wwan)
+ goto err_wwan;
+
+ /* need to run this after usbnet has finished initialisation */
+ qmi_wwan_delayed.dev = dev;
+ INIT_DELAYED_WORK(&qmi_wwan_delayed.dwork, qmi_wwan_deferred_bind);
+ schedule_delayed_work(&qmi_wwan_delayed.dwork, 100);
+
+ dev_info(&intf->dev, "Use one of the ttyUSBx devices to configure PIN code, APN or other required settings\n");
+
+ return 0;
+
+err_wwan:
+ cdc_enc_free_one(cdc_enc);
+err_cdc_enc:
+ /* usbnet_cdc_unbind() makes sure that the driver is unbound
+ * from both the control and data interface. It will be a
+ * harmless noop if usbnet_cdc_bind() failed above.
+ */
+ usbnet_cdc_unbind(dev, intf);
+ status = -1;
+err_cdc_bind:
+ return status;
+}
+
+static void qmi_wwan_cdc_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+ struct qmi_wwan_state *info = (void *)&dev->data;
+ struct cdc_enc_state *cdc_enc;
+
+ /* release allocated client ID - no need to wait for the reply */
+ qmi_ctl_release_wds_cid(info->wwan);
+
+ /* release our private structure */
+ cdc_enc = info->wwan->cdc_enc ;
+ cdc_enc_destroy_client(info->wwan);
+ cdc_enc_free_one(cdc_enc);
+ info->wwan = NULL;
+
+ /* disconnect from data interface as well, if there is one */
+ usbnet_cdc_unbind(dev, intf);
+}
+
+static int qmi_wwan_reset(struct usbnet *dev)
+{
+ struct qmi_wwan_state *info = (void *)&dev->data;
+
+ /* reset QMI state to a something sane */
+ qmi_state_reset(info->wwan);
+
+ /* trigger client ID allocation if needed */
+ qmi_ctl_request_wds_cid(info->wwan);
+
+ return 1;
+}
+
+static int qmi_wwan_stop(struct usbnet *dev)
+{
+ struct qmi_wwan_state *info = (void *)&dev->data;
+
+ /* disconnect from mobile network */
+ qmi_wds_stop(info->wwan);
+ return 1;
+}
+
+static int qmi_wwan_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct usbnet *dev = usb_get_intfdata(intf);
+ struct qmi_wwan_state *info = (void *)&dev->data;
+
+ /* kill the read URB */
+ if (!dev->suspend_count)
+ cdc_enc_kill_readurb(info->wwan->cdc_enc);
+
+ return usbnet_suspend(intf, message);
+}
+
+static int qmi_wwan_resume(struct usb_interface *intf)
+{
+ struct usbnet *dev = usb_get_intfdata(intf);
+ int ret;
+
+ ret = usbnet_resume(intf);
+
+ /* resume interrupt URB in case usbnet didn't do it */
+ if (!dev->suspend_count && dev->interrupt && !test_bit(EVENT_DEV_OPEN, &dev->flags))
+ usb_submit_urb(dev->interrupt, GFP_NOIO);
+
+ return ret;
+}
+
+/* stolen from cdc_ether.c */
+static int qmi_wwan_manage_power(struct usbnet *dev, int on)
+{
+ dev->intf->needs_remote_wakeup = on;
+ return 0;
+}
+
+/* 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;
+ struct cdc_enc_client *wwan = info->wwan;
+ struct qmi_state *qmi = (void *)&wwan->priv;
+
+ /* reset was supposed to allocate a CID - not way out if that failed! */
+ if (qmi->flags & QMI_STATE_FLAG_CIDERR) {
+ netdev_info(dev->net, "QMI_WDS client ID allocation failed - cannot continue\n");
+ return -1;
+ }
+
+ switch (qmi->wds_status) {
+ case 0: /* no status set yet - trigger an update */
+ qmi_wds_status(wwan);
+ break;
+ case 1: /* disconnected - trigger a connection */
+ qmi_wds_start(wwan);
+ break;
+ case 2: /* connected */
+ return 0;
+ /* other states may indicate connection in progress - do nothing */
+ }
+
+ /* usbnet_open() will fail unless we kill their URB here */
+ if (!test_bit(EVENT_DEV_OPEN, &dev->flags))
+ usb_kill_urb(dev->interrupt);
+
+ return 1; /* "not connected" */
+}
+
+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,
+ .manage_power = qmi_wwan_manage_power,
+ .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_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_info,
+}, {
+ /* Qmi_Wwan E392, E398, ++? "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_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 = qmi_wwan_suspend,
+ .resume = qmi_wwan_resume,
+ .reset_resume = qmi_wwan_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) ||
+ offsetof(struct cdc_state, control) != offsetof(struct qmi_wwan_state, control) ||
+ offsetof(struct cdc_state, data) != offsetof(struct qmi_wwan_state, data));
+
+ BUILD_BUG_ON(sizeof(struct qmi_state) > sizeof(unsigned long));
+
+ 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 MSM 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] 8+ messages in thread
* Re: [PATCH v2 2/3] net: usb: cdc_enc: New driver exposing USB CDC encapsulated protocols
[not found] ` <d11888532ee381806b35974e71af4c0122255c05.1326607234.git.bjorn-yOkvZcmFvRU@public.gmane.org>
@ 2012-01-15 13:36 ` Oliver Neukum
[not found] ` <201201151436.29794.oliver-GvhC2dPhHPQdnm+yROfE0A@public.gmane.org>
0 siblings, 1 reply; 8+ messages in thread
From: Oliver Neukum @ 2012-01-15 13:36 UTC (permalink / raw)
To: Bjørn Mork
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA,
Dan Williams, Thomas Schäfer
Am Sonntag, 15. Januar 2012, 07:40:36 schrieb Bjørn Mork:
> USB CDC Ethernet devices may encapsulate vendor specific protocols inside
> USB_CDC_SEND_ENCAPSULATED_COMMAND +USB_CDC_GET_ENCAPSULATED_RESPONSE
> control messages. Devices based on Qualcomm MSM
> chipsets are known to use this for exporting the Qualcomm
> MSM Interface (QMI) protocol. Examples of such devices are
> the Huawei E392 and E398 LTE modems.
The problem is that this is another version of the code already
in cdc-wdm, which is tested well.
I'd really like to unify this stuff.
> +static ssize_t cdc_enc_fops_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
> +{
> + struct cdc_enc_client *client = file->private_data;
> + int ret = -EFAULT;
> +
> + if (!client || !client->cdc_enc)
> + return -ENODEV;
> +
> + while (!test_bit(CDC_ENC_CLIENT_RX, &client->flags)) {/* no data */
> + if (file->f_flags & O_NONBLOCK)
> + return -EAGAIN;
> +
> + if (wait_for_completion_interruptible(&client->ready) < 0)
> + return -EINTR;
> +
> + /* shutdown requested? */
> + if (test_bit(CDC_ENC_CLIENT_SHUTDOWN, &client->flags))
> + return -ENXIO;
> + }
> +
> + /* someone else is using our buffer */
> + if (test_and_set_bit(CDC_ENC_CLIENT_BUSY, &client->flags))
> + return -ERESTARTSYS;
> +
> + /* must read a complete packet */
> + if (client->len > size || copy_to_user(buf, client->buf, client->len)) {
This is not nice at all.
> + ret = -EFAULT;
> + goto err;
> + }
> + ret = client->len;
> +
> +err:
> + /* order is important! */
> + clear_bit(CDC_ENC_CLIENT_RX, &client->flags);
> + clear_bit(CDC_ENC_CLIENT_BUSY, &client->flags);
If order is important here, you need write barriers.
And read barriers at another place.
> + return ret;
> +}
> +
> +static ssize_t cdc_enc_fops_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
> +{
> + struct cdc_enc_client *client = file->private_data;
> + int ret = -EFAULT;
> +
> + if (!client || !client->cdc_enc)
> + return -ENODEV;
> +
> + /* shutdown requested? */
> + if (test_bit(CDC_ENC_CLIENT_SHUTDOWN, &client->flags))
> + return -ENXIO;
> +
> + /* no meaning in attempting to send an incomplete packet */
> + if (size > sizeof(client->tx_buf))
> + return -EFAULT;
> +
> + /* are someone else using our buffer? */
> + if (test_and_set_bit(CDC_ENC_CLIENT_TX, &client->flags))
> + return -ERESTARTSYS;
> +
> + if (size > sizeof(client->tx_buf) || copy_from_user(client->tx_buf, buf, size))
> + goto err;
> +
> + /* send to the device */
> + ret = cdc_enc_send_sync(client, client->tx_buf, size);
> + if (ret < 0)
> + return -EFAULT;
Error handling is shot. The device is now eternally busy.
> +static int cdc_enc_fops_open(struct inode *inode, struct file *file)
> +{
> + struct cdc_enc_state *cdc_enc;
> + struct cdc_enc_client *client;
> +
> + /* associate the file with our backing CDC_ENC device */
> + cdc_enc = container_of(inode->i_cdev, struct cdc_enc_state, cdev);
> + if (!cdc_enc)
> + return -ENODEV;
> +
> + /* don't allow interface to sleep while we are using it */
> + usb_autopm_get_interface(cdc_enc->intf);
This can fail.
> + /* enable status URB even if networking is down */
> + cdc_enc_status_urb(cdc_enc, GFP_KERNEL);
> +
> + /* set up a ring buffer to receive our readable data? */
> + client = cdc_enc_add_client(cdc_enc, cdc_enc_newdata_rcvd);
> +
> + if (!client)
> + return -ENOMEM;
Error handling. The device now can never again autosuspend.
> +
> + file->private_data = client;
> + return 0;
> +}
[..]
> +/* disable and free a CDC_ENC state device */
> +int cdc_enc_free_one(struct cdc_enc_state *cdc_enc)
> +{
> + struct cdc_enc_client *tmp;
> +
> + /* kill any pending recv urb */
> + cdc_enc_kill_readurb(cdc_enc);
> +
> + /* wait for all clients to exit first ... */
> + list_for_each_entry(tmp, &cdc_enc->clients, list) {
> + dev_dbg(&cdc_enc->intf->dev, "waiting for client %p to die\n", tmp);
> + set_bit(CDC_ENC_CLIENT_SHUTDOWN, &tmp->flags);
> + complete(&tmp->ready);
> + }
> + wait_event_interruptible(cdc_enc->waitq, list_empty(&cdc_enc->clients));
If you wait interruptably, you need to handle being interrupted.
> +
> +/* per CDC_ENC interface state */
> +struct cdc_enc_state {
> + struct usb_interface *intf;
> + struct urb *urb; /* receive urb */
> + struct urb *interrupt; /* interrupt urb */
> + unsigned char rcvbuf[CDC_ENC_BUFLEN]; /* receive buffer */
This is a violation of the DMA requirements. You must use kmalloc.
I had a not very detailed look at this driver, but I would really prefer
if you could have a look at making unified code for this, cdc-wdm and cdc-acm.
Regards
Oliver
--
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] 8+ messages in thread
* Re: [PATCH v2 2/3] net: usb: cdc_enc: New driver exposing USB CDC encapsulated protocols
[not found] ` <201201151436.29794.oliver-GvhC2dPhHPQdnm+yROfE0A@public.gmane.org>
@ 2012-01-15 14:22 ` Bjørn Mork
2012-01-15 19:52 ` Bjørn Mork
0 siblings, 1 reply; 8+ messages in thread
From: Bjørn Mork @ 2012-01-15 14:22 UTC (permalink / raw)
To: Oliver Neukum
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA,
Dan Williams, Thomas Schäfer
Oliver Neukum <oliver-GvhC2dPhHPQdnm+yROfE0A@public.gmane.org> writes:
> Am Sonntag, 15. Januar 2012, 07:40:36 schrieb Bjørn Mork:
>> USB CDC Ethernet devices may encapsulate vendor specific protocols inside
>> USB_CDC_SEND_ENCAPSULATED_COMMAND +USB_CDC_GET_ENCAPSULATED_RESPONSE
>> control messages. Devices based on Qualcomm MSM
>> chipsets are known to use this for exporting the Qualcomm
>> MSM Interface (QMI) protocol. Examples of such devices are
>> the Huawei E392 and E398 LTE modems.
>
> The problem is that this is another version of the code already
> in cdc-wdm, which is tested well.
Huh? I thought that was something entirely different. Isn't it?
Anyway, I see that I should look thoroughly at it as it does these
things in a much nicer way. Thanks for the pointer.
> I'd really like to unify this stuff.
That makes sense.
[snipped a number of detailed issues which I obviously need to fix]
>> +
>> +/* per CDC_ENC interface state */
>> +struct cdc_enc_state {
>> + struct usb_interface *intf;
>> + struct urb *urb; /* receive urb */
>> + struct urb *interrupt; /* interrupt urb */
>> + unsigned char rcvbuf[CDC_ENC_BUFLEN]; /* receive buffer */
>
> This is a violation of the DMA requirements. You must use kmalloc.
This probably only demonstrates my lack of understanding these
requirements. I thought this was OK as long as I kmalloc'ed the whole
struct, but I see now that that isn't necessarily true.
>
> I had a not very detailed look at this driver, but I would really prefer
> if you could have a look at making unified code for this, cdc-wdm and cdc-acm.
Will do. Thanks a lot for the fast review.
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] 8+ messages in thread
* Re: [PATCH v2 2/3] net: usb: cdc_enc: New driver exposing USB CDC encapsulated protocols
2012-01-15 14:22 ` Bjørn Mork
@ 2012-01-15 19:52 ` Bjørn Mork
[not found] ` <87ehv0eoah.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>
0 siblings, 1 reply; 8+ messages in thread
From: Bjørn Mork @ 2012-01-15 19:52 UTC (permalink / raw)
To: Oliver Neukum; +Cc: netdev, linux-usb, Dan Williams, Thomas Schäfer
Bjørn Mork <bjorn@mork.no> writes:
> Oliver Neukum <oliver@neukum.org> writes:
>> Am Sonntag, 15. Januar 2012, 07:40:36 schrieb Bjørn Mork:
>>> USB CDC Ethernet devices may encapsulate vendor specific protocols inside
>>> USB_CDC_SEND_ENCAPSULATED_COMMAND +USB_CDC_GET_ENCAPSULATED_RESPONSE
>>> control messages. Devices based on Qualcomm MSM
>>> chipsets are known to use this for exporting the Qualcomm
>>> MSM Interface (QMI) protocol. Examples of such devices are
>>> the Huawei E392 and E398 LTE modems.
>>
>> The problem is that this is another version of the code already
>> in cdc-wdm, which is tested well.
>
> Huh? I thought that was something entirely different. Isn't it?
I should look at things before asking. I see what you mean. The
cdc-wdm code is very similar, and should be fairly easy to adapt. Why
didn't I just research that from the start? Well, you learn more from
doing such mistakes ;-)
There is one fundamental design issue which worries me though: cdc-wdm
doesn't support multiple simultaneous readers/writers. This is sort of
a blocker for my use case, as I will need to keep a reader/writer for
the driver internal wwan management at all times. So two simultaneous
clients are necessary to actually have something available for
userspace at all.
Are there any reasons for this choice, and do you think it's feasible
changing it without destroying the advantage of the well tested code? I
seem to remember that cdc-wdm allowed simultaneous reading/writing in
the past, but this changed somewhere between 2.6.32 and 3.0. I have an
Ericsson modem with two such AT command speaking interfaces, and I use a
simple shell script to configure it. At one point I had to switch from
using /dev/cdc-wdm0 to /dev/ttyACM0 because the former started hanging
on constructs like this:
while read x; do
case "$x" in
foo)
echo -e "AT+bar\r" >/dev/cdc-wdm0
;;
...
esac
done </dev/cdc-wdm0
This used to work with 2.6.32, but that might have been just pure luck?
Anyway, that change really made cdc-wdm much less usable for me, and
that sort of proves that supporting simultaneous read/write would be an
improvement even for the existing supported devices.
And then there are a couple of minor issues which I believe needs
sorting out. If I understand the WDM spec correctly (without bothering
to actually read if, of course), then it only defines the interface but
not the protocol. Exactly like the embedded CDC Ethernet command
interface. So I have the Ericsson modem which which speaks an AT
command protocol, of course with a number of vendor specific AT commands
included. And then I have the Huawei modem which speaks QMI. Just
mapping these to a number of /dev/cdc-wdmX interfaces isn't going to
help user applications much. They need to be told *what* protocol to
use on the character device. And the driver should know this, either by
detection or maybe best: vid/pid based tables and sane fallbacks.
I'm not sure this will do (if the latter was changed to a cdc-wdmX
device):
bjorn@nemi:~$ grep . /sys/class/usb/cdc-wdm0/device/*
/sys/class/usb/cdc-wdm0/device/bAlternateSetting: 0
/sys/class/usb/cdc-wdm0/device/bInterfaceClass:02
/sys/class/usb/cdc-wdm0/device/bInterfaceNumber:05
/sys/class/usb/cdc-wdm0/device/bInterfaceProtocol:01
/sys/class/usb/cdc-wdm0/device/bInterfaceSubClass:09
/sys/class/usb/cdc-wdm0/device/bNumEndpoints:01
/sys/class/usb/cdc-wdm0/device/interface:Ericsson F3507g Mobile Broadband Minicard Device Management
/sys/class/usb/cdc-wdm0/device/modalias:usb:v0BDBp1900d0000dc02dsc00dp00ic02isc09ip01
/sys/class/usb/cdc-wdm0/device/supports_autosuspend:1
/sys/class/usb/cdc-wdm0/device/uevent:DEVTYPE=usb_interface
/sys/class/usb/cdc-wdm0/device/uevent:DRIVER=cdc_wdm
/sys/class/usb/cdc-wdm0/device/uevent:DEVICE=/proc/bus/usb/002/056
/sys/class/usb/cdc-wdm0/device/uevent:PRODUCT=bdb/1900/0
/sys/class/usb/cdc-wdm0/device/uevent:TYPE=2/0/0
/sys/class/usb/cdc-wdm0/device/uevent:INTERFACE=2/9/1
/sys/class/usb/cdc-wdm0/device/uevent:MODALIAS=usb:v0BDBp1900d0000dc02dsc00dp00ic02isc09ip01
bjorn@canardo:~$ grep . /sys/class/cdc_enc/cdc-enc0/device/*
/sys/class/cdc_enc/cdc-enc0/device/bAlternateSetting: 0
/sys/class/cdc_enc/cdc-enc0/device/bInterfaceClass:ff
/sys/class/cdc_enc/cdc-enc0/device/bInterfaceNumber:03
/sys/class/cdc_enc/cdc-enc0/device/bInterfaceProtocol:09
/sys/class/cdc_enc/cdc-enc0/device/bInterfaceSubClass:01
/sys/class/cdc_enc/cdc-enc0/device/bNumEndpoints:01
/sys/class/cdc_enc/cdc-enc0/device/modalias:usb:v12D1p1506d0000dc00dsc00dp00icFFisc01ip09
/sys/class/cdc_enc/cdc-enc0/device/supports_autosuspend:1
/sys/class/cdc_enc/cdc-enc0/device/uevent:DEVTYPE=usb_interface
/sys/class/cdc_enc/cdc-enc0/device/uevent:DRIVER=qmi_wwan
/sys/class/cdc_enc/cdc-enc0/device/uevent:DEVICE=/proc/bus/usb/001/020
/sys/class/cdc_enc/cdc-enc0/device/uevent:PRODUCT=12d1/1506/0
/sys/class/cdc_enc/cdc-enc0/device/uevent:TYPE=0/0/0
/sys/class/cdc_enc/cdc-enc0/device/uevent:INTERFACE=255/1/9
/sys/class/cdc_enc/cdc-enc0/device/uevent:MODALIAS=usb:v12D1p1506d0000dc00dsc00dp00icFFisc01ip09
So my proposal have been something like a "protocol" attribute:
bjorn@canardo:~$ grep . /sys/class/cdc_enc/cdc-enc0/*
/sys/class/cdc_enc/cdc-enc0/dev:250:0
/sys/class/cdc_enc/cdc-enc0/protocol:qmi
/sys/class/cdc_enc/cdc-enc0/uevent:MAJOR=250
/sys/class/cdc_enc/cdc-enc0/uevent:MINOR=0
/sys/class/cdc_enc/cdc-enc0/uevent:DEVNAME=cdc-enc0
bjorn@nemi:~$ grep . /sys/class/usb/cdc-wdm0/*
/sys/class/usb/cdc-wdm0/dev:180:0
/sys/class/usb/cdc-wdm0/uevent:MAJOR=180
/sys/class/usb/cdc-wdm0/uevent:MINOR=0
/sys/class/usb/cdc-wdm0/uevent:DEVNAME=cdc-wdm0
Do you think it would be possible to add that (or similar meta data) to
the cdc-wdmX devices? Maybe letting it default to "AT"? Or "unknown"?
Any thoughts on this? How do actual user applications deal with this?
Bjørn
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 2/3] net: usb: cdc_enc: New driver exposing USB CDC encapsulated protocols
[not found] ` <87ehv0eoah.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>
@ 2012-01-15 20:33 ` Oliver Neukum
0 siblings, 0 replies; 8+ messages in thread
From: Oliver Neukum @ 2012-01-15 20:33 UTC (permalink / raw)
To: Bjørn Mork
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, linux-usb-u79uwXL29TY76Z2rM5mHXA,
Dan Williams, Thomas Schäfer
Am Sonntag, 15. Januar 2012, 20:52:38 schrieb Bjørn Mork:
> There is one fundamental design issue which worries me though: cdc-wdm
> doesn't support multiple simultaneous readers/writers. This is sort of
> a blocker for my use case, as I will need to keep a reader/writer for
> the driver internal wwan management at all times. So two simultaneous
> clients are necessary to actually have something available for
> userspace at all.
What exactly do you need? The ability to write while another task is reading?
> Are there any reasons for this choice, and do you think it's feasible
Easier to get the locking right.
> changing it without destroying the advantage of the well tested code? I
> seem to remember that cdc-wdm allowed simultaneous reading/writing in
> the past, but this changed somewhere between 2.6.32 and 3.0. I have an
There might have been a locking bug which was easy to fix this way.
> Ericsson modem with two such AT command speaking interfaces, and I use a
> simple shell script to configure it. At one point I had to switch from
> using /dev/cdc-wdm0 to /dev/ttyACM0 because the former started hanging
> on constructs like this:
>
> while read x; do
> case "$x" in
> foo)
> echo -e "AT+bar\r" >/dev/cdc-wdm0
> ;;
> ...
> esac
> done </dev/cdc-wdm0
>
>
> This used to work with 2.6.32, but that might have been just pure luck?
> Anyway, that change really made cdc-wdm much less usable for me, and
> that sort of proves that supporting simultaneous read/write would be an
> improvement even for the existing supported devices.
Then feel free to add it, just beware of deadlocks.
> And then there are a couple of minor issues which I believe needs
> sorting out. If I understand the WDM spec correctly (without bothering
> to actually read if, of course), then it only defines the interface but
> not the protocol. Exactly like the embedded CDC Ethernet command
> interface. So I have the Ericsson modem which which speaks an AT
> command protocol, of course with a number of vendor specific AT commands
> included. And then I have the Huawei modem which speaks QMI. Just
> mapping these to a number of /dev/cdc-wdmX interfaces isn't going to
> help user applications much. They need to be told *what* protocol to
> use on the character device. And the driver should know this, either by
> detection or maybe best: vid/pid based tables and sane fallbacks.
I see.
> Do you think it would be possible to add that (or similar meta data) to
> the cdc-wdmX devices? Maybe letting it default to "AT"? Or "unknown"?
> Any thoughts on this? How do actual user applications deal with this?
Well, you need a pointer at the correct "control" interface anyway. Wouldn't you better
put the "protocol" attribute there?
Regards
Oliver
--
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] 8+ messages in thread
end of thread, other threads:[~2012-01-15 20:33 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-01-15 6:40 [PATCH v2 0/3] Adding new drivers for Qualcomm MSM based LTE modems Bjørn Mork
[not found] ` <cover.1326607234.git.bjorn-yOkvZcmFvRU@public.gmane.org>
2012-01-15 6:40 ` [PATCH v2 1/3] cdc_ether: allow vendor specific data interface if control interface is vendor specific Bjørn Mork
2012-01-15 6:40 ` [PATCH v2 2/3] net: usb: cdc_enc: New driver exposing USB CDC encapsulated protocols Bjørn Mork
[not found] ` <d11888532ee381806b35974e71af4c0122255c05.1326607234.git.bjorn-yOkvZcmFvRU@public.gmane.org>
2012-01-15 13:36 ` Oliver Neukum
[not found] ` <201201151436.29794.oliver-GvhC2dPhHPQdnm+yROfE0A@public.gmane.org>
2012-01-15 14:22 ` Bjørn Mork
2012-01-15 19:52 ` Bjørn Mork
[not found] ` <87ehv0eoah.fsf-lbf33ChDnrE/G1V5fR+Y7Q@public.gmane.org>
2012-01-15 20:33 ` Oliver Neukum
2012-01-15 6:40 ` [PATCH v2 3/3] net: usb: qmi_wwan: New driver for QMI WAN devices 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 NNTP newsgroup(s).