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, + ¶m, 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