diff options
Diffstat (limited to 'drivers/net/usb/cdc_ncm.c')
| -rw-r--r-- | drivers/net/usb/cdc_ncm.c | 1767 | 
1 files changed, 1085 insertions, 682 deletions
diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 593c104ab19..80a844e0ae0 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -1,7 +1,7 @@  /*   * cdc_ncm.c   * - * Copyright (C) ST-Ericsson 2010 + * Copyright (C) ST-Ericsson 2010-2012   * Contact: Alexey Orishko <alexey.orishko@stericsson.com>   * Original author: Hans Petter Selasky <hans.petter.selasky@stericsson.com>   * @@ -39,7 +39,6 @@   */  #include <linux/module.h> -#include <linux/init.h>  #include <linux/netdevice.h>  #include <linux/ctype.h>  #include <linux/ethtool.h> @@ -47,214 +46,524 @@  #include <linux/mii.h>  #include <linux/crc32.h>  #include <linux/usb.h> -#include <linux/version.h> -#include <linux/timer.h> -#include <linux/spinlock.h> +#include <linux/hrtimer.h>  #include <linux/atomic.h>  #include <linux/usb/usbnet.h>  #include <linux/usb/cdc.h> +#include <linux/usb/cdc_ncm.h> + +#if IS_ENABLED(CONFIG_USB_NET_CDC_MBIM) +static bool prefer_mbim = true; +#else +static bool prefer_mbim; +#endif +module_param(prefer_mbim, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(prefer_mbim, "Prefer MBIM setting on dual NCM/MBIM functions"); + +static void cdc_ncm_txpath_bh(unsigned long param); +static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx); +static enum hrtimer_restart cdc_ncm_tx_timer_cb(struct hrtimer *hr_timer); +static struct usb_driver cdc_ncm_driver; -#define	DRIVER_VERSION				"30-Nov-2010" +struct cdc_ncm_stats { +	char stat_string[ETH_GSTRING_LEN]; +	int sizeof_stat; +	int stat_offset; +}; -/* CDC NCM subclass 3.2.1 */ -#define USB_CDC_NCM_NDP16_LENGTH_MIN		0x10 +#define CDC_NCM_STAT(str, m) { \ +		.stat_string = str, \ +		.sizeof_stat = sizeof(((struct cdc_ncm_ctx *)0)->m), \ +		.stat_offset = offsetof(struct cdc_ncm_ctx, m) } +#define CDC_NCM_SIMPLE_STAT(m)	CDC_NCM_STAT(__stringify(m), m) + +static const struct cdc_ncm_stats cdc_ncm_gstrings_stats[] = { +	CDC_NCM_SIMPLE_STAT(tx_reason_ntb_full), +	CDC_NCM_SIMPLE_STAT(tx_reason_ndp_full), +	CDC_NCM_SIMPLE_STAT(tx_reason_timeout), +	CDC_NCM_SIMPLE_STAT(tx_reason_max_datagram), +	CDC_NCM_SIMPLE_STAT(tx_overhead), +	CDC_NCM_SIMPLE_STAT(tx_ntbs), +	CDC_NCM_SIMPLE_STAT(rx_overhead), +	CDC_NCM_SIMPLE_STAT(rx_ntbs), +}; -/* Maximum NTB length */ -#define	CDC_NCM_NTB_MAX_SIZE_TX			16384	/* bytes */ -#define	CDC_NCM_NTB_MAX_SIZE_RX			16384	/* bytes */ +static int cdc_ncm_get_sset_count(struct net_device __always_unused *netdev, int sset) +{ +	switch (sset) { +	case ETH_SS_STATS: +		return ARRAY_SIZE(cdc_ncm_gstrings_stats); +	default: +		return -EOPNOTSUPP; +	} +} -/* Minimum value for MaxDatagramSize, ch. 6.2.9 */ -#define	CDC_NCM_MIN_DATAGRAM_SIZE		1514	/* bytes */ +static void cdc_ncm_get_ethtool_stats(struct net_device *netdev, +				    struct ethtool_stats __always_unused *stats, +				    u64 *data) +{ +	struct usbnet *dev = netdev_priv(netdev); +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	int i; +	char *p = NULL; -#define	CDC_NCM_MIN_TX_PKT			512	/* bytes */ +	for (i = 0; i < ARRAY_SIZE(cdc_ncm_gstrings_stats); i++) { +		p = (char *)ctx + cdc_ncm_gstrings_stats[i].stat_offset; +		data[i] = (cdc_ncm_gstrings_stats[i].sizeof_stat == sizeof(u64)) ? *(u64 *)p : *(u32 *)p; +	} +} -/* Default value for MaxDatagramSize */ -#define	CDC_NCM_MAX_DATAGRAM_SIZE		2048	/* bytes */ +static void cdc_ncm_get_strings(struct net_device __always_unused *netdev, u32 stringset, u8 *data) +{ +	u8 *p = data; +	int i; + +	switch (stringset) { +	case ETH_SS_STATS: +		for (i = 0; i < ARRAY_SIZE(cdc_ncm_gstrings_stats); i++) { +			memcpy(p, cdc_ncm_gstrings_stats[i].stat_string, ETH_GSTRING_LEN); +			p += ETH_GSTRING_LEN; +		} +	} +} -/* - * Maximum amount of datagrams in NCM Datagram Pointer Table, not counting - * the last NULL entry. Any additional datagrams in NTB would be discarded. - */ -#define	CDC_NCM_DPT_DATAGRAMS_MAX		32 +static void cdc_ncm_update_rxtx_max(struct usbnet *dev, u32 new_rx, u32 new_tx); + +static const struct ethtool_ops cdc_ncm_ethtool_ops = { +	.get_settings      = usbnet_get_settings, +	.set_settings      = usbnet_set_settings, +	.get_link          = usbnet_get_link, +	.nway_reset        = usbnet_nway_reset, +	.get_drvinfo       = usbnet_get_drvinfo, +	.get_msglevel      = usbnet_get_msglevel, +	.set_msglevel      = usbnet_set_msglevel, +	.get_ts_info       = ethtool_op_get_ts_info, +	.get_sset_count    = cdc_ncm_get_sset_count, +	.get_strings       = cdc_ncm_get_strings, +	.get_ethtool_stats = cdc_ncm_get_ethtool_stats, +}; -/* Restart the timer, if amount of datagrams is less than given value */ -#define	CDC_NCM_RESTART_TIMER_DATAGRAM_CNT	3 +static u32 cdc_ncm_check_rx_max(struct usbnet *dev, u32 new_rx) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	u32 val, max, min; -/* The following macro defines the minimum header space */ -#define	CDC_NCM_MIN_HDR_SIZE \ -	(sizeof(struct usb_cdc_ncm_nth16) + sizeof(struct usb_cdc_ncm_ndp16) + \ -	(CDC_NCM_DPT_DATAGRAMS_MAX + 1) * sizeof(struct usb_cdc_ncm_dpe16)) +	/* clamp new_rx to sane values */ +	min = USB_CDC_NCM_NTB_MIN_IN_SIZE; +	max = min_t(u32, CDC_NCM_NTB_MAX_SIZE_RX, le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize)); -struct connection_speed_change { -	__le32	USBitRate; /* holds 3GPP downlink value, bits per second */ -	__le32	DSBitRate; /* holds 3GPP uplink value, bits per second */ -} __attribute__ ((packed)); +	/* dwNtbInMaxSize spec violation? Use MIN size for both limits */ +	if (max < min) { +		dev_warn(&dev->intf->dev, "dwNtbInMaxSize=%u is too small. Using %u\n", +			 le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize), min); +		max = min; +	} -struct cdc_ncm_data { -	struct usb_cdc_ncm_nth16 nth16; -	struct usb_cdc_ncm_ndp16 ndp16; -	struct usb_cdc_ncm_dpe16 dpe16[CDC_NCM_DPT_DATAGRAMS_MAX + 1]; -}; +	val = clamp_t(u32, new_rx, min, max); +	if (val != new_rx) +		dev_dbg(&dev->intf->dev, "rx_max must be in the [%u, %u] range\n", min, max); -struct cdc_ncm_ctx { -	struct cdc_ncm_data rx_ncm; -	struct cdc_ncm_data tx_ncm; -	struct usb_cdc_ncm_ntb_parameters ncm_parm; -	struct timer_list tx_timer; - -	const struct usb_cdc_ncm_desc *func_desc; -	const struct usb_cdc_header_desc *header_desc; -	const struct usb_cdc_union_desc *union_desc; -	const struct usb_cdc_ether_desc *ether_desc; - -	struct net_device *netdev; -	struct usb_device *udev; -	struct usb_host_endpoint *in_ep; -	struct usb_host_endpoint *out_ep; -	struct usb_host_endpoint *status_ep; -	struct usb_interface *intf; -	struct usb_interface *control; -	struct usb_interface *data; - -	struct sk_buff *tx_curr_skb; -	struct sk_buff *tx_rem_skb; - -	spinlock_t mtx; - -	u32 tx_timer_pending; -	u32 tx_curr_offset; -	u32 tx_curr_last_offset; -	u32 tx_curr_frame_num; -	u32 rx_speed; -	u32 tx_speed; -	u32 rx_max; -	u32 tx_max; -	u32 max_datagram_size; -	u16 tx_max_datagrams; -	u16 tx_remainder; -	u16 tx_modulus; -	u16 tx_ndp_modulus; -	u16 tx_seq; -	u16 connected; -	u8 data_claimed; -	u8 control_claimed; -}; +	return val; +} -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 u32 cdc_ncm_check_tx_max(struct usbnet *dev, u32 new_tx) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	u32 val, max, min; -static const struct usb_device_id cdc_devs[] = { -	{ USB_INTERFACE_INFO(USB_CLASS_COMM, -		USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), -		.driver_info = (unsigned long)&cdc_ncm_info, -	}, -	{ -	}, -}; +	/* clamp new_tx to sane values */ +	min = ctx->max_datagram_size + ctx->max_ndp_size + sizeof(struct usb_cdc_ncm_nth16); +	max = min_t(u32, CDC_NCM_NTB_MAX_SIZE_TX, le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize)); -MODULE_DEVICE_TABLE(usb, cdc_devs); +	/* some devices set dwNtbOutMaxSize too low for the above default */ +	min = min(min, max); -static void -cdc_ncm_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info) +	val = clamp_t(u32, new_tx, min, max); +	if (val != new_tx) +		dev_dbg(&dev->intf->dev, "tx_max must be in the [%u, %u] range\n", min, max); + +	return val; +} + +static ssize_t cdc_ncm_show_min_tx_pkt(struct device *d, struct device_attribute *attr, char *buf)  { -	struct usbnet *dev = netdev_priv(net); +	struct usbnet *dev = netdev_priv(to_net_dev(d)); +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; -	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)); +	return sprintf(buf, "%u\n", ctx->min_tx_pkt);  } -static int -cdc_ncm_do_request(struct cdc_ncm_ctx *ctx, struct usb_cdc_notification *req, -		   void *data, u16 flags, u16 *actlen, u16 timeout) +static ssize_t cdc_ncm_show_rx_max(struct device *d, struct device_attribute *attr, char *buf)  { -	int err; +	struct usbnet *dev = netdev_priv(to_net_dev(d)); +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; -	err = usb_control_msg(ctx->udev, (req->bmRequestType & USB_DIR_IN) ? -				usb_rcvctrlpipe(ctx->udev, 0) : -				usb_sndctrlpipe(ctx->udev, 0), -				req->bNotificationType, req->bmRequestType, -				req->wValue, -				req->wIndex, data, -				req->wLength, timeout); +	return sprintf(buf, "%u\n", ctx->rx_max); +} -	if (err < 0) { -		if (actlen) -			*actlen = 0; -		return err; +static ssize_t cdc_ncm_show_tx_max(struct device *d, struct device_attribute *attr, char *buf) +{ +	struct usbnet *dev = netdev_priv(to_net_dev(d)); +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + +	return sprintf(buf, "%u\n", ctx->tx_max); +} + +static ssize_t cdc_ncm_show_tx_timer_usecs(struct device *d, struct device_attribute *attr, char *buf) +{ +	struct usbnet *dev = netdev_priv(to_net_dev(d)); +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + +	return sprintf(buf, "%u\n", ctx->timer_interval / (u32)NSEC_PER_USEC); +} + +static ssize_t cdc_ncm_store_min_tx_pkt(struct device *d,  struct device_attribute *attr, const char *buf, size_t len) +{ +	struct usbnet *dev = netdev_priv(to_net_dev(d)); +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	unsigned long val; + +	/* no need to restrict values - anything from 0 to infinity is OK */ +	if (kstrtoul(buf, 0, &val)) +		return -EINVAL; + +	ctx->min_tx_pkt = val; +	return len; +} + +static ssize_t cdc_ncm_store_rx_max(struct device *d,  struct device_attribute *attr, const char *buf, size_t len) +{ +	struct usbnet *dev = netdev_priv(to_net_dev(d)); +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	unsigned long val; + +	if (kstrtoul(buf, 0, &val) || cdc_ncm_check_rx_max(dev, val) != val) +		return -EINVAL; + +	cdc_ncm_update_rxtx_max(dev, val, ctx->tx_max); +	return len; +} + +static ssize_t cdc_ncm_store_tx_max(struct device *d,  struct device_attribute *attr, const char *buf, size_t len) +{ +	struct usbnet *dev = netdev_priv(to_net_dev(d)); +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	unsigned long val; + +	if (kstrtoul(buf, 0, &val) || cdc_ncm_check_tx_max(dev, val) != val) +		return -EINVAL; + +	cdc_ncm_update_rxtx_max(dev, ctx->rx_max, val); +	return len; +} + +static ssize_t cdc_ncm_store_tx_timer_usecs(struct device *d,  struct device_attribute *attr, const char *buf, size_t len) +{ +	struct usbnet *dev = netdev_priv(to_net_dev(d)); +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	ssize_t ret; +	unsigned long val; + +	ret = kstrtoul(buf, 0, &val); +	if (ret) +		return ret; +	if (val && (val < CDC_NCM_TIMER_INTERVAL_MIN || val > CDC_NCM_TIMER_INTERVAL_MAX)) +		return -EINVAL; + +	spin_lock_bh(&ctx->mtx); +	ctx->timer_interval = val * NSEC_PER_USEC; +	if (!ctx->timer_interval) +		ctx->tx_timer_pending = 0; +	spin_unlock_bh(&ctx->mtx); +	return len; +} + +static DEVICE_ATTR(min_tx_pkt, S_IRUGO | S_IWUSR, cdc_ncm_show_min_tx_pkt, cdc_ncm_store_min_tx_pkt); +static DEVICE_ATTR(rx_max, S_IRUGO | S_IWUSR, cdc_ncm_show_rx_max, cdc_ncm_store_rx_max); +static DEVICE_ATTR(tx_max, S_IRUGO | S_IWUSR, cdc_ncm_show_tx_max, cdc_ncm_store_tx_max); +static DEVICE_ATTR(tx_timer_usecs, S_IRUGO | S_IWUSR, cdc_ncm_show_tx_timer_usecs, cdc_ncm_store_tx_timer_usecs); + +#define NCM_PARM_ATTR(name, format, tocpu)				\ +static ssize_t cdc_ncm_show_##name(struct device *d, struct device_attribute *attr, char *buf) \ +{ \ +	struct usbnet *dev = netdev_priv(to_net_dev(d)); \ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; \ +	return sprintf(buf, format "\n", tocpu(ctx->ncm_parm.name));	\ +} \ +static DEVICE_ATTR(name, S_IRUGO, cdc_ncm_show_##name, NULL) + +NCM_PARM_ATTR(bmNtbFormatsSupported, "0x%04x", le16_to_cpu); +NCM_PARM_ATTR(dwNtbInMaxSize, "%u", le32_to_cpu); +NCM_PARM_ATTR(wNdpInDivisor, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNdpInPayloadRemainder, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNdpInAlignment, "%u", le16_to_cpu); +NCM_PARM_ATTR(dwNtbOutMaxSize, "%u", le32_to_cpu); +NCM_PARM_ATTR(wNdpOutDivisor, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNdpOutPayloadRemainder, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNdpOutAlignment, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNtbOutMaxDatagrams, "%u", le16_to_cpu); + +static struct attribute *cdc_ncm_sysfs_attrs[] = { +	&dev_attr_min_tx_pkt.attr, +	&dev_attr_rx_max.attr, +	&dev_attr_tx_max.attr, +	&dev_attr_tx_timer_usecs.attr, +	&dev_attr_bmNtbFormatsSupported.attr, +	&dev_attr_dwNtbInMaxSize.attr, +	&dev_attr_wNdpInDivisor.attr, +	&dev_attr_wNdpInPayloadRemainder.attr, +	&dev_attr_wNdpInAlignment.attr, +	&dev_attr_dwNtbOutMaxSize.attr, +	&dev_attr_wNdpOutDivisor.attr, +	&dev_attr_wNdpOutPayloadRemainder.attr, +	&dev_attr_wNdpOutAlignment.attr, +	&dev_attr_wNtbOutMaxDatagrams.attr, +	NULL, +}; + +static struct attribute_group cdc_ncm_sysfs_attr_group = { +	.name = "cdc_ncm", +	.attrs = cdc_ncm_sysfs_attrs, +}; + +/* handle rx_max and tx_max changes */ +static void cdc_ncm_update_rxtx_max(struct usbnet *dev, u32 new_rx, u32 new_tx) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	u8 iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber; +	u32 val; + +	val = cdc_ncm_check_rx_max(dev, new_rx); + +	/* inform device about NTB input size changes */ +	if (val != ctx->rx_max) { +		__le32 dwNtbInMaxSize = cpu_to_le32(val); + +		dev_info(&dev->intf->dev, "setting rx_max = %u\n", val); + +		/* tell device to use new size */ +		if (usbnet_write_cmd(dev, USB_CDC_SET_NTB_INPUT_SIZE, +				     USB_TYPE_CLASS | USB_DIR_OUT +				     | USB_RECIP_INTERFACE, +				     0, iface_no, &dwNtbInMaxSize, 4) < 0) +			dev_dbg(&dev->intf->dev, "Setting NTB Input Size failed\n"); +		else +			ctx->rx_max = val; +	} + +	/* usbnet use these values for sizing rx queues */ +	if (dev->rx_urb_size != ctx->rx_max) { +		dev->rx_urb_size = ctx->rx_max; +		if (netif_running(dev->net)) +			usbnet_unlink_rx_urbs(dev); +	} + +	val = cdc_ncm_check_tx_max(dev, new_tx); +	if (val != ctx->tx_max) +		dev_info(&dev->intf->dev, "setting tx_max = %u\n", val); + +	/* Adding a pad byte here if necessary simplifies the handling +	 * in cdc_ncm_fill_tx_frame, making tx_max always represent +	 * the real skb max size. +	 * +	 * We cannot use dev->maxpacket here because this is called from +	 * .bind which is called before usbnet sets up dev->maxpacket +	 */ +	if (val != le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize) && +	    val % usb_maxpacket(dev->udev, dev->out, 1) == 0) +		val++; + +	/* we might need to flush any pending tx buffers if running */ +	if (netif_running(dev->net) && val > ctx->tx_max) { +		netif_tx_lock_bh(dev->net); +		usbnet_start_xmit(NULL, dev->net); +		/* make sure tx_curr_skb is reallocated if it was empty */ +		if (ctx->tx_curr_skb) { +			dev_kfree_skb_any(ctx->tx_curr_skb); +			ctx->tx_curr_skb = NULL; +		} +		ctx->tx_max = val; +		netif_tx_unlock_bh(dev->net); +	} else { +		ctx->tx_max = val;  	} -	if (actlen) -		*actlen = err; +	dev->hard_mtu = ctx->tx_max; + +	/* max qlen depend on hard_mtu and rx_urb_size */ +	usbnet_update_max_qlen(dev); + +	/* never pad more than 3 full USB packets per transfer */ +	ctx->min_tx_pkt = clamp_t(u16, ctx->tx_max - 3 * usb_maxpacket(dev->udev, dev->out, 1), +				  CDC_NCM_MIN_TX_PKT, ctx->tx_max); +} + +/* helpers for NCM and MBIM differences */ +static u8 cdc_ncm_flags(struct usbnet *dev) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting) && ctx->mbim_desc) +		return ctx->mbim_desc->bmNetworkCapabilities; +	if (ctx->func_desc) +		return ctx->func_desc->bmNetworkCapabilities;  	return 0;  } -static u8 cdc_ncm_setup(struct cdc_ncm_ctx *ctx) +static int cdc_ncm_eth_hlen(struct usbnet *dev)  { -	struct usb_cdc_notification req; -	u32 val; -	__le16 max_datagram_size; -	u8 flags; -	u8 iface_no; +	if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting)) +		return 0; +	return ETH_HLEN; +} + +static u32 cdc_ncm_min_dgram_size(struct usbnet *dev) +{ +	if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting)) +		return CDC_MBIM_MIN_DATAGRAM_SIZE; +	return CDC_NCM_MIN_DATAGRAM_SIZE; +} + +static u32 cdc_ncm_max_dgram_size(struct usbnet *dev) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + +	if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting) && ctx->mbim_desc) +		return le16_to_cpu(ctx->mbim_desc->wMaxSegmentSize); +	if (ctx->ether_desc) +		return le16_to_cpu(ctx->ether_desc->wMaxSegmentSize); +	return CDC_NCM_MAX_DATAGRAM_SIZE; +} + +/* initial one-time device setup.  MUST be called with the data interface + * in altsetting 0 + */ +static int cdc_ncm_init(struct usbnet *dev) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	u8 iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber;  	int err; -	iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber; +	err = usbnet_read_cmd(dev, USB_CDC_GET_NTB_PARAMETERS, +			      USB_TYPE_CLASS | USB_DIR_IN +			      |USB_RECIP_INTERFACE, +			      0, iface_no, &ctx->ncm_parm, +			      sizeof(ctx->ncm_parm)); +	if (err < 0) { +		dev_err(&dev->intf->dev, "failed GET_NTB_PARAMETERS\n"); +		return err; /* GET_NTB_PARAMETERS is required */ +	} -	req.bmRequestType = USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE; -	req.bNotificationType = USB_CDC_GET_NTB_PARAMETERS; -	req.wValue = 0; -	req.wIndex = cpu_to_le16(iface_no); -	req.wLength = cpu_to_le16(sizeof(ctx->ncm_parm)); +	/* set CRC Mode */ +	if (cdc_ncm_flags(dev) & USB_CDC_NCM_NCAP_CRC_MODE) { +		dev_dbg(&dev->intf->dev, "Setting CRC mode off\n"); +		err = usbnet_write_cmd(dev, USB_CDC_SET_CRC_MODE, +				       USB_TYPE_CLASS | USB_DIR_OUT +				       | USB_RECIP_INTERFACE, +				       USB_CDC_NCM_CRC_NOT_APPENDED, +				       iface_no, NULL, 0); +		if (err < 0) +			dev_err(&dev->intf->dev, "SET_CRC_MODE failed\n"); +	} -	err = cdc_ncm_do_request(ctx, &req, &ctx->ncm_parm, 0, NULL, 1000); -	if (err) { -		pr_debug("failed GET_NTB_PARAMETERS\n"); -		return 1; +	/* set NTB format, if both formats are supported. +	 * +	 * "The host shall only send this command while the NCM Data +	 *  Interface is in alternate setting 0." +	 */ +	if (le16_to_cpu(ctx->ncm_parm.bmNtbFormatsSupported) & +						USB_CDC_NCM_NTB32_SUPPORTED) { +		dev_dbg(&dev->intf->dev, "Setting NTB format to 16-bit\n"); +		err = usbnet_write_cmd(dev, USB_CDC_SET_NTB_FORMAT, +				       USB_TYPE_CLASS | USB_DIR_OUT +				       | USB_RECIP_INTERFACE, +				       USB_CDC_NCM_NTB16_FORMAT, +				       iface_no, NULL, 0); +		if (err < 0) +			dev_err(&dev->intf->dev, "SET_NTB_FORMAT failed\n");  	} -	/* read correct set of parameters according to device mode */ +	/* set initial device values */  	ctx->rx_max = le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize);  	ctx->tx_max = le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize);  	ctx->tx_remainder = le16_to_cpu(ctx->ncm_parm.wNdpOutPayloadRemainder);  	ctx->tx_modulus = le16_to_cpu(ctx->ncm_parm.wNdpOutDivisor);  	ctx->tx_ndp_modulus = le16_to_cpu(ctx->ncm_parm.wNdpOutAlignment); +	/* devices prior to NCM Errata shall set this field to zero */ +	ctx->tx_max_datagrams = le16_to_cpu(ctx->ncm_parm.wNtbOutMaxDatagrams); -	if (ctx->func_desc != NULL) -		flags = ctx->func_desc->bmNetworkCapabilities; -	else -		flags = 0; - -	pr_debug("dwNtbInMaxSize=%u dwNtbOutMaxSize=%u " -		 "wNdpOutPayloadRemainder=%u wNdpOutDivisor=%u " -		 "wNdpOutAlignment=%u flags=0x%x\n", -		 ctx->rx_max, ctx->tx_max, ctx->tx_remainder, ctx->tx_modulus, -		 ctx->tx_ndp_modulus, flags); - -	/* max count of tx datagrams without terminating NULL entry */ -	ctx->tx_max_datagrams = CDC_NCM_DPT_DATAGRAMS_MAX; - -	/* verify maximum size of received NTB in bytes */ -	if ((ctx->rx_max < -	    (CDC_NCM_MIN_HDR_SIZE + CDC_NCM_MIN_DATAGRAM_SIZE)) || -	    (ctx->rx_max > CDC_NCM_NTB_MAX_SIZE_RX)) { -		pr_debug("Using default maximum receive length=%d\n", -						CDC_NCM_NTB_MAX_SIZE_RX); -		ctx->rx_max = CDC_NCM_NTB_MAX_SIZE_RX; +	dev_dbg(&dev->intf->dev, +		"dwNtbInMaxSize=%u dwNtbOutMaxSize=%u wNdpOutPayloadRemainder=%u wNdpOutDivisor=%u wNdpOutAlignment=%u wNtbOutMaxDatagrams=%u flags=0x%x\n", +		ctx->rx_max, ctx->tx_max, ctx->tx_remainder, ctx->tx_modulus, +		ctx->tx_ndp_modulus, ctx->tx_max_datagrams, cdc_ncm_flags(dev)); + +	/* max count of tx datagrams */ +	if ((ctx->tx_max_datagrams == 0) || +			(ctx->tx_max_datagrams > CDC_NCM_DPT_DATAGRAMS_MAX)) +		ctx->tx_max_datagrams = CDC_NCM_DPT_DATAGRAMS_MAX; + +	/* set up maximum NDP size */ +	ctx->max_ndp_size = sizeof(struct usb_cdc_ncm_ndp16) + (ctx->tx_max_datagrams + 1) * sizeof(struct usb_cdc_ncm_dpe16); + +	/* initial coalescing timer interval */ +	ctx->timer_interval = CDC_NCM_TIMER_INTERVAL_USEC * NSEC_PER_USEC; + +	return 0; +} + +/* set a new max datagram size */ +static void cdc_ncm_set_dgram_size(struct usbnet *dev, int new_size) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	u8 iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber; +	__le16 max_datagram_size; +	u16 mbim_mtu; +	int err; + +	/* set default based on descriptors */ +	ctx->max_datagram_size = clamp_t(u32, new_size, +					 cdc_ncm_min_dgram_size(dev), +					 CDC_NCM_MAX_DATAGRAM_SIZE); + +	/* inform the device about the selected Max Datagram Size? */ +	if (!(cdc_ncm_flags(dev) & USB_CDC_NCM_NCAP_MAX_DATAGRAM_SIZE)) +		goto out; + +	/* read current mtu value from device */ +	err = usbnet_read_cmd(dev, USB_CDC_GET_MAX_DATAGRAM_SIZE, +			      USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE, +			      0, iface_no, &max_datagram_size, 2); +	if (err < 0) { +		dev_dbg(&dev->intf->dev, "GET_MAX_DATAGRAM_SIZE failed\n"); +		goto out;  	} -	/* verify maximum size of transmitted NTB in bytes */ -	if ((ctx->tx_max < -	    (CDC_NCM_MIN_HDR_SIZE + CDC_NCM_MIN_DATAGRAM_SIZE)) || -	    (ctx->tx_max > CDC_NCM_NTB_MAX_SIZE_TX)) { -		pr_debug("Using default maximum transmit length=%d\n", -						CDC_NCM_NTB_MAX_SIZE_TX); -		ctx->tx_max = CDC_NCM_NTB_MAX_SIZE_TX; +	if (le16_to_cpu(max_datagram_size) == ctx->max_datagram_size) +		goto out; + +	max_datagram_size = cpu_to_le16(ctx->max_datagram_size); +	err = usbnet_write_cmd(dev, USB_CDC_SET_MAX_DATAGRAM_SIZE, +			       USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE, +			       0, iface_no, &max_datagram_size, 2); +	if (err < 0) +		dev_dbg(&dev->intf->dev, "SET_MAX_DATAGRAM_SIZE failed\n"); + +out: +	/* set MTU to max supported by the device if necessary */ +	dev->net->mtu = min_t(int, dev->net->mtu, ctx->max_datagram_size - cdc_ncm_eth_hlen(dev)); + +	/* do not exceed operater preferred MTU */ +	if (ctx->mbim_extended_desc) { +		mbim_mtu = le16_to_cpu(ctx->mbim_extended_desc->wMTU); +		if (mbim_mtu != 0 && mbim_mtu < dev->net->mtu) +			dev->net->mtu = mbim_mtu;  	} +} + +static void cdc_ncm_fix_modulus(struct usbnet *dev) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	u32 val;  	/*  	 * verify that the structure alignment is: @@ -266,7 +575,7 @@ static u8 cdc_ncm_setup(struct cdc_ncm_ctx *ctx)  	if ((val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE) ||  	    (val != ((-val) & val)) || (val >= ctx->tx_max)) { -		pr_debug("Using default alignment: 4 bytes\n"); +		dev_dbg(&dev->intf->dev, "Using default alignment: 4 bytes\n");  		ctx->tx_ndp_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE;  	} @@ -280,76 +589,49 @@ static u8 cdc_ncm_setup(struct cdc_ncm_ctx *ctx)  	if ((val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE) ||  	    (val != ((-val) & val)) || (val >= ctx->tx_max)) { -		pr_debug("Using default transmit modulus: 4 bytes\n"); +		dev_dbg(&dev->intf->dev, "Using default transmit modulus: 4 bytes\n");  		ctx->tx_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE;  	}  	/* verify the payload remainder */  	if (ctx->tx_remainder >= ctx->tx_modulus) { -		pr_debug("Using default transmit remainder: 0 bytes\n"); +		dev_dbg(&dev->intf->dev, "Using default transmit remainder: 0 bytes\n");  		ctx->tx_remainder = 0;  	}  	/* adjust TX-remainder according to NCM specification. */ -	ctx->tx_remainder = ((ctx->tx_remainder - ETH_HLEN) & -						(ctx->tx_modulus - 1)); +	ctx->tx_remainder = ((ctx->tx_remainder - cdc_ncm_eth_hlen(dev)) & +			     (ctx->tx_modulus - 1)); +} -	/* additional configuration */ +static int cdc_ncm_setup(struct usbnet *dev) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	u32 def_rx, def_tx; -	/* set CRC Mode */ -	req.bmRequestType = USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE; -	req.bNotificationType = USB_CDC_SET_CRC_MODE; -	req.wValue = cpu_to_le16(USB_CDC_NCM_CRC_NOT_APPENDED); -	req.wIndex = cpu_to_le16(iface_no); -	req.wLength = 0; - -	err = cdc_ncm_do_request(ctx, &req, NULL, 0, NULL, 1000); -	if (err) -		pr_debug("Setting CRC mode off failed\n"); - -	/* set NTB format */ -	req.bmRequestType = USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE; -	req.bNotificationType = USB_CDC_SET_NTB_FORMAT; -	req.wValue = cpu_to_le16(USB_CDC_NCM_NTB16_FORMAT); -	req.wIndex = cpu_to_le16(iface_no); -	req.wLength = 0; - -	err = cdc_ncm_do_request(ctx, &req, NULL, 0, NULL, 1000); -	if (err) -		pr_debug("Setting NTB format to 16-bit failed\n"); - -	/* set Max Datagram Size (MTU) */ -	req.bmRequestType = USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE; -	req.bNotificationType = USB_CDC_GET_MAX_DATAGRAM_SIZE; -	req.wValue = 0; -	req.wIndex = cpu_to_le16(iface_no); -	req.wLength = cpu_to_le16(2); - -	err = cdc_ncm_do_request(ctx, &req, &max_datagram_size, 0, NULL, 1000); -	if (err) { -		pr_debug(" GET_MAX_DATAGRAM_SIZE failed, using size=%u\n", -			 CDC_NCM_MIN_DATAGRAM_SIZE); -		/* use default */ -		ctx->max_datagram_size = CDC_NCM_MIN_DATAGRAM_SIZE; -	} else { -		ctx->max_datagram_size = le16_to_cpu(max_datagram_size); +	/* be conservative when selecting intial buffer size to +	 * increase the number of hosts this will work for +	 */ +	def_rx = min_t(u32, CDC_NCM_NTB_DEF_SIZE_RX, +		       le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize)); +	def_tx = min_t(u32, CDC_NCM_NTB_DEF_SIZE_TX, +		       le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize)); -		if (ctx->max_datagram_size < CDC_NCM_MIN_DATAGRAM_SIZE) -			ctx->max_datagram_size = CDC_NCM_MIN_DATAGRAM_SIZE; -		else if (ctx->max_datagram_size > CDC_NCM_MAX_DATAGRAM_SIZE) -			ctx->max_datagram_size = CDC_NCM_MAX_DATAGRAM_SIZE; -	} +	/* clamp rx_max and tx_max and inform device */ +	cdc_ncm_update_rxtx_max(dev, def_rx, def_tx); -	if (ctx->netdev->mtu != (ctx->max_datagram_size - ETH_HLEN)) -		ctx->netdev->mtu = ctx->max_datagram_size - ETH_HLEN; +	/* sanitize the modulus and remainder values */ +	cdc_ncm_fix_modulus(dev); +	/* set max datagram size */ +	cdc_ncm_set_dgram_size(dev, cdc_ncm_max_dgram_size(dev));  	return 0;  }  static void -cdc_ncm_find_endpoints(struct cdc_ncm_ctx *ctx, struct usb_interface *intf) +cdc_ncm_find_endpoints(struct usbnet *dev, struct usb_interface *intf)  { -	struct usb_host_endpoint *e; +	struct usb_host_endpoint *e, *in = NULL, *out = NULL;  	u8 ep;  	for (ep = 0; ep < intf->cur_altsetting->desc.bNumEndpoints; ep++) { @@ -358,18 +640,18 @@ cdc_ncm_find_endpoints(struct cdc_ncm_ctx *ctx, struct usb_interface *intf)  		switch (e->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {  		case USB_ENDPOINT_XFER_INT:  			if (usb_endpoint_dir_in(&e->desc)) { -				if (ctx->status_ep == NULL) -					ctx->status_ep = e; +				if (!dev->status) +					dev->status = e;  			}  			break;  		case USB_ENDPOINT_XFER_BULK:  			if (usb_endpoint_dir_in(&e->desc)) { -				if (ctx->in_ep == NULL) -					ctx->in_ep = e; +				if (!in) +					in = e;  			} else { -				if (ctx->out_ep == NULL) -					ctx->out_ep = e; +				if (!out) +					out = e;  			}  			break; @@ -377,6 +659,14 @@ cdc_ncm_find_endpoints(struct cdc_ncm_ctx *ctx, struct usb_interface *intf)  			break;  		}  	} +	if (in && !dev->in) +		dev->in = usb_rcvbulkpipe(dev->udev, +					  in->desc.bEndpointAddress & +					  USB_ENDPOINT_NUMBER_MASK); +	if (out && !dev->out) +		dev->out = usb_sndbulkpipe(dev->udev, +					   out->desc.bEndpointAddress & +					   USB_ENDPOINT_NUMBER_MASK);  }  static void cdc_ncm_free(struct cdc_ncm_ctx *ctx) @@ -384,19 +674,6 @@ static void cdc_ncm_free(struct cdc_ncm_ctx *ctx)  	if (ctx == NULL)  		return; -	del_timer_sync(&ctx->tx_timer); - -	if (ctx->data_claimed) { -		usb_set_intfdata(ctx->data, NULL); -		usb_driver_release_interface(driver_of(ctx->intf), ctx->data); -	} - -	if (ctx->control_claimed) { -		usb_set_intfdata(ctx->control, NULL); -		usb_driver_release_interface(driver_of(ctx->intf), -								ctx->control); -	} -  	if (ctx->tx_rem_skb != NULL) {  		dev_kfree_skb_any(ctx->tx_rem_skb);  		ctx->tx_rem_skb = NULL; @@ -410,8 +687,9 @@ static void cdc_ncm_free(struct cdc_ncm_ctx *ctx)  	kfree(ctx);  } -static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf) +int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting)  { +	const struct usb_cdc_union_desc *union_desc = NULL;  	struct cdc_ncm_ctx *ctx;  	struct usb_driver *driver;  	u8 *buf; @@ -419,27 +697,28 @@ static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)  	int temp;  	u8 iface_no; -	ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); -	if (ctx == NULL) -		goto error; +	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); +	if (!ctx) +		return -ENOMEM; -	memset(ctx, 0, sizeof(*ctx)); - -	init_timer(&ctx->tx_timer); +	hrtimer_init(&ctx->tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); +	ctx->tx_timer.function = &cdc_ncm_tx_timer_cb; +	ctx->bh.data = (unsigned long)dev; +	ctx->bh.func = cdc_ncm_txpath_bh; +	atomic_set(&ctx->stop, 0);  	spin_lock_init(&ctx->mtx); -	ctx->netdev = dev->net;  	/* store ctx pointer in device data field */  	dev->data[0] = (unsigned long)ctx; +	/* only the control interface can be successfully probed */ +	ctx->control = intf; +  	/* get some pointers */  	driver = driver_of(intf);  	buf = intf->cur_altsetting->extra;  	len = intf->cur_altsetting->extralen; -	ctx->udev = dev->udev; -	ctx->intf = intf; -  	/* parse through descriptors associated with control interface */  	while ((len > 0) && (buf[0] > 2) && (buf[0] <= len)) { @@ -448,16 +727,18 @@ static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)  		switch (buf[2]) {  		case USB_CDC_UNION_TYPE: -			if (buf[0] < sizeof(*(ctx->union_desc))) +			if (buf[0] < sizeof(*union_desc))  				break; -			ctx->union_desc = -					(const struct usb_cdc_union_desc *)buf; - -			ctx->control = usb_ifnum_to_if(dev->udev, -					ctx->union_desc->bMasterInterface0); +			union_desc = (const struct usb_cdc_union_desc *)buf; +			/* the master must be the interface we are probing */ +			if (intf->cur_altsetting->desc.bInterfaceNumber != +			    union_desc->bMasterInterface0) { +				dev_dbg(&intf->dev, "bogus CDC Union\n"); +				goto error; +			}  			ctx->data = usb_ifnum_to_if(dev->udev, -					ctx->union_desc->bSlaveInterface0); +						    union_desc->bSlaveInterface0);  			break;  		case USB_CDC_ETHERNET_TYPE: @@ -466,19 +747,6 @@ static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)  			ctx->ether_desc =  					(const struct usb_cdc_ether_desc *)buf; - -			dev->hard_mtu = -				le16_to_cpu(ctx->ether_desc->wMaxSegmentSize); - -			if (dev->hard_mtu < -			    (CDC_NCM_MIN_DATAGRAM_SIZE - ETH_HLEN)) -				dev->hard_mtu = -					CDC_NCM_MIN_DATAGRAM_SIZE - ETH_HLEN; - -			else if (dev->hard_mtu > -				 (CDC_NCM_MAX_DATAGRAM_SIZE - ETH_HLEN)) -				dev->hard_mtu = -					CDC_NCM_MAX_DATAGRAM_SIZE - ETH_HLEN;  			break;  		case USB_CDC_NCM_TYPE: @@ -488,6 +756,21 @@ static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)  			ctx->func_desc = (const struct usb_cdc_ncm_desc *)buf;  			break; +		case USB_CDC_MBIM_TYPE: +			if (buf[0] < sizeof(*(ctx->mbim_desc))) +				break; + +			ctx->mbim_desc = (const struct usb_cdc_mbim_desc *)buf; +			break; + +		case USB_CDC_MBIM_EXTENDED_TYPE: +			if (buf[0] < sizeof(*(ctx->mbim_extended_desc))) +				break; + +			ctx->mbim_extended_desc = +				(const struct usb_cdc_mbim_extended_desc *)buf; +			break; +  		default:  			break;  		} @@ -498,198 +781,288 @@ advance:  		len -= temp;  	} +	/* some buggy devices have an IAD but no CDC Union */ +	if (!union_desc && intf->intf_assoc && intf->intf_assoc->bInterfaceCount == 2) { +		ctx->data = usb_ifnum_to_if(dev->udev, intf->cur_altsetting->desc.bInterfaceNumber + 1); +		dev_dbg(&intf->dev, "CDC Union missing - got slave from IAD\n"); +	} +  	/* check if we got everything */ -	if ((ctx->control == NULL) || (ctx->data == NULL) || -	    (ctx->ether_desc == NULL)) +	if (!ctx->data) { +		dev_dbg(&intf->dev, "CDC Union missing and no IAD found\n");  		goto error; - -	/* claim interfaces, if any */ -	if (ctx->data != intf) { -		temp = usb_driver_claim_interface(driver, ctx->data, dev); -		if (temp) +	} +	if (cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) { +		if (!ctx->mbim_desc) { +			dev_dbg(&intf->dev, "MBIM functional descriptor missing\n");  			goto error; -		ctx->data_claimed = 1; +		} +	} else { +		if (!ctx->ether_desc || !ctx->func_desc) { +			dev_dbg(&intf->dev, "NCM or ECM functional descriptors missing\n"); +			goto error; +		}  	} -	if (ctx->control != intf) { -		temp = usb_driver_claim_interface(driver, ctx->control, dev); -		if (temp) +	/* claim data interface, if different from control */ +	if (ctx->data != ctx->control) { +		temp = usb_driver_claim_interface(driver, ctx->data, dev); +		if (temp) { +			dev_dbg(&intf->dev, "failed to claim data intf\n");  			goto error; -		ctx->control_claimed = 1; +		}  	}  	iface_no = ctx->data->cur_altsetting->desc.bInterfaceNumber;  	/* reset data interface */  	temp = usb_set_interface(dev->udev, iface_no, 0); -	if (temp) -		goto error; +	if (temp) { +		dev_dbg(&intf->dev, "set interface failed\n"); +		goto error2; +	} -	/* initialize data interface */ -	if (cdc_ncm_setup(ctx)) -		goto error; +	/* initialize basic device settings */ +	if (cdc_ncm_init(dev)) +		goto error2;  	/* configure data interface */ -	temp = usb_set_interface(dev->udev, iface_no, 1); -	if (temp) -		goto error; - -	cdc_ncm_find_endpoints(ctx, ctx->data); -	cdc_ncm_find_endpoints(ctx, ctx->control); - -	if ((ctx->in_ep == NULL) || (ctx->out_ep == NULL) || -	    (ctx->status_ep == NULL)) -		goto error; +	temp = usb_set_interface(dev->udev, iface_no, data_altsetting); +	if (temp) { +		dev_dbg(&intf->dev, "set interface failed\n"); +		goto error2; +	} -	dev->net->ethtool_ops = &cdc_ncm_ethtool_ops; +	cdc_ncm_find_endpoints(dev, ctx->data); +	cdc_ncm_find_endpoints(dev, ctx->control); +	if (!dev->in || !dev->out || !dev->status) { +		dev_dbg(&intf->dev, "failed to collect endpoints\n"); +		goto error2; +	}  	usb_set_intfdata(ctx->data, dev);  	usb_set_intfdata(ctx->control, dev); -	usb_set_intfdata(ctx->intf, dev); -	temp = usbnet_get_ethernet_addr(dev, ctx->ether_desc->iMACAddress); -	if (temp) -		goto error; +	if (ctx->ether_desc) { +		temp = usbnet_get_ethernet_addr(dev, ctx->ether_desc->iMACAddress); +		if (temp) { +			dev_dbg(&intf->dev, "failed to get mac address\n"); +			goto error2; +		} +		dev_info(&intf->dev, "MAC-Address: %pM\n", dev->net->dev_addr); +	} -	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]); +	/* finish setting up the device specific data */ +	cdc_ncm_setup(dev); -	dev->in = usb_rcvbulkpipe(dev->udev, -		ctx->in_ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); -	dev->out = usb_sndbulkpipe(dev->udev, -		ctx->out_ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); -	dev->status = ctx->status_ep; -	dev->rx_urb_size = ctx->rx_max; +	/* override ethtool_ops */ +	dev->net->ethtool_ops = &cdc_ncm_ethtool_ops; + +	/* add our sysfs attrs */ +	dev->net->sysfs_groups[0] = &cdc_ncm_sysfs_attr_group; -	/* -	 * We should get an event when network connection is "connected" or -	 * "disconnected". Set network connection in "disconnected" state -	 * (carrier is OFF) during attach, so the IP network stack does not -	 * start IPv6 negotiation and more. -	 */ -	netif_carrier_off(dev->net); -	ctx->tx_speed = ctx->rx_speed = 0;  	return 0; +error2: +	usb_set_intfdata(ctx->control, NULL); +	usb_set_intfdata(ctx->data, NULL); +	if (ctx->data != ctx->control) +		usb_driver_release_interface(driver, ctx->data);  error:  	cdc_ncm_free((struct cdc_ncm_ctx *)dev->data[0]);  	dev->data[0] = 0; -	dev_info(&dev->udev->dev, "Descriptor failure\n"); +	dev_info(&intf->dev, "bind() failure\n");  	return -ENODEV;  } +EXPORT_SYMBOL_GPL(cdc_ncm_bind_common); -static void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf) +void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)  {  	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; -	struct usb_driver *driver; +	struct usb_driver *driver = driver_of(intf);  	if (ctx == NULL)  		return;		/* no setup */ -	driver = driver_of(intf); +	atomic_set(&ctx->stop, 1); -	usb_set_intfdata(ctx->data, NULL); -	usb_set_intfdata(ctx->control, NULL); -	usb_set_intfdata(ctx->intf, NULL); +	if (hrtimer_active(&ctx->tx_timer)) +		hrtimer_cancel(&ctx->tx_timer); + +	tasklet_kill(&ctx->bh); -	/* release interfaces, if any */ -	if (ctx->data_claimed) { +	/* handle devices with combined control and data interface */ +	if (ctx->control == ctx->data) +		ctx->data = NULL; + +	/* disconnect master --> disconnect slave */ +	if (intf == ctx->control && ctx->data) { +		usb_set_intfdata(ctx->data, NULL);  		usb_driver_release_interface(driver, ctx->data); -		ctx->data_claimed = 0; -	} +		ctx->data = NULL; -	if (ctx->control_claimed) { +	} else if (intf == ctx->data && ctx->control) { +		usb_set_intfdata(ctx->control, NULL);  		usb_driver_release_interface(driver, ctx->control); -		ctx->control_claimed = 0; +		ctx->control = NULL;  	} +	usb_set_intfdata(intf, NULL);  	cdc_ncm_free(ctx);  } +EXPORT_SYMBOL_GPL(cdc_ncm_unbind); -static void cdc_ncm_zero_fill(u8 *ptr, u32 first, u32 end, u32 max) +/* Return the number of the MBIM control interface altsetting iff it + * is preferred and available, + */ +u8 cdc_ncm_select_altsetting(struct usb_interface *intf)  { -	if (first >= max) -		return; -	if (first >= end) -		return; -	if (end > max) -		end = max; -	memset(ptr + first, 0, end - first); +	struct usb_host_interface *alt; + +	/* The MBIM spec defines a NCM compatible default altsetting, +	 * which we may have matched: +	 * +	 *  "Functions that implement both NCM 1.0 and MBIM (an +	 *   “NCM/MBIM function”) according to this recommendation +	 *   shall provide two alternate settings for the +	 *   Communication Interface.  Alternate setting 0, and the +	 *   associated class and endpoint descriptors, shall be +	 *   constructed according to the rules given for the +	 *   Communication Interface in section 5 of [USBNCM10]. +	 *   Alternate setting 1, and the associated class and +	 *   endpoint descriptors, shall be constructed according to +	 *   the rules given in section 6 (USB Device Model) of this +	 *   specification." +	 */ +	if (intf->num_altsetting < 2) +		return intf->cur_altsetting->desc.bAlternateSetting; + +	if (prefer_mbim) { +		alt = usb_altnum_to_altsetting(intf, CDC_NCM_COMM_ALTSETTING_MBIM); +		if (alt && cdc_ncm_comm_intf_is_mbim(alt)) +			return CDC_NCM_COMM_ALTSETTING_MBIM; +	} +	return CDC_NCM_COMM_ALTSETTING_NCM;  } +EXPORT_SYMBOL_GPL(cdc_ncm_select_altsetting); -static struct sk_buff * -cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb) +static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)  { -	struct sk_buff *skb_out; -	u32 rem; -	u32 offset; -	u32 last_offset; -	u16 n = 0; -	u8 timeout = 0; +	int ret; -	/* if there is a remaining skb, it gets priority */ -	if (skb != NULL) -		swap(skb, ctx->tx_rem_skb); -	else -		timeout = 1; +	/* MBIM backwards compatible function? */ +	if (cdc_ncm_select_altsetting(intf) != CDC_NCM_COMM_ALTSETTING_NCM) +		return -ENODEV; + +	/* The NCM data altsetting is fixed */ +	ret = cdc_ncm_bind_common(dev, intf, CDC_NCM_DATA_ALTSETTING_NCM);  	/* -	 * +----------------+ -	 * | skb_out        | -	 * +----------------+ -	 *           ^ offset -	 *        ^ last_offset +	 * We should get an event when network connection is "connected" or +	 * "disconnected". Set network connection in "disconnected" state +	 * (carrier is OFF) during attach, so the IP network stack does not +	 * start IPv6 negotiation and more.  	 */ +	usbnet_link_change(dev, 0, 0); +	return ret; +} -	/* check if we are resuming an OUT skb */ -	if (ctx->tx_curr_skb != NULL) { -		/* pop variables */ -		skb_out = ctx->tx_curr_skb; -		offset = ctx->tx_curr_offset; -		last_offset = ctx->tx_curr_last_offset; -		n = ctx->tx_curr_frame_num; +static void cdc_ncm_align_tail(struct sk_buff *skb, size_t modulus, size_t remainder, size_t max) +{ +	size_t align = ALIGN(skb->len, modulus) - skb->len + remainder; + +	if (skb->len + align > max) +		align = max - skb->len; +	if (align && skb_tailroom(skb) >= align) +		memset(skb_put(skb, align), 0, align); +} + +/* return a pointer to a valid struct usb_cdc_ncm_ndp16 of type sign, possibly + * allocating a new one within skb + */ +static struct usb_cdc_ncm_ndp16 *cdc_ncm_ndp(struct cdc_ncm_ctx *ctx, struct sk_buff *skb, __le32 sign, size_t reserve) +{ +	struct usb_cdc_ncm_ndp16 *ndp16 = NULL; +	struct usb_cdc_ncm_nth16 *nth16 = (void *)skb->data; +	size_t ndpoffset = le16_to_cpu(nth16->wNdpIndex); + +	/* follow the chain of NDPs, looking for a match */ +	while (ndpoffset) { +		ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb->data + ndpoffset); +		if  (ndp16->dwSignature == sign) +			return ndp16; +		ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); +	} + +	/* align new NDP */ +	cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_max); + +	/* verify that there is room for the NDP and the datagram (reserve) */ +	if ((ctx->tx_max - skb->len - reserve) < ctx->max_ndp_size) +		return NULL; +	/* link to it */ +	if (ndp16) +		ndp16->wNextNdpIndex = cpu_to_le16(skb->len); +	else +		nth16->wNdpIndex = cpu_to_le16(skb->len); + +	/* push a new empty NDP */ +	ndp16 = (struct usb_cdc_ncm_ndp16 *)memset(skb_put(skb, ctx->max_ndp_size), 0, ctx->max_ndp_size); +	ndp16->dwSignature = sign; +	ndp16->wLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_ndp16) + sizeof(struct usb_cdc_ncm_dpe16)); +	return ndp16; +} + +struct sk_buff * +cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign) +{ +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	struct usb_cdc_ncm_nth16 *nth16; +	struct usb_cdc_ncm_ndp16 *ndp16; +	struct sk_buff *skb_out; +	u16 n = 0, index, ndplen; +	u8 ready2send = 0; + +	/* if there is a remaining skb, it gets priority */ +	if (skb != NULL) { +		swap(skb, ctx->tx_rem_skb); +		swap(sign, ctx->tx_rem_sign);  	} else { -		/* reset variables */ +		ready2send = 1; +	} + +	/* check if we are resuming an OUT skb */ +	skb_out = ctx->tx_curr_skb; + +	/* allocate a new OUT skb */ +	if (!skb_out) {  		skb_out = alloc_skb(ctx->tx_max, GFP_ATOMIC);  		if (skb_out == NULL) {  			if (skb != NULL) {  				dev_kfree_skb_any(skb); -				ctx->netdev->stats.tx_dropped++; +				dev->net->stats.tx_dropped++;  			}  			goto exit_no_skb;  		} +		/* fill out the initial 16-bit NTB header */ +		nth16 = (struct usb_cdc_ncm_nth16 *)memset(skb_put(skb_out, sizeof(struct usb_cdc_ncm_nth16)), 0, sizeof(struct usb_cdc_ncm_nth16)); +		nth16->dwSignature = cpu_to_le32(USB_CDC_NCM_NTH16_SIGN); +		nth16->wHeaderLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_nth16)); +		nth16->wSequence = cpu_to_le16(ctx->tx_seq++); -		/* make room for NTH and NDP */ -		offset = ALIGN(sizeof(struct usb_cdc_ncm_nth16), -					ctx->tx_ndp_modulus) + -					sizeof(struct usb_cdc_ncm_ndp16) + -					(ctx->tx_max_datagrams + 1) * -					sizeof(struct usb_cdc_ncm_dpe16); - -		/* store last valid offset before alignment */ -		last_offset = offset; -		/* align first Datagram offset correctly */ -		offset = ALIGN(offset, ctx->tx_modulus) + ctx->tx_remainder; -		/* zero buffer till the first IP datagram */ -		cdc_ncm_zero_fill(skb_out->data, 0, offset, offset); -		n = 0; +		/* count total number of frames in this NTB */  		ctx->tx_curr_frame_num = 0; -	} -	for (; n < ctx->tx_max_datagrams; n++) { -		/* check if end of transmit buffer is reached */ -		if (offset >= ctx->tx_max) -			break; - -		/* compute maximum buffer size */ -		rem = ctx->tx_max - offset; +		/* recent payload counter for this skb_out */ +		ctx->tx_curr_frame_payload = 0; +	} +	for (n = ctx->tx_curr_frame_num; n < ctx->tx_max_datagrams; n++) { +		/* send any remaining skb first */  		if (skb == NULL) {  			skb = ctx->tx_rem_skb; +			sign = ctx->tx_rem_sign;  			ctx->tx_rem_skb = NULL;  			/* check for end of skb */ @@ -697,53 +1070,60 @@ cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb)  				break;  		} -		if (skb->len > rem) { +		/* get the appropriate NDP for this skb */ +		ndp16 = cdc_ncm_ndp(ctx, skb_out, sign, skb->len + ctx->tx_modulus + ctx->tx_remainder); + +		/* align beginning of next frame */ +		cdc_ncm_align_tail(skb_out,  ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max); + +		/* check if we had enough room left for both NDP and frame */ +		if (!ndp16 || skb_out->len + skb->len > ctx->tx_max) {  			if (n == 0) {  				/* won't fit, MTU problem? */  				dev_kfree_skb_any(skb);  				skb = NULL; -				ctx->netdev->stats.tx_dropped++; +				dev->net->stats.tx_dropped++;  			} else {  				/* no room for skb - store for later */  				if (ctx->tx_rem_skb != NULL) {  					dev_kfree_skb_any(ctx->tx_rem_skb); -					ctx->netdev->stats.tx_dropped++; +					dev->net->stats.tx_dropped++;  				}  				ctx->tx_rem_skb = skb; +				ctx->tx_rem_sign = sign;  				skb = NULL; - -				/* loop one more time */ -				timeout = 1; +				ready2send = 1; +				ctx->tx_reason_ntb_full++;	/* count reason for transmitting */  			}  			break;  		} -		memcpy(((u8 *)skb_out->data) + offset, skb->data, skb->len); - -		ctx->tx_ncm.dpe16[n].wDatagramLength = cpu_to_le16(skb->len); -		ctx->tx_ncm.dpe16[n].wDatagramIndex = cpu_to_le16(offset); - -		/* update offset */ -		offset += skb->len; - -		/* store last valid offset before alignment */ -		last_offset = offset; - -		/* align offset correctly */ -		offset = ALIGN(offset, ctx->tx_modulus) + ctx->tx_remainder; +		/* calculate frame number withing this NDP */ +		ndplen = le16_to_cpu(ndp16->wLength); +		index = (ndplen - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16) - 1; -		/* zero padding */ -		cdc_ncm_zero_fill(skb_out->data, last_offset, offset, -								ctx->tx_max); +		/* OK, add this skb */ +		ndp16->dpe16[index].wDatagramLength = cpu_to_le16(skb->len); +		ndp16->dpe16[index].wDatagramIndex = cpu_to_le16(skb_out->len); +		ndp16->wLength = cpu_to_le16(ndplen + sizeof(struct usb_cdc_ncm_dpe16)); +		memcpy(skb_put(skb_out, skb->len), skb->data, skb->len); +		ctx->tx_curr_frame_payload += skb->len;	/* count real tx payload data */  		dev_kfree_skb_any(skb);  		skb = NULL; + +		/* send now if this NDP is full */ +		if (index >= CDC_NCM_DPT_DATAGRAMS_MAX) { +			ready2send = 1; +			ctx->tx_reason_ndp_full++;	/* count reason for transmitting */ +			break; +		}  	}  	/* free up any dangling skb */  	if (skb != NULL) {  		dev_kfree_skb_any(skb);  		skb = NULL; -		ctx->netdev->stats.tx_dropped++; +		dev->net->stats.tx_dropped++;  	}  	ctx->tx_curr_frame_num = n; @@ -752,139 +1132,113 @@ cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb)  		/* wait for more frames */  		/* push variables */  		ctx->tx_curr_skb = skb_out; -		ctx->tx_curr_offset = offset; -		ctx->tx_curr_last_offset = last_offset;  		goto exit_no_skb; -	} else if ((n < ctx->tx_max_datagrams) && (timeout == 0)) { +	} else if ((n < ctx->tx_max_datagrams) && (ready2send == 0) && (ctx->timer_interval > 0)) {  		/* wait for more frames */  		/* push variables */  		ctx->tx_curr_skb = skb_out; -		ctx->tx_curr_offset = offset; -		ctx->tx_curr_last_offset = last_offset;  		/* set the pending count */  		if (n < CDC_NCM_RESTART_TIMER_DATAGRAM_CNT) -			ctx->tx_timer_pending = 2; +			ctx->tx_timer_pending = CDC_NCM_TIMER_PENDING_CNT;  		goto exit_no_skb;  	} else { +		if (n == ctx->tx_max_datagrams) +			ctx->tx_reason_max_datagram++;	/* count reason for transmitting */  		/* frame goes out */  		/* variables will be reset at next call */  	} -	/* check for overflow */ -	if (last_offset > ctx->tx_max) -		last_offset = ctx->tx_max; - -	/* revert offset */ -	offset = last_offset; - -	/* -	 * If collected data size is less or equal CDC_NCM_MIN_TX_PKT bytes, -	 * we send buffers as it is. If we get more data, it would be more -	 * efficient for USB HS mobile device with DMA engine to receive a full -	 * size NTB, than canceling DMA transfer and receiving a short packet. +	/* If collected data size is less or equal ctx->min_tx_pkt +	 * bytes, we send buffers as it is. If we get more data, it +	 * would be more efficient for USB HS mobile device with DMA +	 * engine to receive a full size NTB, than canceling DMA +	 * transfer and receiving a short packet. +	 * +	 * This optimization support is pointless if we end up sending +	 * a ZLP after full sized NTBs.  	 */ -	if (offset > CDC_NCM_MIN_TX_PKT) -		offset = ctx->tx_max; +	if (!(dev->driver_info->flags & FLAG_SEND_ZLP) && +	    skb_out->len > ctx->min_tx_pkt) +		memset(skb_put(skb_out, ctx->tx_max - skb_out->len), 0, +		       ctx->tx_max - skb_out->len); +	else if (skb_out->len < ctx->tx_max && (skb_out->len % dev->maxpacket) == 0) +		*skb_put(skb_out, 1) = 0;	/* force short packet */ -	/* final zero padding */ -	cdc_ncm_zero_fill(skb_out->data, last_offset, offset, ctx->tx_max); +	/* set final frame length */ +	nth16 = (struct usb_cdc_ncm_nth16 *)skb_out->data; +	nth16->wBlockLength = cpu_to_le16(skb_out->len); -	/* store last offset */ -	last_offset = offset; - -	if ((last_offset < ctx->tx_max) && ((last_offset % -			le16_to_cpu(ctx->out_ep->desc.wMaxPacketSize)) == 0)) { -		/* force short packet */ -		*(((u8 *)skb_out->data) + last_offset) = 0; -		last_offset++; -	} - -	/* zero the rest of the DPEs plus the last NULL entry */ -	for (; n <= CDC_NCM_DPT_DATAGRAMS_MAX; n++) { -		ctx->tx_ncm.dpe16[n].wDatagramLength = 0; -		ctx->tx_ncm.dpe16[n].wDatagramIndex = 0; -	} - -	/* fill out 16-bit NTB header */ -	ctx->tx_ncm.nth16.dwSignature = cpu_to_le32(USB_CDC_NCM_NTH16_SIGN); -	ctx->tx_ncm.nth16.wHeaderLength = -					cpu_to_le16(sizeof(ctx->tx_ncm.nth16)); -	ctx->tx_ncm.nth16.wSequence = cpu_to_le16(ctx->tx_seq); -	ctx->tx_ncm.nth16.wBlockLength = cpu_to_le16(last_offset); -	ctx->tx_ncm.nth16.wFpIndex = ALIGN(sizeof(struct usb_cdc_ncm_nth16), -							ctx->tx_ndp_modulus); - -	memcpy(skb_out->data, &(ctx->tx_ncm.nth16), sizeof(ctx->tx_ncm.nth16)); -	ctx->tx_seq++; - -	/* fill out 16-bit NDP table */ -	ctx->tx_ncm.ndp16.dwSignature = -				cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN); -	rem = sizeof(ctx->tx_ncm.ndp16) + ((ctx->tx_curr_frame_num + 1) * -					sizeof(struct usb_cdc_ncm_dpe16)); -	ctx->tx_ncm.ndp16.wLength = cpu_to_le16(rem); -	ctx->tx_ncm.ndp16.wNextFpIndex = 0; /* reserved */ - -	memcpy(((u8 *)skb_out->data) + ctx->tx_ncm.nth16.wFpIndex, -						&(ctx->tx_ncm.ndp16), -						sizeof(ctx->tx_ncm.ndp16)); +	/* return skb */ +	ctx->tx_curr_skb = NULL; +	dev->net->stats.tx_packets += ctx->tx_curr_frame_num; -	memcpy(((u8 *)skb_out->data) + ctx->tx_ncm.nth16.wFpIndex + -					sizeof(ctx->tx_ncm.ndp16), -					&(ctx->tx_ncm.dpe16), -					(ctx->tx_curr_frame_num + 1) * -					sizeof(struct usb_cdc_ncm_dpe16)); +	/* keep private stats: framing overhead and number of NTBs */ +	ctx->tx_overhead += skb_out->len - ctx->tx_curr_frame_payload; +	ctx->tx_ntbs++; -	/* set frame length */ -	skb_put(skb_out, last_offset); +	/* usbnet has already counted all the framing overhead. +	 * Adjust the stats so that the tx_bytes counter show real +	 * payload data instead. +	 */ +	dev->net->stats.tx_bytes -= skb_out->len - ctx->tx_curr_frame_payload; -	/* return skb */ -	ctx->tx_curr_skb = NULL;  	return skb_out;  exit_no_skb: +	/* Start timer, if there is a remaining non-empty skb */ +	if (ctx->tx_curr_skb != NULL && n > 0) +		cdc_ncm_tx_timeout_start(ctx);  	return NULL;  } +EXPORT_SYMBOL_GPL(cdc_ncm_fill_tx_frame);  static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx)  {  	/* start timer, if not already started */ -	if (timer_pending(&ctx->tx_timer) == 0) { -		ctx->tx_timer.function = &cdc_ncm_tx_timeout; -		ctx->tx_timer.data = (unsigned long)ctx; -		ctx->tx_timer.expires = jiffies + ((HZ + 999) / 1000); -		add_timer(&ctx->tx_timer); -	} +	if (!(hrtimer_active(&ctx->tx_timer) || atomic_read(&ctx->stop))) +		hrtimer_start(&ctx->tx_timer, +				ktime_set(0, ctx->timer_interval), +				HRTIMER_MODE_REL);  } -static void cdc_ncm_tx_timeout(unsigned long arg) +static enum hrtimer_restart cdc_ncm_tx_timer_cb(struct hrtimer *timer)  { -	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)arg; -	u8 restart; +	struct cdc_ncm_ctx *ctx = +			container_of(timer, struct cdc_ncm_ctx, tx_timer); -	spin_lock(&ctx->mtx); -	if (ctx->tx_timer_pending != 0) { -		ctx->tx_timer_pending--; -		restart = 1; -	} else -		restart = 0; +	if (!atomic_read(&ctx->stop)) +		tasklet_schedule(&ctx->bh); +	return HRTIMER_NORESTART; +} -	spin_unlock(&ctx->mtx); +static void cdc_ncm_txpath_bh(unsigned long param) +{ +	struct usbnet *dev = (struct usbnet *)param; +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; -	if (restart) +	spin_lock_bh(&ctx->mtx); +	if (ctx->tx_timer_pending != 0) { +		ctx->tx_timer_pending--;  		cdc_ncm_tx_timeout_start(ctx); -	else if (ctx->netdev != NULL) -		usbnet_start_xmit(NULL, ctx->netdev); +		spin_unlock_bh(&ctx->mtx); +	} else if (dev->net != NULL) { +		ctx->tx_reason_timeout++;	/* count reason for transmitting */ +		spin_unlock_bh(&ctx->mtx); +		netif_tx_lock_bh(dev->net); +		usbnet_start_xmit(NULL, dev->net); +		netif_tx_unlock_bh(dev->net); +	} else { +		spin_unlock_bh(&ctx->mtx); +	}  } -static struct sk_buff * +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_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; -	u8 need_timer = 0;  	/*  	 * The Ethernet API we are using does not support transmitting @@ -896,18 +1250,9 @@ cdc_ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)  	if (ctx == NULL)  		goto error; -	spin_lock(&ctx->mtx); -	skb_out = cdc_ncm_fill_tx_frame(ctx, skb); -	if (ctx->tx_curr_skb != NULL) -		need_timer = 1; -	spin_unlock(&ctx->mtx); - -	/* Start timer, if there is a remaining skb */ -	if (need_timer) -		cdc_ncm_tx_timeout_start(ctx); - -	if (skb_out) -		dev->net->stats.tx_packets += ctx->tx_curr_frame_num; +	spin_lock_bh(&ctx->mtx); +	skb_out = cdc_ncm_fill_tx_frame(dev, skb, cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN)); +	spin_unlock_bh(&ctx->mtx);  	return skb_out;  error: @@ -916,159 +1261,197 @@ error:  	return NULL;  } +EXPORT_SYMBOL_GPL(cdc_ncm_tx_fixup); -static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in) +/* verify NTB header and return offset of first NDP, or negative error */ +int cdc_ncm_rx_verify_nth16(struct cdc_ncm_ctx *ctx, struct sk_buff *skb_in)  { -	struct sk_buff *skb; -	struct cdc_ncm_ctx *ctx; -	int sumlen; -	int actlen; -	int temp; -	int nframes; -	int x; -	int offset; +	struct usbnet *dev = netdev_priv(skb_in->dev); +	struct usb_cdc_ncm_nth16 *nth16; +	int len; +	int ret = -EINVAL; -	ctx = (struct cdc_ncm_ctx *)dev->data[0];  	if (ctx == NULL)  		goto error; -	actlen = skb_in->len; -	sumlen = CDC_NCM_NTB_MAX_SIZE_RX; - -	if (actlen < (sizeof(ctx->rx_ncm.nth16) + sizeof(ctx->rx_ncm.ndp16))) { -		pr_debug("frame too short\n"); +	if (skb_in->len < (sizeof(struct usb_cdc_ncm_nth16) + +					sizeof(struct usb_cdc_ncm_ndp16))) { +		netif_dbg(dev, rx_err, dev->net, "frame too short\n");  		goto error;  	} -	memcpy(&(ctx->rx_ncm.nth16), ((u8 *)skb_in->data), -						sizeof(ctx->rx_ncm.nth16)); +	nth16 = (struct usb_cdc_ncm_nth16 *)skb_in->data; -	if (le32_to_cpu(ctx->rx_ncm.nth16.dwSignature) != -	    USB_CDC_NCM_NTH16_SIGN) { -		pr_debug("invalid NTH16 signature <%u>\n", -			 le32_to_cpu(ctx->rx_ncm.nth16.dwSignature)); +	if (nth16->dwSignature != cpu_to_le32(USB_CDC_NCM_NTH16_SIGN)) { +		netif_dbg(dev, rx_err, dev->net, +			  "invalid NTH16 signature <%#010x>\n", +			  le32_to_cpu(nth16->dwSignature));  		goto error;  	} -	temp = le16_to_cpu(ctx->rx_ncm.nth16.wBlockLength); -	if (temp > sumlen) { -		pr_debug("unsupported NTB block length %u/%u\n", temp, sumlen); +	len = le16_to_cpu(nth16->wBlockLength); +	if (len > ctx->rx_max) { +		netif_dbg(dev, rx_err, dev->net, +			  "unsupported NTB block length %u/%u\n", len, +			  ctx->rx_max);  		goto error;  	} -	temp = le16_to_cpu(ctx->rx_ncm.nth16.wFpIndex); -	if ((temp + sizeof(ctx->rx_ncm.ndp16)) > actlen) { -		pr_debug("invalid DPT16 index\n"); -		goto error; +	if ((ctx->rx_seq + 1) != le16_to_cpu(nth16->wSequence) && +	    (ctx->rx_seq || le16_to_cpu(nth16->wSequence)) && +	    !((ctx->rx_seq == 0xffff) && !le16_to_cpu(nth16->wSequence))) { +		netif_dbg(dev, rx_err, dev->net, +			  "sequence number glitch prev=%d curr=%d\n", +			  ctx->rx_seq, le16_to_cpu(nth16->wSequence));  	} +	ctx->rx_seq = le16_to_cpu(nth16->wSequence); + +	ret = le16_to_cpu(nth16->wNdpIndex); +error: +	return ret; +} +EXPORT_SYMBOL_GPL(cdc_ncm_rx_verify_nth16); -	memcpy(&(ctx->rx_ncm.ndp16), ((u8 *)skb_in->data) + temp, -						sizeof(ctx->rx_ncm.ndp16)); +/* verify NDP header and return number of datagrams, or negative error */ +int cdc_ncm_rx_verify_ndp16(struct sk_buff *skb_in, int ndpoffset) +{ +	struct usbnet *dev = netdev_priv(skb_in->dev); +	struct usb_cdc_ncm_ndp16 *ndp16; +	int ret = -EINVAL; -	if (le32_to_cpu(ctx->rx_ncm.ndp16.dwSignature) != -	    USB_CDC_NCM_NDP16_NOCRC_SIGN) { -		pr_debug("invalid DPT16 signature <%u>\n", -			 le32_to_cpu(ctx->rx_ncm.ndp16.dwSignature)); +	if ((ndpoffset + sizeof(struct usb_cdc_ncm_ndp16)) > skb_in->len) { +		netif_dbg(dev, rx_err, dev->net, "invalid NDP offset  <%u>\n", +			  ndpoffset);  		goto error;  	} +	ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset); -	if (le16_to_cpu(ctx->rx_ncm.ndp16.wLength) < -	    USB_CDC_NCM_NDP16_LENGTH_MIN) { -		pr_debug("invalid DPT16 length <%u>\n", -			 le32_to_cpu(ctx->rx_ncm.ndp16.dwSignature)); +	if (le16_to_cpu(ndp16->wLength) < USB_CDC_NCM_NDP16_LENGTH_MIN) { +		netif_dbg(dev, rx_err, dev->net, "invalid DPT16 length <%u>\n", +			  le16_to_cpu(ndp16->wLength));  		goto error;  	} -	nframes = ((le16_to_cpu(ctx->rx_ncm.ndp16.wLength) - +	ret = ((le16_to_cpu(ndp16->wLength) -  					sizeof(struct usb_cdc_ncm_ndp16)) /  					sizeof(struct usb_cdc_ncm_dpe16)); -	nframes--; /* we process NDP entries except for the last one */ +	ret--; /* we process NDP entries except for the last one */ -	pr_debug("nframes = %u\n", nframes); +	if ((sizeof(struct usb_cdc_ncm_ndp16) + +	     ret * (sizeof(struct usb_cdc_ncm_dpe16))) > skb_in->len) { +		netif_dbg(dev, rx_err, dev->net, "Invalid nframes = %d\n", ret); +		ret = -EINVAL; +	} + +error: +	return ret; +} +EXPORT_SYMBOL_GPL(cdc_ncm_rx_verify_ndp16); -	temp += sizeof(ctx->rx_ncm.ndp16); +int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in) +{ +	struct sk_buff *skb; +	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; +	int len; +	int nframes; +	int x; +	int offset; +	struct usb_cdc_ncm_ndp16 *ndp16; +	struct usb_cdc_ncm_dpe16 *dpe16; +	int ndpoffset; +	int loopcount = 50; /* arbitrary max preventing infinite loop */ +	u32 payload = 0; + +	ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in); +	if (ndpoffset < 0) +		goto error; -	if ((temp + nframes * (sizeof(struct usb_cdc_ncm_dpe16))) > actlen) { -		pr_debug("Invalid nframes = %d\n", nframes); +next_ndp: +	nframes = cdc_ncm_rx_verify_ndp16(skb_in, ndpoffset); +	if (nframes < 0)  		goto error; -	} -	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; -	} +	ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset); -	memcpy(&(ctx->rx_ncm.dpe16), ((u8 *)skb_in->data) + temp, -				nframes * (sizeof(struct usb_cdc_ncm_dpe16))); +	if (ndp16->dwSignature != cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN)) { +		netif_dbg(dev, rx_err, dev->net, +			  "invalid DPT16 signature <%#010x>\n", +			  le32_to_cpu(ndp16->dwSignature)); +		goto err_ndp; +	} +	dpe16 = ndp16->dpe16; -	for (x = 0; x < nframes; x++) { -		offset = le16_to_cpu(ctx->rx_ncm.dpe16[x].wDatagramIndex); -		temp = le16_to_cpu(ctx->rx_ncm.dpe16[x].wDatagramLength); +	for (x = 0; x < nframes; x++, dpe16++) { +		offset = le16_to_cpu(dpe16->wDatagramIndex); +		len = le16_to_cpu(dpe16->wDatagramLength);  		/*  		 * CDC NCM ch. 3.7  		 * All entries after first NULL entry are to be ignored  		 */ -		if ((offset == 0) || (temp == 0)) { +		if ((offset == 0) || (len == 0)) {  			if (!x) -				goto error; /* empty NTB */ +				goto err_ndp; /* empty NTB */  			break;  		}  		/* sanity checking */ -		if (((offset + temp) > actlen) || -		    (temp > CDC_NCM_MAX_DATAGRAM_SIZE) || (temp < ETH_HLEN)) { -			pr_debug("invalid frame detected (ignored)" -				"offset[%u]=%u, length=%u, skb=%p\n", -							x, offset, temp, skb); +		if (((offset + len) > skb_in->len) || +				(len > ctx->rx_max) || (len < ETH_HLEN)) { +			netif_dbg(dev, rx_err, dev->net, +				  "invalid frame detected (ignored) offset[%u]=%u, length=%u, skb=%p\n", +				  x, offset, len, skb_in);  			if (!x) -				goto error; +				goto err_ndp;  			break;  		} else { -			skb = skb_clone(skb_in, GFP_ATOMIC); -			skb->len = temp; -			skb->data = ((u8 *)skb_in->data) + offset; -			skb_set_tail_pointer(skb, temp); +			/* create a fresh copy to reduce truesize */ +			skb = netdev_alloc_skb_ip_align(dev->net,  len); +			if (!skb) +				goto error; +			memcpy(skb_put(skb, len), skb_in->data + offset, len);  			usbnet_skb_return(dev, skb); +			payload += len;	/* count payload bytes in this NTB */  		}  	} +err_ndp: +	/* are there more NDPs to process? */ +	ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); +	if (ndpoffset && loopcount--) +		goto next_ndp; + +	/* update stats */ +	ctx->rx_overhead += skb_in->len - payload; +	ctx->rx_ntbs++; +  	return 1;  error:  	return 0;  } +EXPORT_SYMBOL_GPL(cdc_ncm_rx_fixup);  static void -cdc_ncm_speed_change(struct cdc_ncm_ctx *ctx, -		     struct connection_speed_change *data) +cdc_ncm_speed_change(struct usbnet *dev, +		     struct usb_cdc_speed_change *data)  { -	uint32_t rx_speed = le32_to_cpu(data->USBitRate); -	uint32_t tx_speed = le32_to_cpu(data->DSBitRate); +	uint32_t rx_speed = le32_to_cpu(data->DLBitRRate); +	uint32_t tx_speed = le32_to_cpu(data->ULBitRate);  	/*  	 * Currently the USB-NET API does not support reporting the actual  	 * device speed. Do print it instead.  	 */ -	if ((tx_speed != ctx->tx_speed) || (rx_speed != ctx->rx_speed)) { -		ctx->tx_speed = tx_speed; -		ctx->rx_speed = rx_speed; - -		if ((tx_speed > 1000000) && (rx_speed > 1000000)) { -			printk(KERN_INFO KBUILD_MODNAME -				": %s: %u mbit/s downlink " -				"%u mbit/s uplink\n", -				ctx->netdev->name, -				(unsigned int)(rx_speed / 1000000U), -				(unsigned int)(tx_speed / 1000000U)); -		} else { -			printk(KERN_INFO KBUILD_MODNAME -				": %s: %u kbit/s downlink " -				"%u kbit/s uplink\n", -				ctx->netdev->name, -				(unsigned int)(rx_speed / 1000U), -				(unsigned int)(tx_speed / 1000U)); -		} +	if ((tx_speed > 1000000) && (rx_speed > 1000000)) { +		netif_info(dev, link, dev->net, +			   "%u mbit/s downlink %u mbit/s uplink\n", +			   (unsigned int)(rx_speed / 1000000U), +			   (unsigned int)(tx_speed / 1000000U)); +	} else { +		netif_info(dev, link, dev->net, +			   "%u kbit/s downlink %u kbit/s uplink\n", +			   (unsigned int)(rx_speed / 1000U), +			   (unsigned int)(tx_speed / 1000U));  	}  } @@ -1084,8 +1467,8 @@ static void cdc_ncm_status(struct usbnet *dev, struct urb *urb)  	/* test for split data in 8-byte chunks */  	if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) { -		cdc_ncm_speed_change(ctx, -		      (struct connection_speed_change *)urb->transfer_buffer); +		cdc_ncm_speed_change(dev, +		      (struct usb_cdc_speed_change *)urb->transfer_buffer);  		return;  	} @@ -1098,115 +1481,135 @@ static void cdc_ncm_status(struct usbnet *dev, struct urb *urb)  		 * USB_CDC_NOTIFY_NETWORK_CONNECTION notification shall be  		 * sent by device after USB_CDC_NOTIFY_SPEED_CHANGE.  		 */ -		ctx->connected = event->wValue; - -		printk(KERN_INFO KBUILD_MODNAME ": %s: network connection:" -			" %sconnected\n", -			ctx->netdev->name, ctx->connected ? "" : "dis"); - -		if (ctx->connected) -			netif_carrier_on(dev->net); -		else { -			netif_carrier_off(dev->net); -			ctx->tx_speed = ctx->rx_speed = 0; -		} +		netif_info(dev, link, dev->net, +			   "network connection: %sconnected\n", +			   !!event->wValue ? "" : "dis"); +		usbnet_link_change(dev, !!event->wValue, 0);  		break;  	case USB_CDC_NOTIFY_SPEED_CHANGE: -		if (urb->actual_length < -		    (sizeof(*event) + sizeof(struct connection_speed_change))) +		if (urb->actual_length < (sizeof(*event) + +					sizeof(struct usb_cdc_speed_change)))  			set_bit(EVENT_STS_SPLIT, &dev->flags);  		else -			cdc_ncm_speed_change(ctx, -				(struct connection_speed_change *) &event[1]); +			cdc_ncm_speed_change(dev, +					     (struct usb_cdc_speed_change *)&event[1]);  		break;  	default: -		dev_err(&dev->udev->dev, "NCM: unexpected " -			"notification 0x%02x!\n", event->bNotificationType); +		dev_dbg(&dev->udev->dev, +			"NCM: unexpected notification 0x%02x!\n", +			event->bNotificationType);  		break;  	}  } -static int cdc_ncm_check_connect(struct usbnet *dev) -{ -	struct cdc_ncm_ctx *ctx; +static const struct driver_info cdc_ncm_info = { +	.description = "CDC NCM", +	.flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET, +	.bind = cdc_ncm_bind, +	.unbind = cdc_ncm_unbind, +	.manage_power = usbnet_manage_power, +	.status = cdc_ncm_status, +	.rx_fixup = cdc_ncm_rx_fixup, +	.tx_fixup = cdc_ncm_tx_fixup, +}; -	ctx = (struct cdc_ncm_ctx *)dev->data[0]; -	if (ctx == NULL) -		return 1;	/* disconnected */ +/* Same as cdc_ncm_info, but with FLAG_WWAN */ +static const struct driver_info wwan_info = { +	.description = "Mobile Broadband Network Device", +	.flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET +			| FLAG_WWAN, +	.bind = cdc_ncm_bind, +	.unbind = cdc_ncm_unbind, +	.manage_power = usbnet_manage_power, +	.status = cdc_ncm_status, +	.rx_fixup = cdc_ncm_rx_fixup, +	.tx_fixup = cdc_ncm_tx_fixup, +}; -	return !ctx->connected; -} +/* Same as wwan_info, but with FLAG_NOARP  */ +static const struct driver_info wwan_noarp_info = { +	.description = "Mobile Broadband Network Device (NO ARP)", +	.flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET +			| FLAG_WWAN | FLAG_NOARP, +	.bind = cdc_ncm_bind, +	.unbind = cdc_ncm_unbind, +	.manage_power = usbnet_manage_power, +	.status = cdc_ncm_status, +	.rx_fixup = cdc_ncm_rx_fixup, +	.tx_fixup = cdc_ncm_tx_fixup, +}; -static int -cdc_ncm_probe(struct usb_interface *udev, const struct usb_device_id *prod) -{ -	return usbnet_probe(udev, prod); -} +static const struct usb_device_id cdc_devs[] = { +	/* Ericsson MBM devices like F5521gw */ +	{ .match_flags = USB_DEVICE_ID_MATCH_INT_INFO +		| USB_DEVICE_ID_MATCH_VENDOR, +	  .idVendor = 0x0bdb, +	  .bInterfaceClass = USB_CLASS_COMM, +	  .bInterfaceSubClass = USB_CDC_SUBCLASS_NCM, +	  .bInterfaceProtocol = USB_CDC_PROTO_NONE, +	  .driver_info = (unsigned long) &wwan_info, +	}, -static void cdc_ncm_disconnect(struct usb_interface *intf) -{ -	struct usbnet *dev = usb_get_intfdata(intf); +	/* Dell branded MBM devices like DW5550 */ +	{ .match_flags = USB_DEVICE_ID_MATCH_INT_INFO +		| USB_DEVICE_ID_MATCH_VENDOR, +	  .idVendor = 0x413c, +	  .bInterfaceClass = USB_CLASS_COMM, +	  .bInterfaceSubClass = USB_CDC_SUBCLASS_NCM, +	  .bInterfaceProtocol = USB_CDC_PROTO_NONE, +	  .driver_info = (unsigned long) &wwan_info, +	}, -	if (dev == NULL) -		return;		/* already disconnected */ +	/* Toshiba branded MBM devices */ +	{ .match_flags = USB_DEVICE_ID_MATCH_INT_INFO +		| USB_DEVICE_ID_MATCH_VENDOR, +	  .idVendor = 0x0930, +	  .bInterfaceClass = USB_CLASS_COMM, +	  .bInterfaceSubClass = USB_CDC_SUBCLASS_NCM, +	  .bInterfaceProtocol = USB_CDC_PROTO_NONE, +	  .driver_info = (unsigned long) &wwan_info, +	}, -	usbnet_disconnect(intf); -} +	/* tag Huawei devices as wwan */ +	{ USB_VENDOR_AND_INTERFACE_INFO(0x12d1, +					USB_CLASS_COMM, +					USB_CDC_SUBCLASS_NCM, +					USB_CDC_PROTO_NONE), +	  .driver_info = (unsigned long)&wwan_info, +	}, -static int cdc_ncm_manage_power(struct usbnet *dev, int status) -{ -	dev->intf->needs_remote_wakeup = status; -	return 0; -} +	/* Infineon(now Intel) HSPA Modem platform */ +	{ USB_DEVICE_AND_INTERFACE_INFO(0x1519, 0x0443, +		USB_CLASS_COMM, +		USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), +	  .driver_info = (unsigned long)&wwan_noarp_info, +	}, -static const struct driver_info cdc_ncm_info = { -	.description = "CDC NCM", -	.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET, -	.bind = cdc_ncm_bind, -	.unbind = cdc_ncm_unbind, -	.check_connect = cdc_ncm_check_connect, -	.manage_power = cdc_ncm_manage_power, -	.status = cdc_ncm_status, -	.rx_fixup = cdc_ncm_rx_fixup, -	.tx_fixup = cdc_ncm_tx_fixup, +	/* Generic CDC-NCM devices */ +	{ USB_INTERFACE_INFO(USB_CLASS_COMM, +		USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), +		.driver_info = (unsigned long)&cdc_ncm_info, +	}, +	{ +	},  }; +MODULE_DEVICE_TABLE(usb, cdc_devs);  static struct usb_driver cdc_ncm_driver = {  	.name = "cdc_ncm",  	.id_table = cdc_devs, -	.probe = cdc_ncm_probe, -	.disconnect = cdc_ncm_disconnect, +	.probe = usbnet_probe, +	.disconnect = usbnet_disconnect,  	.suspend = usbnet_suspend,  	.resume = usbnet_resume, +	.reset_resume =	usbnet_resume,  	.supports_autosuspend = 1, +	.disable_hub_initiated_lpm = 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) -{ -	printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION "\n"); -	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_usb_driver(cdc_ncm_driver);  MODULE_AUTHOR("Hans Petter Selasky");  MODULE_DESCRIPTION("USB CDC NCM host driver");  | 
