[RFC/PATCH 5/8] usb: cdc_ncm: initial version

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

 



From: Yauheni Kaliuta <yauheni.kaliuta@xxxxxxxxx>

Signed-off-by: Yauheni Kaliuta <yauheni.kaliuta@xxxxxxxxx>
---
 drivers/net/usb/Kconfig   |    7 +
 drivers/net/usb/Makefile  |    1 +
 drivers/net/usb/cdc_ncm.c |  459 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 467 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/usb/cdc_ncm.c

diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index ba56ce4..ec0493d 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -196,6 +196,13 @@ 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 && USB_NET_CDCETHER && EXPERIMENTAL
+	help
+	  This option supports devices conforming to the Communication Device
+	  Class (CDC) Network Communication Model
+
 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 82ea629..6c6ac25 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -23,4 +23,5 @@ obj-$(CONFIG_USB_NET_MCS7830)	+= mcs7830.o
 obj-$(CONFIG_USB_USBNET)	+= usbnet.o
 obj-$(CONFIG_USB_NET_INT51X1)	+= int51x1.o
 obj-$(CONFIG_USB_CDC_PHONET)	+= cdc-phonet.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..0ddb7c5
--- /dev/null
+++ b/drivers/net/usb/cdc_ncm.c
@@ -0,0 +1,459 @@
+/*
+ * USB CDC NCM network interface driver
+ * Copyright (C) 2010 Nokia Corporation
+ * Contact: Yauheni Kaliuta <yauheni.kaliuta@xxxxxxxxx>
+ *
+ * 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/ctype.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/crc32.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+#include <linux/usb/ncm.h>
+
+struct ncmnet {
+	struct usbnet		usbnet; /* Should be first */
+
+	/* zeroed by netdev allocator */
+	size_t			out_max_size;
+	unsigned		out_align;
+	unsigned		out_div;
+	unsigned		out_rem;
+	bool			is_ncm32;
+	bool			is_crc;
+	struct ndp_parser_opts	*parser_opts;
+};
+
+static inline struct ncmnet *usb_to_ncm(struct usbnet *usb)
+{
+	return container_of(usb, struct ncmnet, usbnet);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS;
+static struct ndp_parser_opts ndp32_opts = INIT_NDP32_OPTS;
+
+/*-------------------------------------------------------------------------*/
+
+static void dumpspeed(struct usbnet *dev, __le32 *speeds)
+{
+	if (netif_msg_timer(dev))
+		netdev_info(dev->net, "link speeds: %u kbps up, %u kbps down\n",
+			    __le32_to_cpu(speeds[0]) / 1000,
+			    __le32_to_cpu(speeds[1]) / 1000);
+}
+
+static void cdc_status(struct usbnet *dev, struct urb *urb)
+{
+	struct usb_cdc_notification	*event;
+
+	if (urb->actual_length < sizeof *event)
+		return;
+
+	/* SPEED_CHANGE can get split into two 8-byte packets */
+	if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) {
+		dumpspeed(dev, (__le32 *) urb->transfer_buffer);
+		return;
+	}
+
+	event = urb->transfer_buffer;
+	switch (event->bNotificationType) {
+	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+		if (netif_msg_timer(dev))
+			netdev_dbg(dev->net, "CDC: carrier %s\n",
+				   event->wValue ? "on" : "off");
+		if (event->wValue)
+			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))
+			netdev_dbg(dev->net, "CDC: speed change (len %d)\n",
+				   urb->actual_length);
+		if (urb->actual_length != (sizeof *event + 8))
+			set_bit(EVENT_STS_SPLIT, &dev->flags);
+		else
+			dumpspeed(dev, (__le32 *) &event[1]);
+		break;
+	/* USB_CDC_NOTIFY_RESPONSE_AVAILABLE can happen too (e.g. RNDIS),
+	 * but there are no standard formats for the response data.
+	 */
+	default:
+		netdev_err(dev->net, "CDC: unexpected notification %02x!\n",
+			   event->bNotificationType);
+		break;
+	}
+}
+
+static int ncm_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct ncmnet			*ncm = usb_to_ncm(dev);
+	int				status;
+	struct cdc_state		*info = (void *) &dev->data;
+	int				ctrl_num;
+	struct usb_cdc_ncm_ntb_parameter param;
+	bool 				need32 = false;
+	bool				need_crc = false;
+
+	status = usbnet_generic_cdc_bind(dev, intf);
+	if (status < 0)
+		return status;
+
+	status = usbnet_get_ethernet_addr(dev, info->ether->iMACAddress);
+	if (status < 0)
+		goto err;
+
+	if (need_crc)
+		need_crc = (info->ncm->bmNetworkCapabilities &
+			    NCM_NCAP_CRC_MODE) == NCM_NCAP_CRC_MODE;
+
+	ctrl_num = info->control->cur_altsetting->desc.bInterfaceNumber;
+	status = usb_control_msg(dev->udev,
+				 usb_rcvctrlpipe(dev->udev, 0),
+				 USB_CDC_GET_NTB_PARAMETERS,
+				 USB_DIR_IN |
+				 USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+				 0, ctrl_num,
+				 &param, sizeof(param),
+				 (5 * 1000));
+	if (status < 0)
+		goto err;
+
+	dev->rx_urb_size = le32_to_cpu(param.dwNtbInMaxSize);
+	if (dev->rx_urb_size < NCM_NTB_MIN_IN_SIZE) {
+		netdev_warn(dev->net, "dwNtbInMaxSize is too small: %d\n",
+			    dev->rx_urb_size);
+		status = -ETOOSMALL;
+		goto err;
+	}
+	ncm->out_max_size = le32_to_cpu(param.dwNtbOutMaxSize);
+	ncm->out_align = le16_to_cpu(param.wNdpOutAlignment);
+	ncm->out_div = le16_to_cpu(param.wNdpOutDivisor);
+	ncm->out_rem = le16_to_cpu(param.wNdpOutPayloadRemainder);
+	ncm->is_ncm32 = ((le16_to_cpu(param.bmNtbFormatSupported) & 0x0002)
+			 == 0x0002);
+
+	ncm->parser_opts = &ndp16_opts;
+	if (need32) {
+		status = usb_control_msg(dev->udev,
+					 usb_sndctrlpipe(dev->udev, 0),
+					 USB_CDC_SET_NTB_FORMAT,
+					 USB_DIR_OUT |
+					 USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+					 0x0001, ctrl_num,
+					 NULL, 0,
+					 (5 * 1000));
+		if (status < 0)
+			netdev_warn(dev->net, "Could not enable NCM32\n");
+		else
+			ncm->parser_opts = &ndp32_opts;
+	}
+
+	if (need_crc) {
+		status = usb_control_msg(dev->udev,
+					 usb_sndctrlpipe(dev->udev, 0),
+					 USB_CDC_SET_CRC_MODE,
+					 USB_DIR_OUT |
+					 USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+					 0x0001, ctrl_num,
+					 NULL, 0,
+					 (5 * 1000));
+		if (status < 0)
+			netdev_warn(dev->net, "Could not enable CRC\n");
+		else
+			ncm->is_crc = true;
+	}
+	return 0;
+err:
+	usb_set_intfdata(info->data, NULL);
+	usb_driver_release_interface(driver_of(intf), info->data);
+	return status;
+}
+
+static struct sk_buff *ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
+				    gfp_t flags)
+{
+	struct ncmnet *ncm = usb_to_ncm(dev);
+	struct sk_buff *skb2;
+	int ncb_len = 0;
+	__le16 *tmp;
+	int div = ncm->out_div;
+	int rem = ncm->out_rem;
+	int pad;
+	int ndp_align = ncm->out_align;
+	int ndp_pad;
+	unsigned max_size = ncm->out_max_size;
+	struct ndp_parser_opts *opts = ncm->parser_opts;
+	unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0;
+
+	ncb_len += opts->nth_size;
+	ncb_len += opts->ndp_size;
+	ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len;
+	ncb_len += ndp_pad;
+	ncb_len += 2 * 2 * opts->dgram_item_len; /* Datagram entry */
+	ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
+	/* padding, could not find one-division solution :( */
+	pad = rem + ((ncb_len + rem + div - 1) / div) * div
+		- (ncb_len % div <= rem) ? div : 0;
+	ncb_len += pad;
+
+	if (ncb_len + skb->len + crc_len > max_size) {
+		dev_kfree_skb_any(skb);
+		return NULL;
+	}
+
+	/* skb2 = skb_realloc_headroom(skb, ncb_len); */
+	skb2 = skb_copy_expand(skb, ncb_len,
+			       max_size - skb->len - ncb_len - crc_len,
+			       GFP_ATOMIC);
+	dev_kfree_skb_any(skb);
+	if (!skb2)
+		return NULL;
+
+	skb = skb2;
+
+	tmp = (void *) skb_push(skb, ncb_len);
+	memset(tmp, 0, ncb_len);
+
+	put_unaligned_le32(opts->nth_sign, tmp); /* dwSignature */
+	tmp += 2;
+	/* wHeaderLength */
+	put_unaligned_le16(opts->nth_size, tmp++);
+	tmp++; /* skip wSequence */
+	put_ncm(&tmp, opts->block_length, skb->len); /* (d)wBlockLength */
+	/* (d)wFpIndex */
+	/* the first pointer is right after the NTH */
+	put_ncm(&tmp, opts->fp_index, opts->nth_size + ndp_pad);
+
+	tmp = (void *)tmp + ndp_pad;
+
+	/* NDP */
+	put_unaligned_le32(opts->ndp_sign, tmp); /* dwSignature */
+	tmp += 2;
+	/* wLength */
+	put_unaligned_le16(ncb_len - opts->nth_size - pad, tmp++);
+
+	tmp += opts->reserved1;
+	tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */
+	tmp += opts->reserved2;
+
+	if (ncm->is_crc) {
+		uint32_t crc;
+
+		crc = ~crc32_le(~0,
+				skb->data + ncb_len,
+				skb->len - ncb_len);
+		put_unaligned_le32(crc, skb->data + skb->len);
+		skb_put(skb, crc_len);
+	}
+
+	/* (d)wDatagramIndex[0] */
+	put_ncm(&tmp, opts->dgram_item_len, ncb_len);
+	/* (d)wDatagramLength[0] */
+	put_ncm(&tmp, opts->dgram_item_len, skb->len - ncb_len);
+
+	/* (d)wDatagramIndex[1] and  (d)wDatagramLength[1] already zeroed */
+	memset(skb_put(skb, max_size - skb->len), 0, max_size - skb->len);
+
+	return skb;
+}
+
+static int ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	struct ncmnet	*ncm = usb_to_ncm(dev);
+	__le16		*tmp = (void *) skb->data;
+	unsigned	index, index2;
+	unsigned	dg_len, dg_len2;
+	unsigned	ndp_len;
+	struct sk_buff	*skb2;
+	int 		ret = -EINVAL;
+	unsigned	max_size = dev->rx_urb_size;
+	struct ndp_parser_opts *opts = ncm->parser_opts;
+	unsigned	crc_len = ncm->is_crc ? sizeof(uint32_t) : 0;
+
+	/* dwSignature */
+	if (get_unaligned_le32(tmp) != opts->nth_sign) {
+		netdev_err(dev->net, "Wrong NTH SIGN, skblen %d\n", skb->len);
+		goto err;
+	}
+	tmp += 2;
+	/* wHeaderLength */
+	if (get_unaligned_le16(tmp++) != opts->nth_size) {
+		netdev_err(dev->net, "Wrong NTB headersize\n");
+		goto err;
+	}
+	tmp++; /* skip wSequence */
+
+	/* (d)wBlockLength */
+	if (get_ncm(&tmp, opts->block_length) > max_size) {
+		netdev_err(dev->net, "OUT size exceeded\n");
+		goto err;
+	}
+
+	index = get_ncm(&tmp, opts->fp_index);
+	/* NCM 3.2 */
+	if (((index % 4) != 0) && (index < opts->nth_size)) {
+		netdev_err(dev->net, "Bad NCM index: %x\n", index);
+		goto err;
+	}
+
+	/* walk through NDP */
+	tmp = ((void *)skb->data) + index;
+	if (get_unaligned_le32(tmp) != opts->ndp_sign) {
+		netdev_err(dev->net, "Wrong NDP SIGN\n");
+		goto err;
+	}
+	tmp += 2;
+
+	ndp_len = get_unaligned_le16(tmp++);
+	/*
+	 * NCM 3.3.1
+	 * entry is 2 items
+	 * item size is opts->dgram_item_len * 2 bytes
+	 * minimal: struct usb_cdc_ncm_ndpX + normal entry + zero entry
+	 */
+	if ((ndp_len < opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2))
+	    || (ndp_len % opts->ndplen_align != 0)) {
+		netdev_err(dev->net, "Bad NDP length: %x\n", ndp_len);
+		goto err;
+	}
+	tmp += opts->reserved1;
+	tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */
+	tmp += opts->reserved2;
+
+	ndp_len -= opts->ndp_size;
+	index2 = get_ncm(&tmp, opts->dgram_item_len);
+	dg_len2 = get_ncm(&tmp, opts->dgram_item_len);
+
+	do {
+		index = index2;
+		dg_len = dg_len2;
+		if (dg_len < 14 + crc_len) { /* ethernet header + crc */
+			netdev_err(dev->net, "Bad dgram length: %x\n", dg_len);
+			goto err;
+		}
+		if (ncm->is_crc) {
+			uint32_t crc, crc2;
+
+			crc = get_unaligned_le32(skb->data +
+						 index + dg_len - crc_len);
+			crc2 = ~crc32_le(~0,
+					 skb->data + index,
+					 dg_len - crc_len);
+			if (crc != crc2) {
+				netdev_err(dev->net, "Bad CRC\n");
+				goto err;
+			}
+		}
+
+		index2 = get_ncm(&tmp, opts->dgram_item_len);
+		dg_len2 = get_ncm(&tmp, opts->dgram_item_len);
+
+		if (index2 == 0 || dg_len2 == 0) {
+			skb2 = skb;
+		} else {
+			skb2 = skb_clone(skb, GFP_ATOMIC);
+			if (skb2 == NULL)
+				goto err;
+		}
+
+		if (!skb_pull(skb2, index)) {
+			ret = -EOVERFLOW;
+			goto err;
+		}
+
+		skb_trim(skb2, dg_len - crc_len);
+		if (skb != skb2) /* caller will return the last one */
+			usbnet_skb_return(dev, skb2);
+
+		ndp_len -= 2 * (opts->dgram_item_len * 2);
+
+		if (index2 == 0 || dg_len2 == 0)
+			break;
+	} while (ndp_len > 2 * (opts->dgram_item_len * 2)); /* zero entry */
+
+	return 1;
+err:
+	/* freed by usbnet code */
+	return 0;
+}
+
+static int ncm_probe(struct usb_interface *udev,
+		     const struct usb_device_id *prod)
+{
+	return usbnet_probe_pvtsize(udev, prod, sizeof(struct ncmnet));
+}
+
+static const struct driver_info ncm_info = {
+	.description =	"CDC NCM Device",
+	/* we make only dwNtbOutMaxSize transfers */
+	.flags =	FLAG_NO_SHORT,
+	.bind =		ncm_bind,
+	.unbind = 	usbnet_cdc_unbind,
+	.status =	cdc_status,
+	.rx_fixup =	ncm_rx_fixup,
+	.tx_fixup =	ncm_tx_fixup,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static const struct usb_device_id products[] = {
+{
+	USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM,
+			USB_CDC_PROTO_NONE),
+	.driver_info = (unsigned long) &ncm_info,
+},
+{
+	/* EMPTY == end of list */
+},
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver ncm_driver = {
+	.name =		"cdc_ncm",
+	.id_table =	products,
+	.probe =	ncm_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+};
+
+
+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("Yauheni Kaliuta <yauheni.kaliuta@xxxxxxxxx>");
+MODULE_DESCRIPTION("USB CDC NCM");
+MODULE_LICENSE("GPL");
-- 
1.7.0.4

--
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