Re: [RFC] CDC NCM USB host driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Hans Petter Selasky <hans.petter.selasky@xxxxxxxxxxxxxx>

This patch provides USB host driver for CDC NCM.

Fixed up issues in first RFC pointed out by Oliver Neukum:
- DMA on stack.
- Timer race btw del_timer and kfree.

Feedback on this patch is welcome!

Signed-off-by: Sjur Braendeland <sjur.brandeland@xxxxxxxxxxxxxx>

---
 drivers/net/usb/Kconfig   |   15 +
 drivers/net/usb/Makefile  |    2 +-
 drivers/net/usb/cdc_ncm.c | 1189 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/usb/cdc_ncm.h |  222 +++++++++
 4 files changed, 1427 insertions(+), 1 deletions(-)
 create mode 100644 drivers/net/usb/cdc_ncm.c
 create mode 100644 drivers/net/usb/cdc_ncm.h

diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index d7b7018..cfbe868 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -196,6 +196,21 @@ config USB_NET_CDC_EEM
 	  IEEE 802 "local assignment" bit is set in the address, a "usbX"
 	  name is used instead.
 
+config USB_NET_CDC_NCM
+	tristate "CDC NCM support"
+	depends on USB_USBNET
+	default y
+	help
+	  This driver provides support for CDC NCM (Network Control Model
+	  Device USB Class Specification). The CDC NCM specification is
+	  available from <http://www.usb.org/>.
+
+	  Say "y" to link the driver statically, or "m" to build a
+	  dynamically linked module.
+
+	  This driver should work with at least the following devices:
+	    * ST-Ericsson M570 (reference design)
+
 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 b13a279..ee4c506 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -25,4 +25,4 @@ obj-$(CONFIG_USB_NET_INT51X1)	+= int51x1.o
 obj-$(CONFIG_USB_CDC_PHONET)	+= cdc-phonet.o
 obj-$(CONFIG_USB_IPHETH)	+= ipheth.o
 obj-$(CONFIG_USB_SIERRA_NET)	+= sierra_net.o
-
+obj-$(CONFIG_USB_NET_CDC_NCM)   += cdc_ncm.o
diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c
new file mode 100644
index 0000000..6d8c2ae
--- /dev/null
+++ b/drivers/net/usb/cdc_ncm.c
@@ -0,0 +1,1189 @@
+/*
+ * cdc_ncm.c
+ * Copyright (C) ST-Ericsson AB 2010
+ * Contact: Sjur Braendeland <sjur.brandeland@xxxxxxxxxxxxxx>
+ * Original author:Hans Petter Selasky <hans.petter.selasky@xxxxxxxxxxxxxx>
+ *
+ * USB Host Driver for Network Control Model (NCM)
+ * http://www.usb.org/developers/devclass_docs/NCM10.zip
+ *
+ * The NCM encoding, decoding and initialisation logic
+ * derives from FreeBSD 8.x. if_cdce.c and if_cdcereg.h
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose this file to be licensed under the terms
+ * of the GNU General Public License (GPL) Version 2 or the 2-clause
+ * BSD license listed below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/ctype.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/version.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+
+#define	DRIVER_VERSION "0.9"
+
+#include <linux/usb/usbnet.h>
+#include "cdc_ncm.h"
+
+static void cdc_ncm_tx_set_rem_skb(struct cdc_ncm_softc *,
+	struct sk_buff *);
+static void cdc_ncm_tx_timeout(unsigned long arg);
+static const struct driver_info cdc_ncm_info;
+static struct usb_driver cdc_ncm_driver;
+static struct ethtool_ops cdc_ncm_ethtool_ops;
+
+static const struct usb_device_id cdc_devs[] = {
+	{ USB_INTERFACE_INFO(USB_CLASS_COMM,
+		USB_SUBCLASS_CODE_NETWORK_CONTROL_MODEL, USB_CDC_PROTO_NONE),
+		.driver_info = (unsigned long)&cdc_ncm_info,
+	},
+	{
+	},
+};
+
+MODULE_DEVICE_TABLE(usb, cdc_devs);
+
+static void
+cdc_ncm_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	strncpy(info->driver, dev->driver_name, sizeof(info->driver));
+	strncpy(info->version, DRIVER_VERSION, sizeof(info->version));
+	strncpy(info->fw_version, dev->driver_info->description,
+		sizeof(info->fw_version));
+	usb_make_path(dev->udev, info->bus_info, sizeof(info->bus_info));
+}
+
+static int
+cdc_ncm_do_request(struct cdc_ncm_softc *sc,
+			struct cdc_ncm_device_request *req,
+			void *data, u16 flags, u16 *actlen, u16 timeout)
+{
+	int err;
+
+	err = usb_control_msg(sc->sc_udev, (req->bmRequestType & 0x80) ?
+		usb_rcvctrlpipe(sc->sc_udev, 0) :
+		usb_sndctrlpipe(sc->sc_udev, 0),
+		req->bRequest, req->bmRequestType,
+		get_unaligned_le16(req->wValue),
+		get_unaligned_le16(req->wIndex), data,
+		get_unaligned_le16(req->wLength), timeout);
+
+	if (err < 0) {
+		if (actlen)
+			*actlen = 0;
+		return err;
+	}
+
+	if (actlen)
+		*actlen = err;
+
+	return 0;
+}
+
+static u8
+cdc_ncm_get_nibble(char c)
+{
+	if (c >= 'a' && c <= 'z')
+		return (c - 'a' + 10) & 0xF;
+	else if (c >= 'A' && c <= 'Z')
+		return (c - 'A' + 10) & 0xF;
+	else
+		return (c - '0') & 0xF;
+}
+
+static int
+cdc_ncm_get_ethernet_addr(struct usbnet *dev,
+				const struct usb_cdc_ether_desc *e)
+{
+	char buf[14];
+	int tmp;
+
+	tmp = usb_string(dev->udev, e->iMACAddress, buf, sizeof(buf));
+	if (tmp < 12)
+		return -EINVAL;
+
+	for (tmp = 0; tmp != 12; tmp += 2) {
+		dev->net->dev_addr[tmp / 2] =
+		  (cdc_ncm_get_nibble(buf[tmp]) << 4) |
+		  cdc_ncm_get_nibble(buf[tmp + 1]);
+	}
+	return 0;
+}
+
+static u8
+cdc_ncm_setup(struct cdc_ncm_softc *sc)
+{
+	struct cdc_ncm_device_request req;
+	u32 val;
+	u8 flags;
+	u8 iface_no;
+	int err;
+
+	iface_no = sc->sc_control->cur_altsetting->desc.bInterfaceNumber;
+
+	req.bmRequestType = USB_TYPE_CLASS |
+		USB_DIR_IN | USB_RECIP_INTERFACE;
+	req.bRequest = CDC_NCM_GET_NTB_PARAMETERS;
+	req.wValue[0] = 0;
+	req.wValue[1] = 0;
+	req.wIndex[0] = iface_no;
+	req.wIndex[1] = 0;
+	req.wLength[0] = sizeof(sc->sc_ncm_parm);
+	req.wLength[1] = 0;
+
+	err = cdc_ncm_do_request(sc, &req,
+				&sc->sc_ncm_parm, 0, NULL, 1000 /* ms */);
+	if (err)
+		return 1;
+
+	/* Read correct set of parameters according to device mode */
+
+	sc->sc_rx_ncm.rx_max = get_unaligned_le16(
+		sc->sc_ncm_parm.dwNtbInMaxSize);
+	sc->sc_rx_ncm.tx_max = get_unaligned_le16(
+		sc->sc_ncm_parm.dwNtbOutMaxSize);
+	sc->sc_rx_ncm.tx_remainder = get_unaligned_le16(
+		sc->sc_ncm_parm.wNdpOutPayloadRemainder);
+	sc->sc_rx_ncm.tx_modulus = get_unaligned_le16(
+		sc->sc_ncm_parm.wNdpOutDivisor);
+	sc->sc_rx_ncm.tx_struct_align = get_unaligned_le16(
+		sc->sc_ncm_parm.wNdpOutAlignment);
+
+	if (sc->sc_func_desc != NULL)
+		flags = sc->sc_func_desc->bmNetworkCapabilities;
+	else
+		flags = 0;
+
+	/* compute the maximum number of TX datagrams */
+	/* we leave one entry for zero-padding */
+
+	if (flags & CDC_NCM_CAP_NTBINPUTSIZE) {
+		val = get_unaligned_le16(sc->sc_ncm_parm.wNtbOutMaxDatagrams);
+
+		sc->sc_rx_ncm.tx_max_datagrams = val;
+
+		if ((val <= 0) || (val > (CDC_NCM_DPT_DATAGRAMS_MAX - 1))) {
+			sc->sc_rx_ncm.tx_max_datagrams =
+			    (CDC_NCM_DPT_DATAGRAMS_MAX - 1);
+		}
+	} else {
+		sc->sc_rx_ncm.tx_max_datagrams =
+		    (CDC_NCM_DPT_DATAGRAMS_MAX - 1);
+	}
+
+	/* Verify maximum receive length */
+
+	if (err || (sc->sc_rx_ncm.rx_max < 32) ||
+	    (sc->sc_rx_ncm.rx_max > CDC_NCM_RX_MAXLEN)) {
+		pr_debug("Using default maximum receive length\n");
+		sc->sc_rx_ncm.rx_max = CDC_NCM_RX_MAXLEN;
+	}
+
+	/* Verify maximum transmit length */
+
+	if (err || (sc->sc_rx_ncm.tx_max < 32) ||
+	    (sc->sc_rx_ncm.tx_max > CDC_NCM_TX_MAXLEN)) {
+		pr_debug("Using default maximum transmit length\n");
+		sc->sc_rx_ncm.tx_max = CDC_NCM_TX_MAXLEN;
+	}
+
+	/*
+	 * Verify that the structure alignment is:
+	 * - power of two
+	 * - not greater than the maximum transmit length
+	 * - not less than four bytes
+	 */
+	val = sc->sc_rx_ncm.tx_struct_align;
+
+	if (err || (val < 4) || (val != ((-val) & val)) ||
+	    (val >= sc->sc_rx_ncm.tx_max)) {
+		pr_debug("Using default other alignment: 4 bytes\n");
+		sc->sc_rx_ncm.tx_struct_align = 4;
+	}
+
+	/*
+	 * Verify that the payload alignment is:
+	 * - power of two
+	 * - not greater than the maximum transmit length
+	 * - not less than four bytes
+	 */
+	val = sc->sc_rx_ncm.tx_modulus;
+
+	if (err || (val < 4) || (val != ((-val) & val)) ||
+	    (val >= sc->sc_rx_ncm.tx_max)) {
+		pr_debug("Using default transmit modulus: 4 bytes\n");
+		sc->sc_rx_ncm.tx_modulus = 4;
+	}
+
+	/* Verify that the payload remainder */
+
+	if (err || (sc->sc_rx_ncm.tx_remainder >= sc->sc_rx_ncm.tx_modulus)) {
+		pr_debug("Using default transmit remainder: 0 bytes\n");
+		sc->sc_rx_ncm.tx_remainder = 0;
+	}
+
+	/* Additional configuration */
+
+	req.bmRequestType = USB_TYPE_CLASS |
+		USB_DIR_OUT | USB_RECIP_INTERFACE;
+	req.bRequest = CDC_NCM_SET_NTB_INPUT_SIZE;
+	req.wValue[0] = 0;
+	req.wValue[1] = 0;
+	req.wIndex[0] = iface_no;
+	req.wIndex[1] = 0;
+
+	if (flags & CDC_NCM_CAP_NTBINPUTSIZE) {
+		req.wLength[0] = 8;
+		req.wLength[1] = 0;
+		put_unaligned_le32(sc->sc_rx_ncm.rx_max, sc->sc_ncm_value);
+		put_unaligned_le16(CDC_NCM_DPT_DATAGRAMS_MAX,
+			sc->sc_ncm_value + 4);
+		put_unaligned_le16(0, sc->sc_ncm_value + 6);
+	} else {
+		req.wLength[0] = 4;
+		req.wLength[1] = 0;
+		put_unaligned_le32(sc->sc_rx_ncm.rx_max, sc->sc_ncm_value);
+	}
+
+	err = cdc_ncm_do_request(sc, &req,
+		&sc->sc_ncm_value, 0, NULL, 1000 /* ms */);
+	if (err)
+		pr_debug("Setting input size "
+		    "to %u failed.\n", sc->sc_rx_ncm.rx_max);
+
+	req.bmRequestType = USB_TYPE_CLASS |
+		USB_DIR_OUT | USB_RECIP_INTERFACE;
+	req.bRequest = CDC_NCM_SET_CRC_MODE;
+	req.wValue[0] = 0;	/* no CRC */
+	req.wValue[1] = 0;
+	req.wIndex[0] = iface_no;
+	req.wIndex[1] = 0;
+	req.wLength[0] = 0;
+	req.wLength[1] = 0;
+
+	err = cdc_ncm_do_request(sc, &req,
+		NULL, 0, NULL, 1000 /* ms */);
+	if (err)
+		pr_debug("Setting CRC mode to off failed.\n");
+
+	req.bmRequestType = USB_TYPE_CLASS |
+		USB_DIR_OUT | USB_RECIP_INTERFACE;
+	req.bRequest = CDC_NCM_SET_NTB_FORMAT;
+	req.wValue[0] = 0;	/* NTB-16 */
+	req.wValue[1] = 0;
+	req.wIndex[0] = iface_no;
+	req.wIndex[1] = 0;
+	req.wLength[0] = 0;
+	req.wLength[1] = 0;
+
+	err = cdc_ncm_do_request(sc, &req,
+		NULL, 0, NULL, 1000 /* ms */);
+	if (err)
+		pr_debug("Setting NTB format to 16-bit failed.\n");
+
+	sc->sc_tx_ncm = sc->sc_rx_ncm;
+
+	return 0;
+}
+
+static void
+cdc_ncm_find_endpoints(struct cdc_ncm_softc *sc, struct usb_interface *intf)
+{
+	struct usb_host_endpoint *e;
+	u8 ep;
+
+	for (ep = 0; ep < intf->cur_altsetting->desc.bNumEndpoints; ep++) {
+
+		e = intf->cur_altsetting->endpoint + ep;
+		switch (e->desc.bmAttributes & 0x03) {
+		case USB_ENDPOINT_XFER_INT:
+			if (usb_endpoint_dir_in(&e->desc)) {
+				if (sc->sc_status_ep == NULL)
+					sc->sc_status_ep = e;
+			}
+			break;
+
+		case USB_ENDPOINT_XFER_BULK:
+			if (usb_endpoint_dir_in(&e->desc)) {
+				if (sc->sc_in_ep == NULL)
+					sc->sc_in_ep = e;
+			} else {
+				if (sc->sc_out_ep == NULL)
+					sc->sc_out_ep = e;
+			}
+			break;
+
+		default:
+			break;
+		}
+	}
+}
+
+static void
+cdc_ncm_softc_free(struct cdc_ncm_softc *sc)
+{
+	if (sc == NULL)
+		return;
+
+	del_timer_sync(&sc->sc_tx_timer);
+
+	if (sc->sc_data_claimed) {
+		usb_set_intfdata(sc->sc_data, NULL);
+		usb_driver_release_interface(driver_of(sc->sc_intf),
+		    sc->sc_data);
+	}
+
+	if (sc->sc_control_claimed) {
+		usb_set_intfdata(sc->sc_control, NULL);
+		usb_driver_release_interface(driver_of(sc->sc_intf),
+		    sc->sc_control);
+	}
+
+	cdc_ncm_tx_set_rem_skb(sc, NULL);
+
+	if (sc->sc_tx_curr_skb != NULL) {
+		dev_kfree_skb_any(sc->sc_tx_curr_skb);
+		sc->sc_tx_curr_skb = NULL;
+	}
+
+	kfree(sc);
+}
+
+static int
+cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct cdc_ncm_softc *sc;
+	struct usb_driver *driver;
+	u8 *buf;
+	int len;
+	int temp;
+	u8 iface_no;
+
+	/* allocate our softc */
+
+	sc = kmalloc(sizeof(*sc), GFP_KERNEL);
+	if (sc == NULL)
+		goto error;
+
+	memset(sc, 0, sizeof(*sc));
+
+	init_timer(&sc->sc_tx_timer);
+	spin_lock_init(&sc->sc_mtx);
+	sc->sc_netdev = dev->net;
+
+	/* store softc pointer in dev's data field */
+	dev->data[0] = (unsigned long)sc;
+
+	/* get some pointers */
+	driver = driver_of(intf);
+	buf = intf->cur_altsetting->extra;
+	len = intf->cur_altsetting->extralen;
+
+	sc->sc_udev = dev->udev;
+	sc->sc_intf = intf;
+
+	/* parse through descriptors associated with control interface */
+
+	while ((len > 0) && (buf[0] > 2) && (buf[0] <= len)) {
+
+		if (buf[1] != USB_DT_CS_INTERFACE)
+			goto advance;
+
+		switch (buf[2]) {
+		case USB_CDC_UNION_TYPE:
+			if (buf[0] < sizeof(*(sc->sc_union)))
+				break;
+
+			sc->sc_union = (const struct usb_cdc_union_desc *)buf;
+
+			sc->sc_control = usb_ifnum_to_if(dev->udev,
+			    sc->sc_union->bMasterInterface0);
+			sc->sc_data = usb_ifnum_to_if(dev->udev,
+			    sc->sc_union->bSlaveInterface0);
+			break;
+
+		case USB_CDC_ETHERNET_TYPE:
+			if (buf[0] < sizeof(*(sc->sc_ether)))
+				break;
+
+			sc->sc_ether = (const struct usb_cdc_ether_desc *)buf;
+
+			dev->hard_mtu =
+			    le16_to_cpu(sc->sc_ether->wMaxSegmentSize);
+
+			if (dev->hard_mtu < 256)
+				dev->hard_mtu = 256;
+			else if (dev->hard_mtu > 2048)
+				dev->hard_mtu = 2048;
+			break;
+
+		case CDC_NCM_FUNC_DESC_CODE:
+			if (buf[0] < sizeof(*(sc->sc_func_desc)))
+				break;
+
+			sc->sc_func_desc =
+			    (const struct cdc_ncm_func_descriptor *)buf;
+			break;
+
+		default:
+			break;
+		}
+advance:
+		/* advance to next descriptor */
+		temp = buf[0];
+		buf += temp;
+		len -= temp;
+	}
+
+	/* check if we got everything */
+
+	if ((sc->sc_control == NULL) ||
+		(sc->sc_data == NULL) ||
+		(sc->sc_ether == NULL))
+		goto error;
+
+	/* claim interfaces, if any */
+
+	if (sc->sc_data != intf) {
+		temp = usb_driver_claim_interface(driver, sc->sc_data, dev);
+		if (temp)
+			goto error;
+		sc->sc_data_claimed = 1;
+	}
+
+	if (sc->sc_control != intf) {
+		temp = usb_driver_claim_interface(driver, sc->sc_control, dev);
+		if (temp)
+			goto error;
+		sc->sc_control_claimed = 1;
+	}
+
+	iface_no = sc->sc_data->cur_altsetting->desc.bInterfaceNumber;
+
+	/* reset data interface */
+
+	temp = usb_set_interface(dev->udev, iface_no, 0);
+	if (temp)
+		goto error;
+
+	/* initialize data interface */
+
+	if (cdc_ncm_setup(sc))
+		goto error;
+
+	/* configure data interface */
+
+	temp = usb_set_interface(dev->udev, iface_no, 1);
+	if (temp)
+		goto error;
+
+	cdc_ncm_find_endpoints(sc, sc->sc_data);
+	cdc_ncm_find_endpoints(sc, sc->sc_control);
+
+	if ((sc->sc_in_ep == NULL) || (sc->sc_out_ep == NULL) ||
+	    (sc->sc_status_ep == NULL))
+		goto error;
+
+	dev->net->ethtool_ops = &cdc_ncm_ethtool_ops;
+
+	usb_set_intfdata(sc->sc_data, dev);
+	usb_set_intfdata(sc->sc_control, dev);
+	usb_set_intfdata(sc->sc_intf, dev);
+
+	temp = cdc_ncm_get_ethernet_addr(dev, sc->sc_ether);
+	if (temp)
+		goto error;
+
+	dev_info(&dev->udev->dev, "MAC-Address: "
+	    "0x%02x:0x%02x:0x%02x:0x%02x:0x%02x:0x%02x\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]);
+
+	dev->in = usb_rcvbulkpipe(dev->udev,
+	    sc->sc_in_ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+	dev->out = usb_sndbulkpipe(dev->udev,
+	    sc->sc_out_ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+	dev->status = sc->sc_status_ep;
+	dev->rx_urb_size = sc->sc_rx_ncm.rx_max;
+
+	/*
+	 * We should get an event when the carrier is ON/OFF. Make
+	 * sure the carrier is OFF during attach, so that the
+	 * network stack does not start IPv6 negotiation and more.
+	 */
+	netif_carrier_off(dev->net);
+
+	return 0;
+
+error:
+	cdc_ncm_softc_free((struct cdc_ncm_softc *)dev->data[0]);
+	dev->data[0] = 0;
+	dev_info(&dev->udev->dev, "Descriptor failure\n");
+
+	return -ENODEV;
+}
+
+static void
+cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct cdc_ncm_softc *sc = (void *)dev->data[0];
+	struct usb_driver *driver;
+
+	if (sc == NULL)
+		return;		/* not setup */
+
+	driver = driver_of(intf);
+
+	usb_set_intfdata(sc->sc_data, NULL);
+	usb_set_intfdata(sc->sc_control, NULL);
+	usb_set_intfdata(sc->sc_intf, NULL);
+
+	/* claim interfaces, if any */
+
+	if (sc->sc_data_claimed) {
+		usb_driver_release_interface(driver, sc->sc_data);
+		sc->sc_data_claimed = 0;
+	}
+
+	if (sc->sc_control_claimed) {
+		usb_driver_release_interface(driver, sc->sc_control);
+		sc->sc_control_claimed = 0;
+	}
+}
+
+static void
+cdc_ncm_fill_tx_frame(struct cdc_ncm_softc *sc, struct sk_buff *skb,
+				struct sk_buff **ppskb_out)
+{
+	struct sk_buff *skb_out;
+
+	u32 rem;
+	u32 offset;
+	u32 last_offset;
+	u16 n;
+
+	u8 timeout = (skb == NULL);
+
+	/* if there is a remaining SKB, it gets priority */
+
+	if (skb != NULL)
+		swap(skb, sc->sc_tx_rem_skb);
+
+	/*
+	 * +----------------+
+	 * | skb_out        |
+	 * +----------------+
+	 *           ^ offset
+	 *        ^ last_offset
+	 */
+
+	/* check if we are resuming an OUT-SKB */
+
+	if (sc->sc_tx_curr_skb != NULL) {
+
+		/* pop variables */
+
+		skb_out = sc->sc_tx_curr_skb;
+		offset = sc->sc_tx_curr_offset;
+		last_offset = sc->sc_tx_curr_last_offset;
+		n = sc->sc_tx_curr_frame_num;
+	} else {
+
+		/* reset variables */
+
+		skb_out = alloc_skb(sc->sc_tx_ncm.tx_max, GFP_ATOMIC);
+		if (skb_out == NULL) {
+			if (skb != NULL)
+				dev_kfree_skb_any(skb);
+
+			goto error;
+		}
+
+		/* make room for headers */
+		offset = sizeof(sc->sc_tx_ncm.nth16) +
+		    sizeof(sc->sc_tx_ncm.ndp16) + sizeof(sc->sc_tx_ncm.dpe16);
+
+		/* store last valid offset before alignment */
+		last_offset = offset;
+
+		/* align offset correctly */
+		offset = sc->sc_tx_ncm.tx_remainder -
+		    ((0UL - offset) & (0UL - sc->sc_tx_ncm.tx_modulus));
+
+		n = 0;
+	}
+
+	for (; n != sc->sc_tx_ncm.tx_max_datagrams; n++) {
+
+		/* check if end of transmit buffer is reached */
+
+		if (offset >= sc->sc_tx_ncm.tx_max)
+			break;
+
+		/* compute maximum buffer size */
+
+		rem = sc->sc_tx_ncm.tx_max - offset;
+
+		if (skb == NULL) {
+			skb = sc->sc_tx_rem_skb;
+			sc->sc_tx_rem_skb = NULL;
+
+			/* check for end of SKB's */
+			if (skb == NULL)
+				break;
+		}
+
+		if (skb->len > rem) {
+
+			if (n == 0) {
+				/* won't fit */
+				dev_kfree_skb_any(skb);
+				skb = NULL;
+			} else {
+				/* no room for SKB - store for later */
+				cdc_ncm_tx_set_rem_skb(sc, skb);
+				skb = NULL;
+
+				/* loop one more time */
+				timeout = 1;
+			}
+			break;
+		}
+
+		memcpy(((u8 *)skb_out->data) + offset, skb->data, skb->len);
+
+		put_unaligned_le16(skb->len,
+			sc->sc_tx_ncm.dpe16[n].wDatagramLength);
+		put_unaligned_le16(offset,
+			sc->sc_tx_ncm.dpe16[n].wDatagramIndex);
+
+		/* Update offset */
+		offset += skb->len;
+
+		/* Store last valid offset before alignment */
+		last_offset = offset;
+
+		/* Align offset correctly */
+		offset = sc->sc_tx_ncm.tx_remainder -
+		    ((0UL - offset) & (0UL - sc->sc_tx_ncm.tx_modulus));
+
+		dev_kfree_skb_any(skb);
+		skb = NULL;
+	}
+
+	/* free up any dangling skb */
+	if (skb != NULL) {
+		dev_kfree_skb_any(skb);
+		skb = NULL;
+	}
+
+	if (n == 0) {
+		/* wait for more frames */
+		/* push variables */
+		sc->sc_tx_curr_skb = skb_out;
+		sc->sc_tx_curr_offset = offset;
+		sc->sc_tx_curr_last_offset = last_offset;
+		sc->sc_tx_curr_frame_num = n;
+
+		goto error;
+
+	} else if ((n != sc->sc_tx_ncm.tx_max_datagrams) && (timeout == 0)) {
+		/* wait for more frames */
+		/* push variables */
+		sc->sc_tx_curr_skb = skb_out;
+		sc->sc_tx_curr_offset = offset;
+		sc->sc_tx_curr_last_offset = last_offset;
+		sc->sc_tx_curr_frame_num = n;
+
+		/* set the pending count */
+		if (n < 8)
+			sc->sc_tx_timer_pending = 2;
+
+		goto error;
+	} else {
+		/* frame goes out */
+		/* variables will be reset at next call */
+	}
+
+	rem = sc->sc_tx_ncm.tx_max - last_offset;
+
+	/* XXX if there is a FORCE SHORT packet flag, use that instead! */
+	if (((rem & 63) == 0) && (rem != 0)) {
+		/* force short packet */
+		*(((u8 *)skb_out->data) + last_offset) = 0;
+		last_offset++;
+	}
+
+	rem = (sizeof(sc->sc_tx_ncm.ndp16) + (4 * n) + 4);
+
+	put_unaligned_le16(rem, sc->sc_tx_ncm.ndp16.wLength);
+
+	/* zero the rest of the data pointer entries */
+	for (; n != CDC_NCM_DPT_DATAGRAMS_MAX; n++) {
+		put_unaligned_le16(0, sc->sc_tx_ncm.dpe16[n].wDatagramLength);
+		put_unaligned_le16(0, sc->sc_tx_ncm.dpe16[n].wDatagramIndex);
+	}
+
+	/* Fill out 16-bit header */
+	sc->sc_tx_ncm.nth16.dwSignature[0] = 'N';
+	sc->sc_tx_ncm.nth16.dwSignature[1] = 'C';
+	sc->sc_tx_ncm.nth16.dwSignature[2] = 'M';
+	sc->sc_tx_ncm.nth16.dwSignature[3] = 'H';
+	put_unaligned_le16(sizeof(sc->sc_tx_ncm.nth16),
+		sc->sc_tx_ncm.nth16.wHeaderLength);
+	put_unaligned_le16(last_offset, sc->sc_tx_ncm.nth16.wBlockLength);
+	put_unaligned_le16(sc->sc_tx_ncm.tx_seq,
+		sc->sc_tx_ncm.nth16.wSequence);
+	put_unaligned_le16(sizeof(sc->sc_tx_ncm.nth16),
+		sc->sc_tx_ncm.nth16.wNdpIndex);
+
+	sc->sc_tx_ncm.tx_seq++;
+
+	/* Fill out 16-bit frame table header */
+	sc->sc_tx_ncm.ndp16.dwSignature[0] = 'N';
+	sc->sc_tx_ncm.ndp16.dwSignature[1] = 'C';
+	sc->sc_tx_ncm.ndp16.dwSignature[2] = 'M';
+	sc->sc_tx_ncm.ndp16.dwSignature[3] = '0';
+	/* Reserved: */
+	sc->sc_tx_ncm.ndp16.wNextNdpIndex[0] = 0;
+	sc->sc_tx_ncm.ndp16.wNextNdpIndex[1] = 0;
+
+	memcpy(skb_out->data, &(sc->sc_tx_ncm.nth16),
+	    sizeof(sc->sc_tx_ncm.nth16));
+	memcpy(((u8 *)skb_out->data) + sizeof(sc->sc_tx_ncm.nth16),
+	    &(sc->sc_tx_ncm.ndp16), sizeof(sc->sc_tx_ncm.ndp16));
+	memcpy(((u8 *)skb_out->data) + sizeof(sc->sc_tx_ncm.nth16) +
+	    sizeof(sc->sc_tx_ncm.ndp16), &(sc->sc_tx_ncm.dpe16),
+	    sizeof(sc->sc_tx_ncm.dpe16));
+
+	/* set frame length */
+	skb_put(skb_out, last_offset);
+
+	/* return SKB */
+	sc->sc_tx_curr_skb = NULL;
+
+	*ppskb_out = skb_out;
+
+	return;
+
+error:
+	*ppskb_out = NULL;
+}
+
+static void
+cdc_ncm_tx_timeout_start(struct cdc_ncm_softc *sc)
+{
+	/* start timer, if not already started */
+	if (timer_pending(&sc->sc_tx_timer) == 0) {
+		sc->sc_tx_timer.function = &cdc_ncm_tx_timeout;
+		sc->sc_tx_timer.data = (unsigned long)sc;
+		sc->sc_tx_timer.expires = jiffies + ((HZ + 999) / 1000);
+		add_timer(&sc->sc_tx_timer);
+	}
+}
+
+static void
+cdc_ncm_tx_timeout(unsigned long arg)
+{
+	struct cdc_ncm_softc *sc = (void *)arg;
+	u8 restart;
+
+	spin_lock(&sc->sc_mtx);
+	if (sc->sc_tx_timer_pending != 0) {
+		sc->sc_tx_timer_pending--;
+		restart = 1;
+	} else {
+		restart = 0;
+	}
+	spin_unlock(&sc->sc_mtx);
+
+	if (restart)
+		cdc_ncm_tx_timeout_start(sc);
+	else if (sc->sc_netdev != NULL)
+		usbnet_start_xmit(NULL, sc->sc_netdev);
+}
+
+static void
+cdc_ncm_tx_set_rem_skb(struct cdc_ncm_softc *sc, struct sk_buff *skb)
+{
+	if (sc->sc_tx_rem_skb != NULL)
+		dev_kfree_skb_any(sc->sc_tx_rem_skb);
+
+	sc->sc_tx_rem_skb = skb;
+}
+
+static struct sk_buff *
+cdc_ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
+{
+	struct sk_buff *skb_out;
+	struct cdc_ncm_softc *sc;
+
+	u8 need_timer;
+
+	/*
+	 * The ethernet API we are using does not support transmitting
+	 * multiple ethernet frames in a single call. This driver will
+	 * accumulate multiple ethernet frames and send out a larger
+	 * USB frame when the USB buffer is full, or a single jiffies
+	 * timeout happens.
+	 */
+	sc = (struct cdc_ncm_softc *)dev->data[0];
+	if (sc == NULL)
+		goto error;
+
+	/* The following function will setup the skb */
+	spin_lock(&sc->sc_mtx);
+	cdc_ncm_fill_tx_frame(sc, skb, &skb_out);
+	need_timer = (sc->sc_tx_curr_skb != NULL);
+	spin_unlock(&sc->sc_mtx);
+
+	/* Start timer, if there is a remaining skb */
+	if (need_timer)
+		cdc_ncm_tx_timeout_start(sc);
+
+	if (skb_out == NULL) {
+		/* compensate the usbnet drop count increment */
+		dev->net->stats.tx_dropped--;
+	}
+
+	return skb_out;
+
+error:
+	if (skb != NULL)
+		dev_kfree_skb_any(skb);
+
+	return NULL;
+}
+
+static int
+cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
+{
+	struct sk_buff *skb;
+	struct cdc_ncm_softc *sc;
+	int sumdata;
+	int sumlen;
+	int actlen;
+	int temp;
+	int nframes;
+	int x;
+	int offset;
+
+	sc = (struct cdc_ncm_softc *)dev->data[0];
+	if (sc == NULL)
+		goto done;
+
+	actlen = skb_in->len;
+	sumlen = CDC_NCM_RX_MAXLEN;
+
+	pr_debug("received %u bytes in 1 frame\n", actlen);
+
+	if (actlen < (sizeof(sc->sc_rx_ncm.nth16) +
+	    sizeof(sc->sc_rx_ncm.ndp16))) {
+		pr_debug("frame too short\n");
+		goto done;
+	}
+
+	memcpy(&(sc->sc_rx_ncm.nth16), ((u8 *)skb_in->data),
+	    sizeof(sc->sc_rx_ncm.nth16));
+
+	if ((sc->sc_rx_ncm.nth16.dwSignature[0] != 'N') ||
+		(sc->sc_rx_ncm.nth16.dwSignature[1] != 'C') ||
+		(sc->sc_rx_ncm.nth16.dwSignature[2] != 'M') ||
+		(sc->sc_rx_ncm.nth16.dwSignature[3] != 'H')) {
+		pr_debug("invalid HDR signature\n");
+		goto done;
+	}
+	temp = get_unaligned_le16(sc->sc_rx_ncm.nth16.wBlockLength);
+	if (temp > sumlen) {
+		pr_debug("unsupported block length %u/%u\n",
+		    temp, sumlen);
+		goto done;
+	}
+	temp = get_unaligned_le16(sc->sc_rx_ncm.nth16.wNdpIndex);
+	if ((temp + sizeof(sc->sc_rx_ncm.ndp16)) > actlen) {
+		pr_debug("invalid DPT index\n");
+		goto done;
+	}
+
+	memcpy(&(sc->sc_rx_ncm.ndp16), ((u8 *)skb_in->data) + temp,
+	    sizeof(sc->sc_rx_ncm.ndp16));
+
+	if ((sc->sc_rx_ncm.ndp16.dwSignature[0] != 'N') ||
+		(sc->sc_rx_ncm.ndp16.dwSignature[1] != 'C') ||
+		(sc->sc_rx_ncm.ndp16.dwSignature[2] != 'M') ||
+		(sc->sc_rx_ncm.ndp16.dwSignature[3] != '0')) {
+
+		pr_debug("invalid DPT signature\n");
+		goto done;
+	}
+	nframes = get_unaligned_le16(sc->sc_rx_ncm.ndp16.wLength) / 4;
+
+	/* Subtract size of header and last zero padded entry */
+	if (nframes >= (2 + 1))
+		nframes -= (2 + 1);
+	else
+		nframes = 0;
+
+	pr_debug("nframes = %u\n", nframes);
+
+	temp += sizeof(sc->sc_rx_ncm.ndp16);
+
+	if ((temp + (4 * nframes)) > actlen) {
+		pr_debug("Invalid nframes = %d\n", nframes);
+		goto done;
+	}
+
+	if (nframes > CDC_NCM_DPT_DATAGRAMS_MAX) {
+		pr_debug("Truncating number of frames from %u to %u\n",
+		    nframes, CDC_NCM_DPT_DATAGRAMS_MAX);
+		nframes = CDC_NCM_DPT_DATAGRAMS_MAX;
+	}
+
+	memcpy(&(sc->sc_rx_ncm.dpe16), ((u8 *)skb_in->data) + temp,
+	    (4 * nframes));
+
+	sumdata = 0;
+
+	for (x = 0; x != nframes; x++) {
+
+		offset = get_unaligned_le16(
+			sc->sc_rx_ncm.dpe16[x].wDatagramIndex);
+		temp = get_unaligned_le16(
+			sc->sc_rx_ncm.dpe16[x].wDatagramLength);
+
+		if ((offset == 0) || (temp == 0))
+			continue;
+
+		/* sanity checking */
+
+		if ((offset + temp) > actlen)
+			skb = NULL;
+		else if (temp > 2048)
+			skb = NULL;
+		else if (temp > 14)
+			skb = skb_clone(skb_in, GFP_ATOMIC);
+		else
+			skb = NULL;
+
+		pr_debug("frame %u, offset = %u, length = %u, skb = %p\n",
+		    x, offset, temp, skb);
+
+		sumdata += temp;
+
+		/* check if we have a buffer */
+		if (skb != NULL) {
+
+			skb->len = temp;
+			skb->data = ((u8 *)skb_in->data) + offset;
+			skb_set_tail_pointer(skb, temp);
+			usbnet_skb_return(dev, skb);
+		} else {
+			pr_debug("invalid frame detected (ignored)\n");
+		}
+	}
+
+	pr_debug("Efficiency: %u/%u bytes\n", sumdata, actlen);
+done:
+	/* compensate for increment of RX error counter in usbnet.c */
+	dev->net->stats.rx_errors--;
+	return 0;
+}
+
+static void
+cdc_ncm_dumpspeed(struct cdc_ncm_softc *sc, u32 *ptr)
+{
+	/* TODO */
+}
+
+static void
+cdc_ncm_status(struct usbnet *dev, struct urb *urb)
+{
+	struct cdc_ncm_softc *sc;
+	struct usb_cdc_notification *event;
+
+	sc = (struct cdc_ncm_softc *)dev->data[0];
+
+	if (urb->actual_length < sizeof(*event))
+		return;
+
+	/* test for split data in 8-byte chunks */
+
+	if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) {
+		cdc_ncm_dumpspeed(sc, (u32 *) urb->transfer_buffer);
+		return;
+	}
+
+	event = urb->transfer_buffer;
+	switch (event->bNotificationType) {
+	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+		sc->sc_connected = event->wValue;
+		if (netif_msg_timer(dev)) {
+			dev_dbg(&dev->udev->dev, "CDC: carrier %s\n",
+				sc->sc_connected ? "on" : "off");
+		}
+
+		if (sc->sc_connected)
+			netif_carrier_on(dev->net);
+		else
+			netif_carrier_off(dev->net);
+		break;
+
+	case USB_CDC_NOTIFY_SPEED_CHANGE:	/* tx/rx rates */
+		if (netif_msg_timer(dev)) {
+			dev_dbg(&dev->udev->dev, "CDC: speed "
+				"change (len %d)\n",
+				urb->actual_length);
+		}
+
+		if (urb->actual_length < (sizeof(*event) + 8))
+			set_bit(EVENT_STS_SPLIT, &dev->flags);
+		else
+			cdc_ncm_dumpspeed(sc, (u32 *) &event[1]);
+		break;
+
+	default:
+		dev_err(&dev->udev->dev, "CDC: unexpected "
+			"notification 0x%02x!\n",
+			event->bNotificationType);
+		break;
+	}
+}
+
+static int
+cdc_ncm_check_connect(struct usbnet *dev)
+{
+	struct cdc_ncm_softc *sc;
+	sc = (struct cdc_ncm_softc *)dev->data[0];
+	if (sc == NULL)
+		return 1;	/* disconnected */
+
+	return !sc->sc_connected;
+}
+
+static int
+cdc_ncm_probe(struct usb_interface *udev, const struct usb_device_id *prod)
+{
+	int err;
+
+	err = usbnet_probe(udev, prod);
+
+	return err;
+}
+
+static int
+cdc_ncm_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	return usbnet_suspend(intf, message);
+}
+
+static int
+cdc_ncm_resume(struct usb_interface *intf)
+{
+	return usbnet_resume(intf);
+}
+
+static void
+cdc_ncm_disconnect(struct usb_interface *intf)
+{
+	struct usbnet *dev;
+	struct cdc_ncm_softc *sc;
+
+	dev = usb_get_intfdata(intf);
+	if (dev == NULL)
+		return;		/* already disconnected */
+
+	sc = (struct cdc_ncm_softc *)dev->data[0];
+	if (sc == NULL)
+		return;		/* should not happen */
+
+	usbnet_disconnect(intf);
+
+	cdc_ncm_softc_free(sc);
+}
+
+static const struct driver_info cdc_ncm_info = {
+	.description = "CDC NCM",
+	.flags = FLAG_NO_SETINT | FLAG_ETHER,
+	.check_connect = cdc_ncm_check_connect,
+	.bind = cdc_ncm_bind,
+	.unbind = cdc_ncm_unbind,
+	.status = cdc_ncm_status,
+	.rx_fixup = cdc_ncm_rx_fixup,
+	.tx_fixup = cdc_ncm_tx_fixup,
+};
+
+static struct usb_driver cdc_ncm_driver = {
+	.name = "cdc_ncm",
+	.id_table = cdc_devs,
+	.probe = cdc_ncm_probe,
+	.disconnect = cdc_ncm_disconnect,
+	.suspend = cdc_ncm_suspend,
+	.resume = cdc_ncm_resume,
+	.supports_autosuspend = 1,
+};
+
+static struct ethtool_ops cdc_ncm_ethtool_ops = {
+	.get_drvinfo = cdc_ncm_get_drvinfo,
+	.get_link = usbnet_get_link,
+	.get_msglevel = usbnet_get_msglevel,
+	.set_msglevel = usbnet_set_msglevel,
+	.get_settings = usbnet_get_settings,
+	.set_settings = usbnet_set_settings,
+	.nway_reset = usbnet_nway_reset,
+};
+
+static int __init
+cdc_ncm_init(void)
+{
+	return usb_register(&cdc_ncm_driver);
+}
+
+module_init(cdc_ncm_init);
+
+static void __exit
+cdc_ncm_exit(void)
+{
+	usb_deregister(&cdc_ncm_driver);
+}
+
+module_exit(cdc_ncm_exit);
+
+MODULE_AUTHOR("Hans Petter Selasky");
+MODULE_DESCRIPTION("CDC NCM");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/usb/cdc_ncm.h b/drivers/net/usb/cdc_ncm.h
new file mode 100644
index 0000000..b858672
--- /dev/null
+++ b/drivers/net/usb/cdc_ncm.h
@@ -0,0 +1,222 @@
+/*
+ * cdc_ncm.h
+ *
+ * Copyright (C) ST-Ericsson AB 2010
+ * Contact: Sjur Braendeland <sjur.brandeland@xxxxxxxxxxxxxx>
+ * Original author:Hans Petter Selasky <hans.petter.selasky@xxxxxxxxxxxxxx>
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose this file to be licensed under the terms
+ * of the GNU General Public License (GPL) Version 2 or the 2-clause
+ * BSD license listed below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _CDC_NCM_H_
+#define	_CDC_NCM_H_
+
+#include <linux/unaligned/le_byteshift.h>
+
+#define	CDC_IND_SIZE_MAX			32      /* bytes */
+#define	CDC_NCM_TX_MAXLEN			0x4000	/* bytes */
+#define	CDC_NCM_RX_MAXLEN			0x4000	/* bytes */
+/* maximum amount of datagrams in NCM Datagram Pointer Table */
+#define	CDC_NCM_DPT_DATAGRAMS_MAX		32
+
+#define	USB_SUBCLASS_CODE_NETWORK_CONTROL_MODEL	0x0D
+
+/*
+ * USB device request structure.
+ *
+ * The protocol structure definitions use Hungarian style
+ * like in the NCM specification.
+ */
+struct cdc_ncm_device_request {
+	u8	bmRequestType;
+	u8	bRequest;
+	u8	wValue[2];
+	u8	wIndex[2];
+	u8	wLength[2];
+} __attribute__((__packed__));
+
+/* 16-bit NCM Transfer Header */
+struct cdc_ncm_nth16 {
+	u8	dwSignature[4];
+	u8	wHeaderLength[2];
+	u8	wSequence[2];
+	u8	wBlockLength[2];
+	u8	wNdpIndex[2];
+} __attribute__((__packed__));
+
+/* 16-bit NCM Datagram Pointer Entry */
+struct cdc_ncm_dpe16 {
+	u8	wDatagramIndex[2];
+	u8	wDatagramLength[2];
+} __attribute__((__packed__));
+
+/* 16-bit NCM Datagram Pointer Table */
+struct cdc_ncm_ndp16 {
+	u8	dwSignature[4];
+	u8	wLength[2];
+	u8	wNextNdpIndex[2];
+	struct cdc_ncm_dpe16 dpe16[0];
+} __attribute__((__packed__));
+
+/* 32-bit NCM Transfer Header */
+struct cdc_ncm_nth32 {
+	u8	dwSignature[4];
+	u8	wHeaderLength[2];
+	u8	wSequence[2];
+	u8	dwBlockLength[4];
+	u8	dwNdpIndex[4];
+} __attribute__((__packed__));
+
+/* 32-bit NCM Datagram Pointer Entry */
+struct cdc_ncm_dpe32 {
+	u8	dwDatagramIndex[4];
+	u8	dwDatagramLength[4];
+} __attribute__((__packed__));
+
+/* 32-bit NCM Datagram Pointer Table */
+struct cdc_ncm_ndp32 {
+	u8	dwSignature[4];
+	u8	wLength[2];
+	u8	wReserved6[2];
+	u8	dwNextNdpIndex[4];
+	u8	dwReserved12[4];
+	struct cdc_ncm_dpe32 dpe32[0];
+} __attribute__((__packed__));
+
+/* Communications interface class specific descriptors */
+#define	CDC_NCM_FUNC_DESC_CODE		0x1A
+
+/* Network Capabilities bit fields */
+#define	CDC_NCM_CAP_FILTER		0x01
+#define	CDC_NCM_CAP_NETADDRESS		0x02
+#define	CDC_NCM_CAP_ENCAP		0x04
+#define	CDC_NCM_CAP_MAXDATAGRAMSIZE	0x08
+#define	CDC_NCM_CAP_CRCMODE		0x10
+#define	CDC_NCM_CAP_NTBINPUTSIZE	0x20
+
+struct cdc_ncm_func_descriptor {
+	u8	bLength;
+	u8	bDescriptorType;
+	u8	bDescriptorSubtype;
+	u8	bcdNcmVersion[2];
+	u8	bmNetworkCapabilities;
+} __attribute__((__packed__));
+
+/* Class-Specific Request Codes for NCM subclass */
+#define	CDC_NCM_SET_ETHERNET_MULTICAST_FILTERS			0x40
+#define	CDC_NCM_SET_ETHERNET_POWER_MGMT_PATTERN_FILTER		0x41
+#define	CDC_NCM_GET_ETHERNET_POWER_MGMT_PATTERN_FILTER		0x42
+#define	CDC_NCM_SET_ETHERNET_PACKET_FILTER			0x43
+#define	CDC_NCM_GET_ETHERNET_STATISTIC				0x44
+#define	CDC_NCM_GET_NTB_PARAMETERS				0x80
+#define	CDC_NCM_GET_NET_ADDRESS					0x81
+#define	CDC_NCM_SET_NET_ADDRESS					0x82
+#define	CDC_NCM_GET_NTB_FORMAT					0x83
+#define	CDC_NCM_SET_NTB_FORMAT					0x84
+#define	CDC_NCM_GET_NTB_INPUT_SIZE				0x85
+#define	CDC_NCM_SET_NTB_INPUT_SIZE				0x86
+#define	CDC_NCM_GET_MAX_DATAGRAM_SIZE				0x87
+#define	CDC_NCM_SET_MAX_DATAGRAM_SIZE				0x88
+#define	CDC_NCM_GET_CRC_MODE					0x89
+#define	CDC_NCM_SET_CRC_MODE					0x8A
+
+struct cdc_ncm_parameters {
+	u8	wLength[2];
+	u8	bmNtbFormatsSupported[2];
+#define	CDC_NCM_FORMAT_NTB16	0x0001
+#define	CDC_NCM_FORMAT_NTB32	0x0002
+	u8	dwNtbInMaxSize[4];
+	u8	wNdpInDivisor[2];
+	u8	wNdpInPayloadRemainder[2];
+	u8	wNdpInAlignment[2];
+	u8	wReserved14[2];
+	u8	dwNtbOutMaxSize[4];
+	u8	wNdpOutDivisor[2];
+	u8	wNdpOutPayloadRemainder[2];
+	u8	wNdpOutAlignment[2];
+	u8	wNtbOutMaxDatagrams[2];
+} __attribute__((__packed__));
+
+/* Class-Specific Notification Codes for NCM subclass */
+#define	CDC_NCM_NOTIF_NETWORK_CONNECTION	0x00
+#define	CDC_NCM_NOTIF_RESPONSE_AVAILABLE	0x01
+#define	CDC_NCM_NOTIF_CONNECTION_SPEED_CHANGE	0x2A
+
+struct cdc_ncm {
+	struct cdc_ncm_nth16 nth16;
+	struct cdc_ncm_ndp16 ndp16;
+	struct cdc_ncm_dpe16 dpe16[CDC_NCM_DPT_DATAGRAMS_MAX];
+	u32 rx_max;
+	u32 tx_max;
+	u16 tx_max_datagrams;
+	u16 tx_remainder;
+	u16 tx_modulus;
+	u16 tx_struct_align;
+	u16 tx_seq;
+};
+
+struct cdc_ncm_softc {
+	struct cdc_ncm sc_rx_ncm;
+	struct cdc_ncm sc_tx_ncm;
+	struct cdc_ncm_parameters sc_ncm_parm;
+
+	struct timer_list sc_tx_timer;
+
+	const struct cdc_ncm_func_descriptor *sc_func_desc;
+	const struct usb_cdc_header_desc *sc_header;
+	const struct usb_cdc_union_desc *sc_union;
+	const struct usb_cdc_ether_desc *sc_ether;
+
+	struct usb_device *sc_udev;
+
+	struct usb_host_endpoint *sc_in_ep;
+	struct usb_host_endpoint *sc_out_ep;
+	struct usb_host_endpoint *sc_status_ep;
+
+	struct usb_interface *sc_intf;
+	struct usb_interface *sc_control;
+	struct usb_interface *sc_data;
+	struct sk_buff *sc_tx_curr_skb;
+	struct sk_buff *sc_tx_rem_skb;
+	struct net_device *sc_netdev;
+
+	spinlock_t sc_mtx;
+
+	u32 sc_tx_timer_pending;
+	u32 sc_tx_curr_offset;
+	u32 sc_tx_curr_last_offset;
+	u32 sc_tx_curr_frame_num;
+
+	u32 sc_rx_speed;
+	u32 sc_tx_sped;
+	u16 sc_connected;
+	u8 sc_data_claimed;
+	u8 sc_control_claimed;
+	u8 sc_ncm_value[16];	/* temporary data */
+};
+
+#endif /* _CDC_NCM_H_ */
-- 
1.6.3.3

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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux