diff options
author | Steve Lin <stlin@nvidia.com> | 2011-02-14 11:31:11 -0800 |
---|---|---|
committer | Varun Colbert <vcolbert@nvidia.com> | 2011-02-18 15:07:29 -0800 |
commit | 3dd8821fd5ff588ac45e7fdd7b71f1cdc5c1eb22 (patch) | |
tree | 32cbf8b93b2dd2445cf2559e61583b34cc372ac9 /drivers | |
parent | 301d02c71f07e00270173ef78a332339c441b3ee (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>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/usb/Kconfig | 18 | ||||
-rw-r--r-- | drivers/net/usb/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/usb/cdc_ether.c | 44 | ||||
-rw-r--r-- | drivers/net/usb/cdc_ncm.c | 687 | ||||
-rw-r--r-- | drivers/net/usb/usbnet.c | 6 |
5 files changed, 750 insertions, 6 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, + ¶ms->ntb_params, sizeof(struct ntb_params))) { + dev_dbg(&intf->dev, "GET_NTB_PARAMETERS request failed!\n"); + goto bad_param; + } else { + le16_to_cpus(¶ms->ntb_params.wLength); + le16_to_cpus(¶ms->ntb_params.bmNtbFormatsSupported); + le16_to_cpus(¶ms->ntb_params.dwNtbInMaxSize); + le16_to_cpus(¶ms->ntb_params.wNdpInDivisor); + le16_to_cpus(¶ms->ntb_params.wNdpInPayloadRemainder); + le16_to_cpus(¶ms->ntb_params.wNdpInAlignment); + le16_to_cpus(¶ms->ntb_params.dwNtbOutMaxSize); + le16_to_cpus(¶ms->ntb_params.wNdpOutDivisor); + le16_to_cpus(¶ms->ntb_params.wNdpOutPayloadRemainder); + le16_to_cpus(¶ms->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: |