2013-08-16 13:43:45

by Enrico Mioso

[permalink] [raw]
Subject: [PATCH] net: huawei_cdc_ncm: Introduce new huawei_cdc_ncm driver

This driver supports devices using the NCM protocol as an encapsulation layer
for other protocols, like the E3131 Huawei 3G modem.

Suggested-by: Bjorn Mork <[email protected]>
Signed-off-by: Enrico Mioso <[email protected]>
---

drivers/net/usb/Kconfig | 11 ++
drivers/net/usb/Makefile | 1 +
drivers/net/usb/huawei_cdc_ncm.c | 210 ++++++++++++++++++++++++++++++++++++++
3 files changed, 222 insertions(+)
create mode 100644 drivers/net/usb/huawei_cdc_ncm.c
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index d84bfd4..6e56751 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -242,6 +242,17 @@ config USB_NET_CDC_NCM
* ST-Ericsson M343 HSPA Mobile Broadband Modem (reference design)
* Ericsson F5521gw Mobile Broadband Module

+config USB_NET_HUAWEI_CDC_NCM
+ tristate "Huawei-style CDC NCM support"
+ depends on USB_USBNET
+ select USB_WDM
+ select USB_NET_CDC_NCM
+ help
+ This driver aims to support huawei-style NCM devices, that use ncm as a
+ transport for other protocols.
+ To compile this driver as a module, choose M here: the module will be
+ called huawei_cdc_ncm.
+
config USB_NET_CDC_MBIM
tristate "CDC MBIM support"
depends on USB_USBNET
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index e817178..fd0e6a7 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_USB_IPHETH) += ipheth.o
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_NET_HUAWEI_CDC_NCM) += huawei_cdc_ncm.o
obj-$(CONFIG_USB_VL600) += lg-vl600.o
obj-$(CONFIG_USB_NET_QMI_WWAN) += qmi_wwan.o
obj-$(CONFIG_USB_NET_CDC_MBIM) += cdc_mbim.o
diff --git a/drivers/net/usb/huawei_cdc_ncm.c b/drivers/net/usb/huawei_cdc_ncm.c
new file mode 100644
index 0000000..d3426b9
--- /dev/null
+++ b/drivers/net/usb/huawei_cdc_ncm.c
@@ -0,0 +1,210 @@
+/*
+ * huawei_cdc_ncm.c - handles huawei-style NCM devices.
+ * Copyright (C) 2013 Enrico Mioso <[email protected]>
+ * This driver handles devices resembling the CDC NCM standard, but
+ * encapsulating another protocol inside it. An example are some Huawei 3G
+ * devices, exposing an embedded AT channel where you can set up the NCM
+ * connection.
+ * This code has been heavily inspired by the cdc_mbim.c driver, which is
+ * Copyright (c) 2012 Smith Micro Software, Inc.
+ * Copyright (c) 2012 Bj??rn Mork <[email protected]>
+ *
+ * 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/if_vlan.h>
+#include <linux/ip.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>
+#include <linux/usb/cdc_ncm.h>
+
+/* Driver data */
+struct huawei_cdc_ncm_state {
+ struct cdc_ncm_ctx *ctx;
+ atomic_t pmcount;
+ struct usb_driver *subdriver;
+ struct usb_interface *control;
+ struct usb_interface *data;
+};
+
+static int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on)
+{
+ struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
+ int rv = 0;
+
+ if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) || (!on && atomic_dec_and_test(&drvstate->pmcount))) {
+ rv = usb_autopm_get_interface(usbnet_dev->intf);
+ if (rv < 0)
+ goto err;
+ usbnet_dev->intf->needs_remote_wakeup = on;
+ usb_autopm_put_interface(usbnet_dev->intf);
+ }
+err:
+ return rv;
+}
+
+static int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf, int status)
+{
+ struct usbnet *usbnet_dev = usb_get_intfdata(intf);
+
+ /* can be called while disconnecting */
+ if (!usbnet_dev)
+ return 0;
+
+ return huawei_cdc_ncm_manage_power(usbnet_dev, status);
+}
+
+
+static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev, struct usb_interface *intf)
+{
+ struct cdc_ncm_ctx *ctx;
+ struct usb_driver *subdriver = ERR_PTR(-ENODEV);
+ int ret = -ENODEV;
+ struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
+
+ ret = cdc_ncm_bind_common(usbnet_dev, intf, 1); /* altsetting should be 1 for NCM devices */
+ if (ret)
+ goto err;
+
+ ctx = drvstate->ctx;
+
+ if (usbnet_dev->status)
+ subdriver = usb_cdc_wdm_register(ctx->control,
+ &usbnet_dev->status->desc,
+ 256, /* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */
+ huawei_cdc_ncm_wdm_manage_power);
+ if (IS_ERR(subdriver)) {
+ ret = PTR_ERR(subdriver);
+ cdc_ncm_unbind(usbnet_dev, intf);
+ goto err;
+ }
+
+ /* Prevent usbnet from using the status descriptor */
+ usbnet_dev->status = NULL;
+
+ drvstate->subdriver = subdriver;
+
+err:
+ return ret;
+}
+
+static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev, struct usb_interface *intf)
+{
+ struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
+ struct cdc_ncm_ctx *ctx = drvstate->ctx;
+
+ if (drvstate->subdriver && drvstate->subdriver->disconnect)
+ drvstate->subdriver->disconnect(ctx->control);
+ drvstate->subdriver = NULL;
+
+ cdc_ncm_unbind(usbnet_dev, intf);
+}
+
+static int huawei_cdc_ncm_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ int ret = 0;
+ struct usbnet *usbnet_dev = usb_get_intfdata(intf);
+ struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
+ struct cdc_ncm_ctx *ctx = drvstate->ctx;
+
+ if (ctx == NULL) {
+ ret = -1;
+ goto error;
+ }
+
+ ret = usbnet_suspend(intf, message);
+ if (ret < 0)
+ goto error;
+
+ if (intf == ctx->control && drvstate->subdriver && drvstate->subdriver->suspend)
+ ret = drvstate->subdriver->suspend(intf, message);
+ if (ret < 0)
+ usbnet_resume(intf);
+
+error:
+ return ret;
+}
+
+static int huawei_cdc_ncm_resume(struct usb_interface *intf)
+{
+ int ret = 0;
+ struct usbnet *usbnet_dev = usb_get_intfdata(intf);
+ struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
+ struct cdc_ncm_ctx *ctx = drvstate->ctx;
+ bool callsub = (intf == ctx->control && drvstate->subdriver && drvstate->subdriver->resume); /* should we call subdriver's resume? ? */
+
+ if (callsub)
+ ret = drvstate->subdriver->resume(intf);
+ if (ret < 0)
+ goto err;
+ ret = usbnet_resume(intf);
+ if (ret < 0 && callsub && drvstate->subdriver->suspend)
+ drvstate->subdriver->suspend(intf, PMSG_SUSPEND);
+err:
+ return ret;
+}
+
+static int huawei_cdc_ncm_check_connect(struct usbnet *usbnet_dev)
+{
+ struct cdc_ncm_ctx *ctx;
+
+ ctx = (struct cdc_ncm_ctx *)usbnet_dev->data[0];
+
+ if (ctx == NULL)
+ return 1; /* disconnected */
+
+ return !ctx->connected;
+}
+
+static const struct driver_info huawei_cdc_ncm_info = {
+ .description = "Huawei CDC NCM device",
+ .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN,
+ .bind = huawei_cdc_ncm_bind,
+ .unbind = huawei_cdc_ncm_unbind,
+ .check_connect = huawei_cdc_ncm_check_connect,
+ .manage_power = huawei_cdc_ncm_manage_power,
+ .rx_fixup = cdc_ncm_rx_fixup,
+ .tx_fixup = cdc_ncm_tx_fixup,
+};
+
+static const struct usb_device_id huawei_cdc_ncm_devs[] = {
+ /* Huawei NCM devices disguised as vendor specific */
+ { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16),
+ .driver_info = (unsigned long)&huawei_cdc_ncm_info,
+ },
+ { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46),
+ .driver_info = (unsigned long)&huawei_cdc_ncm_info,
+ },
+ { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76),
+ .driver_info = (unsigned long)&huawei_cdc_ncm_info,
+ },
+
+ /* Terminating entry */
+ {
+ },
+};
+MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs);
+
+static struct usb_driver huawei_cdc_ncm_driver = {
+ .name = "huawei_cdc_ncm",
+ .id_table = huawei_cdc_ncm_devs,
+ .probe = usbnet_probe,
+ .disconnect = usbnet_disconnect,
+ .suspend = huawei_cdc_ncm_suspend,
+ .resume = huawei_cdc_ncm_resume,
+ .reset_resume = huawei_cdc_ncm_resume,
+ .supports_autosuspend = 1,
+ .disable_hub_initiated_lpm = 1,
+};
+module_usb_driver(huawei_cdc_ncm_driver);
+MODULE_AUTHOR("Enrico Mioso <[email protected]>");
+MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support");
+MODULE_LICENSE("GPL");