summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Lin <stlin@nvidia.com>2011-02-14 11:31:11 -0800
committerVarun Colbert <vcolbert@nvidia.com>2011-02-18 15:07:29 -0800
commit3dd8821fd5ff588ac45e7fdd7b71f1cdc5c1eb22 (patch)
tree32cbf8b93b2dd2445cf2559e61583b34cc372ac9
parent301d02c71f07e00270173ef78a332339c441b3ee (diff)
usb: Adding USB CDC NCM class driver.
This driver supports devices conforming to the Communication Device Class (CDC) Network Control Model. The CDC specifications are available from <http://www.usb.org/>. This driver is implemented as a minidriver for usbnet driver framework. Bug 776360 Change-Id: If5e900f80edebc742536a59716aad546b714ba4c Reviewed-on: http://git-master/r/14921 Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com> Tested-by: Bharat Nihalani <bnihalani@nvidia.com>
-rw-r--r--drivers/net/usb/Kconfig18
-rw-r--r--drivers/net/usb/Makefile1
-rw-r--r--drivers/net/usb/cdc_ether.c44
-rw-r--r--drivers/net/usb/cdc_ncm.c687
-rw-r--r--drivers/net/usb/usbnet.c6
-rw-r--r--include/linux/usb/cdc.h1
-rw-r--r--include/linux/usb/usbnet.h4
7 files changed, 754 insertions, 7 deletions
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index d7b7018a1de1..5520f474b28f 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -196,6 +196,24 @@ config USB_NET_CDC_EEM
IEEE 802 "local assignment" bit is set in the address, a "usbX"
name is used instead.
+config USB_NET_CDCNCM
+ tristate "CDC NCM support (smart devices such as HSDPA and LTE modems)"
+ depends on USB_USBNET
+ select USB_NET_CDCETHER
+ default y
+ help
+ This option supports devices conforming to the Communication Device
+ Class (CDC) Network Control Model. The CDC specifications are
+ available from <http://www.usb.org/>.
+
+ This driver should work with at least the following devices:
+
+ * Ericsson Mobile Broadband Module (M570, F5521gw)
+ * ...
+
+ This driver creates an interface named "ncmX", where X depends on
+ what other networking devices you have in use.
+
config USB_NET_DM9601
tristate "Davicom DM9601 based USB 1.1 10/100 ethernet devices"
depends on USB_USBNET
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index b13a279663ba..6cda51edbe6e 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_USB_HSO) += hso.o
obj-$(CONFIG_USB_NET_AX8817X) += asix.o
obj-$(CONFIG_USB_NET_CDCETHER) += cdc_ether.o
obj-$(CONFIG_USB_NET_CDC_EEM) += cdc_eem.o
+obj-$(CONFIG_USB_NET_CDCNCM) += cdc_ncm.o
obj-$(CONFIG_USB_NET_DM9601) += dm9601.o
obj-$(CONFIG_USB_NET_SMSC75XX) += smsc75xx.o
obj-$(CONFIG_USB_NET_SMSC95XX) += smsc95xx.o
diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c
index b3fe0de40469..d24bdd5e31eb 100644
--- a/drivers/net/usb/cdc_ether.c
+++ b/drivers/net/usb/cdc_ether.c
@@ -69,6 +69,13 @@ static const u8 mbm_guid[16] = {
0xa6, 0x07, 0xc0, 0xff, 0xcb, 0x7e, 0x39, 0x2a,
};
+static int is_ncm(struct usb_interface_descriptor *desc)
+{
+ return desc->bInterfaceClass == USB_CLASS_COMM
+ && desc->bInterfaceSubClass == USB_CDC_SUBCLASS_NCM
+ && desc->bInterfaceProtocol == 0;
+}
+
/*
* probes control interface, claims data interface, collects the bulk
* endpoints, activates data interface (if needed), maybe sets MTU.
@@ -83,6 +90,7 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
struct cdc_state *info = (void *) &dev->data;
int status;
int rndis;
+ int ncm;
struct usb_driver *driver = driver_of(intf);
struct usb_cdc_mdlm_desc *desc = NULL;
struct usb_cdc_mdlm_detail_desc *detail = NULL;
@@ -127,6 +135,9 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
is_activesync(&intf->cur_altsetting->desc) ||
is_wireless_rndis(&intf->cur_altsetting->desc));
+ /* is cdc-ncm device? */
+ ncm = is_ncm(&intf->cur_altsetting->desc);
+
memset(info, 0, sizeof *info);
info->control = intf;
while (len > 3) {
@@ -264,6 +275,24 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
} else
goto bad_desc;
break;
+ case USB_CDC_NCM_TYPE:
+ if (info->ncm) {
+ dev_dbg(&intf->dev,
+ "extra NCM functional descriptor\n");
+ goto bad_desc;
+ }
+ info->ncm = (void *) buf;
+ if (info->ncm->bLength != sizeof *info->ncm) {
+ dev_dbg(&intf->dev, "CDC NCM len %u\n",
+ info->ncm->bLength);
+ goto bad_desc;
+ }
+
+ /* ignore bcdVersion, should be 1.0 anyway */
+
+ dev_dbg(&intf->dev, "bmNetworkCapabilities: 0x%02x\n",
+ info->ncm->bmNetworkCapabilities);
+ break;
}
next_desc:
len -= buf [0]; /* bLength */
@@ -285,11 +314,13 @@ next_desc:
goto bad_desc;
}
- } else if (!info->header || !info->u || (!rndis && !info->ether)) {
- dev_dbg(&intf->dev, "missing cdc %s%s%sdescriptor\n",
+ } else if (!info->header || !info->u || (!rndis && !info->ether) ||
+ (ncm && !info->ncm)) {
+ dev_dbg(&intf->dev, "missing cdc %s%s%s%sdescriptor\n",
info->header ? "" : "header ",
info->u ? "" : "union ",
- info->ether ? "" : "ether ");
+ info->ether ? "" : "ether ",
+ info->ncm ? "" : "ncm ");
goto bad_desc;
}
@@ -380,7 +411,7 @@ static void dumpspeed(struct usbnet *dev, __le32 *speeds)
__le32_to_cpu(speeds[1]) / 1000);
}
-static void cdc_status(struct usbnet *dev, struct urb *urb)
+void usbnet_cdc_status(struct usbnet *dev, struct urb *urb)
{
struct usb_cdc_notification *event;
@@ -420,6 +451,7 @@ static void cdc_status(struct usbnet *dev, struct urb *urb)
break;
}
}
+EXPORT_SYMBOL_GPL(usbnet_cdc_status);
static int cdc_bind(struct usbnet *dev, struct usb_interface *intf)
{
@@ -456,7 +488,7 @@ static const struct driver_info cdc_info = {
// .check_connect = cdc_check_connect,
.bind = cdc_bind,
.unbind = usbnet_cdc_unbind,
- .status = cdc_status,
+ .status = usbnet_cdc_status,
.manage_power = cdc_manage_power,
};
@@ -465,7 +497,7 @@ static const struct driver_info mbm_info = {
.flags = FLAG_WWAN,
.bind = cdc_bind,
.unbind = usbnet_cdc_unbind,
- .status = cdc_status,
+ .status = usbnet_cdc_status,
.manage_power = cdc_manage_power,
};
diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c
new file mode 100644
index 000000000000..ec676e7956b1
--- /dev/null
+++ b/drivers/net/usb/cdc_ncm.c
@@ -0,0 +1,687 @@
+/*
+ * USB CDC NCM class device driver
+ *
+ * Copyright (C) 2009-2011 NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/crc32.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+#include <linux/version.h>
+
+
+/***************************************************
+ NCM Class Definition
+***************************************************/
+
+/* NCM request code */
+enum ncm_control_requests {
+ GET_NTB_PARAMETERS = 0x80, /* required */
+ GET_NET_ADDRESS = 0x81,
+ SET_NET_ADDRESS = 0x82,
+ GET_NTB_FORMAT = 0x83,
+ SET_NTB_FORMAT = 0x84, /* 0: NTB-16; 1:NTB-32 */
+ GET_NTB_INPUT_SIZE = 0x85, /* required */
+ SET_NTB_INPUT_SIZE = 0x86, /* required */
+ GET_MAX_DATAGRAM_SIZE = 0x87,
+ SET_MAX_DATAGRAM_SIZE = 0x88,
+ GET_CRC_MODE = 0x89,
+ SET_CRC_MODE = 0x8A,
+};
+
+/* NCM GET_NTB_PARAMETERS Response */
+struct ntb_params {
+ __le16 wLength; /* should be 0x1c */
+ __le16 bmNtbFormatsSupported; /* bit0: NTB-16 (must); bit1: NTB-32 */
+ __le32 dwNtbInMaxSize;
+ __le16 wNdpInDivisor;
+ __le16 wNdpInPayloadRemainder;
+ __le16 wNdpInAlignment;
+ __le16 wReserved1; /* zero padding */
+ __le32 dwNtbOutMaxSize;
+ __le16 wNdpOutDivisor;
+ __le16 wNdpOutPayloadRemainder;
+ __le16 wNdpOutAlignment;
+ __le16 wReserved2; /* zero padding */
+} __attribute__ ((packed));
+
+/***************************************************
+ NCM Transfer Block Definition
+***************************************************/
+
+#define NTH16_SIGNATURE 0x484D434E
+#define NTH32_SIGNATURE 0x686D636E
+#define NDP16_SIGNATURE 0x304D434E
+#define NDP16_SIG_CRC32 0x314D434E
+#define NDP32_SIGNATURE 0x306D636E
+#define NDP32_SIG_CRC32 0x316D636E
+
+/* NTH16 NCM Transfer Header */
+struct nth16_hdr {
+ __le32 dwSignature; /* signature: must be "NCMH" */
+ __le16 wHeaderLength; /* header length: 0x0C */
+ __le16 wSequence; /* sequence number */
+ __le16 wBlockLength; /* size of this NTB in bytes */
+ __le16 wFpIndex; /* offset of the first NDP16 from the byte zero
+ of the NTB: must be multiple of 4 */
+} __attribute__ ((packed));
+
+/* NDP16 NCM Datagram Pointer Header */
+struct ndp16_hdr {
+ __le32 dwSignature; /* NDP16 signature */
+ __le16 wLength; /* size of this NDP16 in bytes, must be multiple
+ of 4 and must be at least 16 */
+ __le16 wNextFpIndex; /* byte index to the next NDP16 */
+} __attribute__ ((packed));
+
+/* NDP16 NCM Datagram Pointer Entry */
+struct ndp16_ent {
+ __le16 wDatagramIndex; /* offset from byte 0 of the NTB */
+ __le16 wDatagramLength; /* length */
+};
+
+/* NTH32 NCM Transfer Header */
+struct nth32_hdr {
+ __le32 dwSignature; /* signature: must be "ncmh" */
+ __le16 wHeaderLength; /* header length: 0x10 */
+ __le16 wSequence; /* sequence number */
+ __le32 dwBlockLength; /* size of this NTB in bytes */
+ __le32 dwFpIndex; /* offset of the first NDP32 from the byte zero
+ of the NTB: must be multiple of 4 */
+} __attribute__ ((packed));
+
+/* NDP32 NCM Datagram Pointer Header */
+struct ndp32_hdr {
+ __le32 dwSignature;
+ __le16 wLength;
+ __le16 wResvered6;
+ __le32 dwNextNdpIndex;
+ __le32 dwReserved12;
+} __attribute__ ((packed));
+
+/* NDP32 NCM Datagram Pointer Entry */
+struct ndp32_ent {
+ __le32 dwDatagramIndex;
+ __le32 dwDatagramLength;
+};
+
+/* used to check in NCM frames */
+#define MIN_NDP16_SIZE (sizeof(struct ndp16_hdr) + sizeof(struct ndp16_ent)*2)
+#define MIN_NDP32_SIZE (sizeof(struct ndp32_hdr) + sizeof(struct ndp32_ent)*2)
+
+/* used to creat out NCM frames */
+#define MAX_NDP16_ENTRIES 5
+#define MAX_NDP16_SIZE (sizeof(struct ndp16_hdr) + sizeof(struct ndp16_ent)*6)
+
+/* private driver data for each NCM device */
+struct driver_params {
+ struct ntb_params ntb_params;
+ u8 ntb_format;
+ u8 crc_mode;
+ u16 max_datagram_size;
+ u16 ndp_offset;
+ u16 last_rx_seq;
+ u16 tx_seq;
+};
+
+/***************************************************
+ NCM Rx frame fixup
+***************************************************/
+
+static int ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+ struct nth16_hdr hdr;
+ struct ndp16_hdr fph;
+ struct ndp16_ent e;
+ u8 *head;
+ u8 *fp;
+ struct driver_params *params = (struct driver_params *)dev->driver_priv;
+
+ head = (u8 *) skb->data;
+ memcpy(&hdr, head, sizeof(struct nth16_hdr));
+ le32_to_cpus(&hdr.dwSignature);
+ le16_to_cpus(&hdr.wHeaderLength);
+ le16_to_cpus(&hdr.wSequence);
+ le16_to_cpus(&hdr.wBlockLength);
+ le16_to_cpus(&hdr.wFpIndex);
+
+ /* check block header */
+ if (hdr.dwSignature != NTH16_SIGNATURE) {
+ netdev_err(dev->net, "Invalid NCM block signature!\n");
+ return 0;
+ }
+
+ if (hdr.wHeaderLength != sizeof(struct nth16_hdr)) {
+ netdev_err(dev->net, "Invalid NCM block header size!\n");
+ return 0;
+ }
+
+ if (hdr.wBlockLength != skb->len) {
+ netdev_err(dev->net,
+ "NCM block size doesn't match the input size (%u != %u)\n",
+ hdr.wBlockLength, skb->len);
+ return 0;
+ }
+
+ if (!IS_ALIGNED(hdr.wFpIndex, 4)) {
+ netdev_err(dev->net, "wFpIndex is not 4-byte aligned!\n");
+ return 0;
+ }
+
+ /* missing frame? */
+ if ((hdr.wSequence - params->last_rx_seq) > 1)
+ netdev_warn(dev->net, "missing frame (seq# %u last seq# %u)\n",
+ hdr.wSequence, params->last_rx_seq);
+ params->last_rx_seq = hdr.wSequence;
+
+ fp = head + hdr.wFpIndex;
+ memcpy(&fph, fp, sizeof(struct ndp16_hdr));
+ le32_to_cpus(&fph.dwSignature);
+ le16_to_cpus(&fph.wLength);
+ le16_to_cpus(&fph.wNextFpIndex);
+
+ /* check frame pointer header */
+ if ((params->crc_mode && fph.dwSignature != NDP16_SIG_CRC32) ||
+ (fph.dwSignature != NDP16_SIGNATURE) ||
+ (fph.wLength < MIN_NDP16_SIZE) || !IS_ALIGNED(fph.wLength, 4)) {
+ netdev_err(dev->net, "Invalid NCM frame pointer header!\n");
+ return 0;
+ }
+
+ fp += sizeof(struct ndp16_hdr);
+ memcpy(&e, fp, sizeof(struct ndp16_ent));
+ le16_to_cpus(&e.wDatagramIndex);
+ le16_to_cpus(&e.wDatagramLength);
+
+ while (e.wDatagramLength > 0) {
+ unsigned char *frame;
+ struct sk_buff *new_skb;
+
+ if (e.wDatagramLength > params->max_datagram_size) {
+ netdev_err(dev->net, "Bad frame length: %d\n",
+ e.wDatagramLength);
+ return 0;
+ }
+
+ frame = head + e.wDatagramIndex;
+
+ new_skb = skb_clone(skb, GFP_ATOMIC);
+ if (new_skb) {
+ new_skb->len = e.wDatagramLength;
+ new_skb->data = frame;
+ skb_set_tail_pointer(new_skb, e.wDatagramLength);
+ usbnet_skb_return(dev, new_skb);
+ } else {
+ return 0;
+ }
+
+ /* next frame */
+ fp += sizeof(struct ndp16_ent);
+ memcpy(&e, fp, sizeof(struct ndp16_ent));
+ le16_to_cpus(&e.wDatagramIndex);
+ le16_to_cpus(&e.wDatagramLength);
+
+ /* check sequence end mark */
+ if (e.wDatagramIndex == 0)
+ break;
+ }
+
+ skb_pull(skb, skb->len);
+
+ return 1;
+}
+
+/***************************************************
+ NCM Tx frame fixup
+***************************************************/
+
+static struct sk_buff *ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
+ gfp_t flags)
+{
+ u32 blk_size;
+ u32 padlen;
+ u32 dgm_offset;
+ u32 dgm_len;
+ int headroom = skb_headroom(skb);
+ int tailroom = skb_tailroom(skb);
+ struct nth16_hdr hdr;
+ struct ndp16_hdr fph;
+ struct ndp16_ent e;
+ struct driver_params *params = (struct driver_params *)dev->driver_priv;
+
+ dgm_offset = params->ndp_offset + 32;
+ dgm_len = ALIGN(skb->len, params->ntb_params.wNdpOutDivisor);
+
+ blk_size = dgm_len + dgm_offset;
+ if (blk_size > params->ntb_params.dwNtbOutMaxSize) {
+ netdev_err(dev->net, "Tx blk size is too big: %d\n", blk_size);
+ return NULL;
+ }
+
+ padlen = dgm_len - skb->len;
+
+ /* create an NCM transfer block */
+ hdr.dwSignature = cpu_to_le32(NTH16_SIGNATURE);
+ hdr.wHeaderLength = cpu_to_le16(sizeof(struct nth16_hdr));
+ hdr.wSequence = cpu_to_le16(params->tx_seq);
+ if ((blk_size % dev->maxpacket) == 0)
+ hdr.wBlockLength = cpu_to_le16(blk_size + 1);
+ else
+ hdr.wBlockLength = cpu_to_le16(blk_size);
+
+ /* FP starts right after the block header */
+ hdr.wFpIndex = cpu_to_le16(params->ndp_offset);
+ params->tx_seq++;
+
+ fph.dwSignature = cpu_to_le32(NDP16_SIGNATURE);
+ /* send only one frame */
+ fph.wLength = cpu_to_le16(sizeof(struct ndp16_hdr) +
+ sizeof(struct ndp16_ent) * 2);
+ fph.wNextFpIndex = 0;
+
+ /* first frame */
+ e.wDatagramIndex = cpu_to_le16(dgm_offset);
+ e.wDatagramLength = cpu_to_le16(skb->len);
+
+ if ((!skb_cloned(skb))
+ && ((headroom + tailroom) >= (dgm_offset + padlen))) {
+ if ((headroom < dgm_offset) || (tailroom < padlen)) {
+ skb->data = memmove(skb->head + dgm_offset, skb->data,
+ skb->len);
+ skb_set_tail_pointer(skb, skb->len);
+ }
+ } else {
+ struct sk_buff *skb2;
+ skb2 = skb_copy_expand(skb, dgm_offset, padlen, flags);
+ dev_kfree_skb_any(skb);
+ skb = skb2;
+ if (!skb)
+ return NULL;
+ }
+
+ skb_push(skb, dgm_offset);
+ memset(skb->data, 0, dgm_offset);
+ memcpy(skb->data, &hdr, sizeof(struct nth16_hdr));
+ memcpy(skb->data + params->ndp_offset, &fph, sizeof(struct ndp16_hdr));
+ memcpy(skb->data + params->ndp_offset + sizeof(struct ndp16_hdr), &e,
+ sizeof(struct ndp16_ent));
+ skb_put(skb, padlen);
+
+ if (skb->len != blk_size) {
+ netdev_err(dev->net, "skb->len != blk_size (%d)\n", blk_size);
+ return NULL;
+ }
+
+ return skb;
+}
+
+/***************************************************
+ Send NCM control message
+***************************************************/
+
+static int send_ctrl_msg(struct usbnet *dev,
+ u8 dir,
+ u8 request, u16 value, u16 index, void *data, int size)
+{
+ int retval;
+
+ retval = usb_control_msg(dev->udev, dir ?
+ usb_rcvctrlpipe(dev->udev, 0) :
+ usb_sndctrlpipe(dev->udev, 0),
+ request,
+ dir | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ value, index, data, size, 1000);
+
+ if (retval != size)
+ netdev_err(dev->net, "usb_control_msg error: %d\n", retval);
+
+ return (retval < 0) ? retval : 0;
+}
+
+/***************************************************
+ Change MTU
+***************************************************/
+
+static int ncm_change_mtu(struct net_device *net, int new_mtu)
+{
+ struct usbnet *dev = netdev_priv(net);
+ struct cdc_state *info = (void *)dev->data;
+ struct driver_params *params = (struct driver_params *)dev->driver_priv;
+ u16 max_datagram_size;
+ int hard_mtu = new_mtu + net->hard_header_len;
+
+ netdev_dbg(dev->net, "new_mtu=%d\n", new_mtu);
+
+ if (new_mtu <= 0 || hard_mtu > 16384)
+ return -EINVAL;
+
+ if ((hard_mtu % dev->maxpacket) == 0)
+ return -EDOM;
+
+ max_datagram_size = cpu_to_le16(hard_mtu);
+
+ /* assuming the device won't allow changing the max datagram size
+ * larger than max NTB in/out size
+ */
+ if (send_ctrl_msg(dev, USB_DIR_OUT, SET_MAX_DATAGRAM_SIZE, 0,
+ info->u->bMasterInterface0, &max_datagram_size, 2)) {
+ netdev_warn(dev->net,
+ "SET_MAX_DATAGRAM_SIZE request failed!\n");
+ }
+
+ if (send_ctrl_msg(dev, USB_DIR_IN, GET_MAX_DATAGRAM_SIZE, 0,
+ info->u->bMasterInterface0, &max_datagram_size, 2)) {
+ netdev_warn(dev->net,
+ "GET_MAX_DATAGRAM_SIZE request failed!\n");
+ } else {
+ le16_to_cpus(&max_datagram_size);
+ netdev_dbg(dev->net,
+ "max datagram size: %d\n", max_datagram_size);
+ }
+
+ if (max_datagram_size < hard_mtu)
+ return -EINVAL;
+
+ /* update the max datagram size in the driver parameters */
+ if (params->max_datagram_size < max_datagram_size)
+ params->max_datagram_size = max_datagram_size;
+
+ net->mtu = new_mtu;
+ dev->hard_mtu = hard_mtu;
+
+ return 0;
+}
+
+static const struct net_device_ops ncm_netdev_ops = {
+ .ndo_open = usbnet_open,
+ .ndo_stop = usbnet_stop,
+ .ndo_start_xmit = usbnet_start_xmit,
+ .ndo_tx_timeout = usbnet_tx_timeout,
+ .ndo_change_mtu = ncm_change_mtu,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+};
+
+/***************************************************
+ CDC NCM Binding
+***************************************************/
+
+static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+ struct cdc_state *info = (void *)&dev->data;
+ int retval;
+ struct usb_driver *driver = driver_of(intf);
+ struct driver_params *params;
+ u32 max_ntb_in_size = 0;
+ u16 max_datagram_size = 0;
+ u16 ntb_format = 0;
+ u16 crc_mode = 0;
+
+ retval = usbnet_generic_cdc_bind(dev, intf);
+ if (retval < 0)
+ return retval;
+
+ /* create private driver data */
+ dev->driver_priv = kmalloc(sizeof(struct driver_params), GFP_KERNEL);
+ if (dev->driver_priv == NULL) {
+ usb_set_intfdata(info->data, NULL);
+ usb_driver_release_interface(driver, info->data);
+ return -ENOMEM;
+ }
+
+ params = (struct driver_params *)dev->driver_priv;
+ memset(params, 0, sizeof(struct driver_params));
+
+ /* get NTB parameters */
+ if (send_ctrl_msg(dev, USB_DIR_IN, GET_NTB_PARAMETERS, 0,
+ info->u->bMasterInterface0,
+ &params->ntb_params, sizeof(struct ntb_params))) {
+ dev_dbg(&intf->dev, "GET_NTB_PARAMETERS request failed!\n");
+ goto bad_param;
+ } else {
+ le16_to_cpus(&params->ntb_params.wLength);
+ le16_to_cpus(&params->ntb_params.bmNtbFormatsSupported);
+ le16_to_cpus(&params->ntb_params.dwNtbInMaxSize);
+ le16_to_cpus(&params->ntb_params.wNdpInDivisor);
+ le16_to_cpus(&params->ntb_params.wNdpInPayloadRemainder);
+ le16_to_cpus(&params->ntb_params.wNdpInAlignment);
+ le16_to_cpus(&params->ntb_params.dwNtbOutMaxSize);
+ le16_to_cpus(&params->ntb_params.wNdpOutDivisor);
+ le16_to_cpus(&params->ntb_params.wNdpOutPayloadRemainder);
+ le16_to_cpus(&params->ntb_params.wNdpOutAlignment);
+
+ dev_dbg(&intf->dev, "bmNtbFormatsSupported: %u\n",
+ params->ntb_params.bmNtbFormatsSupported);
+ dev_dbg(&intf->dev, "ndwNtbInMaxSize: %u\n",
+ params->ntb_params.dwNtbInMaxSize);
+ dev_dbg(&intf->dev, "nwNdpInDivisor: %u\n",
+ params->ntb_params.wNdpInDivisor);
+ dev_dbg(&intf->dev, "nwNdpInPayloadRemainder: %u\n",
+ params->ntb_params.wNdpInPayloadRemainder);
+ dev_dbg(&intf->dev, "nwNdpInAlignment: %u\n",
+ params->ntb_params.wNdpInAlignment);
+ dev_dbg(&intf->dev, "ndwNtbOutMaxSize: %u\n",
+ params->ntb_params.dwNtbOutMaxSize);
+ dev_dbg(&intf->dev, "nwNdpOutDivisor: %u\n",
+ params->ntb_params.wNdpOutDivisor);
+ dev_dbg(&intf->dev, "nwNdpOutPayloadRemainder: %u\n",
+ params->ntb_params.wNdpOutPayloadRemainder);
+ dev_dbg(&intf->dev, "nwNdpOutAlignment: %u\n",
+ params->ntb_params.wNdpOutAlignment);
+ }
+
+ /* NCM spec 6.2.7 */
+ if (params->ntb_params.dwNtbInMaxSize < 2048) {
+ dev_dbg(&intf->dev, "invalid NtbInMaxSize\n");
+ goto bad_param;
+ }
+
+ /* get max NTB input size */
+ if (send_ctrl_msg(dev, USB_DIR_IN, GET_NTB_INPUT_SIZE, 0,
+ info->u->bMasterInterface0, &max_ntb_in_size, 4)) {
+ dev_dbg(&intf->dev, "GET_NTB_INPUT_SIZE request failed!\n");
+ goto bad_param;
+ } else {
+ le32_to_cpus(&max_ntb_in_size);
+ dev_dbg(&intf->dev, "max ntb input size: %u\n",
+ max_ntb_in_size);
+ }
+
+ dev->rx_urb_size = max_ntb_in_size;
+
+ /* --------- the following control requests are optional --------- */
+
+ /* get net address */
+ if (send_ctrl_msg(dev, USB_DIR_IN, GET_NET_ADDRESS, 0,
+ info->u->bMasterInterface0, dev->net->dev_addr,
+ ETH_ALEN)) {
+ dev_dbg(&intf->dev, "GET_NET_ADDRESS request failed!\n");
+ } else {
+ dev_dbg(&intf->dev, "HW addr: "MAC_FMT"\n",
+ dev->net->dev_addr[0],
+ dev->net->dev_addr[1],
+ dev->net->dev_addr[2],
+ dev->net->dev_addr[3],
+ dev->net->dev_addr[4],
+ dev->net->dev_addr[5]);
+ }
+
+ /* get NTB format */
+ if (send_ctrl_msg(dev, USB_DIR_IN, GET_NTB_FORMAT, 0,
+ info->u->bMasterInterface0, &ntb_format, 2)) {
+ dev_dbg(&intf->dev, "GET_NTB_FORMAT request failed!\n");
+ } else {
+ le16_to_cpus(&ntb_format);
+ dev_dbg(&intf->dev, "ntb_format: %s\n", (ntb_format) ?
+ "NTB32" : "NTB16");
+ }
+
+ /* TODO: only support ntb16 for now - will support ntb32 for usb 3.0 */
+ if (ntb_format != 0) {
+ dev_dbg(&intf->dev, "The ntb32 is not supported!\n");
+ goto bad_param;
+ }
+
+ /* get max datagram size */
+ if (send_ctrl_msg(dev, USB_DIR_IN, GET_MAX_DATAGRAM_SIZE, 0,
+ info->u->bMasterInterface0, &max_datagram_size, 2)) {
+ dev_dbg(&intf->dev, "GET_MAX_DATAGRAM_SIZE request failed!\n");
+ } else {
+ le16_to_cpus(&max_datagram_size);
+ dev_dbg(&intf->dev, "max datagram size: %d\n",
+ max_datagram_size);
+ }
+
+ if (max_datagram_size < ETH_FRAME_LEN)
+ goto bad_param;
+
+ le16_to_cpus(&max_datagram_size);
+ params->max_datagram_size = max_datagram_size;
+
+ /* get CRC mode */
+ if (send_ctrl_msg(dev, USB_DIR_IN, GET_CRC_MODE, 0,
+ info->u->bMasterInterface0, &crc_mode, 2)) {
+ dev_dbg(&intf->dev, "GET_CRC_MODE request failed!\n");
+ } else {
+ le16_to_cpus(&crc_mode);
+ dev_dbg(&intf->dev, "crc mode: %d\n", crc_mode);
+ }
+
+ /* disable crc mode in case it is enabled */
+ if (crc_mode) {
+ crc_mode = 0;
+ if (send_ctrl_msg(dev, USB_DIR_OUT, SET_CRC_MODE, 0,
+ info->u->bMasterInterface0, &crc_mode, 2)) {
+ dev_dbg(&intf->dev, "SET_CRC_MODE request failed!\n");
+ goto bad_param;
+ }
+ }
+
+ params->ntb_format = ntb_format;
+ params->crc_mode = crc_mode;
+ params->ndp_offset = ALIGN(sizeof(struct nth16_hdr),
+ params->ntb_params.wNdpOutAlignment);
+
+ /* reserve headroom space for NTH and NDP to avoid memmove */
+ dev->net->needed_headroom = params->ndp_offset + 32;
+ dev->net->needed_tailroom = params->ntb_params.wNdpOutDivisor;
+ dev->net->netdev_ops = &ncm_netdev_ops;
+
+ strcpy(dev->net->name, "ncm%d");
+
+ return 0;
+
+bad_param:
+ usb_set_intfdata(info->data, NULL);
+ usb_driver_release_interface(driver, info->data);
+ kfree(dev->driver_priv);
+ return -ENODEV;
+}
+
+/***************************************************
+ CDC NCM Unbind
+***************************************************/
+
+static void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+ usbnet_cdc_unbind(dev, intf);
+
+ /* free private driver data */
+ kfree(dev->driver_priv);
+}
+
+static int cdc_manage_power(struct usbnet *dev, int on)
+{
+ dev->intf->needs_remote_wakeup = on;
+ return 0;
+}
+
+static const struct driver_info ncm_info = {
+ .description = "NCM device",
+ .flags = FLAG_ETHER,
+ .tx_fixup = ncm_tx_fixup,
+ .rx_fixup = ncm_rx_fixup,
+ .bind = cdc_ncm_bind,
+ .unbind = cdc_ncm_unbind,
+ .status = usbnet_cdc_status,
+ .manage_power = cdc_manage_power,
+};
+
+static const struct driver_info mbm_info = {
+ .description = "MBM device",
+ .flags = FLAG_WWAN,
+ .tx_fixup = ncm_tx_fixup,
+ .rx_fixup = ncm_rx_fixup,
+ .bind = cdc_ncm_bind,
+ .unbind = cdc_ncm_unbind,
+ .status = usbnet_cdc_status,
+ .manage_power = cdc_manage_power,
+};
+
+static const struct usb_device_id products[] = {
+ {
+ /* standard NCM class device */
+ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM,
+ USB_CDC_PROTO_NONE),
+ .driver_info = (unsigned long)&ncm_info,
+ },
+ {
+ /* Ericsson f5521gw */
+ USB_DEVICE(0x0bdb,0x190d),
+ .driver_info = (unsigned long)&mbm_info,
+ },
+ {},
+};
+
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver ncm_driver = {
+ .name = "CDC NCM device",
+ .id_table = products,
+ .probe = usbnet_probe,
+ .disconnect = usbnet_disconnect,
+ .suspend = usbnet_suspend,
+ .resume = usbnet_resume,
+ .reset_resume = usbnet_resume,
+ .supports_autosuspend = 1,
+};
+
+static int __init ncm_init(void)
+{
+ return usb_register(&ncm_driver);
+}
+
+module_init(ncm_init);
+
+static void __exit ncm_exit(void)
+{
+ usb_deregister(&ncm_driver);
+}
+
+module_exit(ncm_exit);
+
+MODULE_AUTHOR("Steve Lin");
+MODULE_DESCRIPTION("USB NCM devices");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
index c04d49e31f81..35cf60e4df88 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -43,6 +43,7 @@
#include <linux/mii.h>
#include <linux/usb.h>
#include <linux/usb/usbnet.h>
+#include <linux/usb/cdc.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/pm_runtime.h>
@@ -100,6 +101,7 @@ int usbnet_get_endpoints(struct usbnet *dev, struct usb_interface *intf)
struct usb_host_interface *alt = NULL;
struct usb_host_endpoint *in = NULL, *out = NULL;
struct usb_host_endpoint *status = NULL;
+ struct cdc_state *info = (struct cdc_state *)&dev->data;
for (tmp = 0; tmp < intf->num_altsetting; tmp++) {
unsigned ep;
@@ -115,6 +117,10 @@ int usbnet_get_endpoints(struct usbnet *dev, struct usb_interface *intf)
struct usb_host_endpoint *e;
int intr = 0;
+ if (info->ncm && alt->desc.bInterfaceProtocol !=
+ NCM_DATA_CLASS_PROTOCOCL_CODE)
+ continue;
+
e = alt->endpoint + ep;
switch (e->desc.bmAttributes) {
case USB_ENDPOINT_XFER_INT:
diff --git a/include/linux/usb/cdc.h b/include/linux/usb/cdc.h
index c117a68d04a7..c4d4cce83857 100644
--- a/include/linux/usb/cdc.h
+++ b/include/linux/usb/cdc.h
@@ -32,6 +32,7 @@
#define USB_CDC_PROTO_EEM 7
+#define NCM_DATA_CLASS_PROTOCOCL_CODE 0x01
/*-------------------------------------------------------------------------*/
/*
diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h
index 7ae27a473818..6d4922fd707a 100644
--- a/include/linux/usb/usbnet.h
+++ b/include/linux/usb/usbnet.h
@@ -43,7 +43,7 @@ struct usbnet {
/* protocol/interface state */
struct net_device *net;
int msg_enable;
- unsigned long data[5];
+ unsigned long data[6];
u32 xid;
u32 hard_mtu; /* count any extra framing */
size_t rx_urb_size; /* size for rx urbs */
@@ -161,12 +161,14 @@ struct cdc_state {
struct usb_cdc_header_desc *header;
struct usb_cdc_union_desc *u;
struct usb_cdc_ether_desc *ether;
+ struct usb_cdc_ncm_desc *ncm;
struct usb_interface *control;
struct usb_interface *data;
};
extern int usbnet_generic_cdc_bind(struct usbnet *, struct usb_interface *);
extern void usbnet_cdc_unbind(struct usbnet *, struct usb_interface *);
+extern void usbnet_cdc_status(struct usbnet *dev, struct urb *urb);
/* CDC and RNDIS support the same host-chosen packet filters for IN transfers */
#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \