| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2012  Smith Micro Software, Inc. | 
					
						
							|  |  |  |  * Copyright (c) 2012  Bjørn Mork <bjorn@mork.no> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This driver is based on and reuse most of cdc_ncm, which is | 
					
						
							|  |  |  |  * Copyright (C) ST-Ericsson 2010-2012 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or | 
					
						
							|  |  |  |  * modify it under the terms of the GNU General Public License | 
					
						
							|  |  |  |  * version 2 as published by the Free Software Foundation. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/netdevice.h>
 | 
					
						
							|  |  |  | #include <linux/ethtool.h>
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | #include <linux/if_vlan.h>
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | #include <linux/ip.h>
 | 
					
						
							|  |  |  | #include <linux/mii.h>
 | 
					
						
							|  |  |  | #include <linux/usb.h>
 | 
					
						
							|  |  |  | #include <linux/usb/cdc.h>
 | 
					
						
							|  |  |  | #include <linux/usb/usbnet.h>
 | 
					
						
							|  |  |  | #include <linux/usb/cdc-wdm.h>
 | 
					
						
							|  |  |  | #include <linux/usb/cdc_ncm.h>
 | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:10 +01:00
										 |  |  | #include <net/ipv6.h>
 | 
					
						
							|  |  |  | #include <net/addrconf.h>
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | /* driver specific data - must match cdc_ncm usage */ | 
					
						
							|  |  |  | struct cdc_mbim_state { | 
					
						
							|  |  |  | 	struct cdc_ncm_ctx *ctx; | 
					
						
							|  |  |  | 	atomic_t pmcount; | 
					
						
							|  |  |  | 	struct usb_driver *subdriver; | 
					
						
							|  |  |  | 	struct usb_interface *control; | 
					
						
							|  |  |  | 	struct usb_interface *data; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* using a counter to merge subdriver requests with our own into a combined state */ | 
					
						
							|  |  |  | static int cdc_mbim_manage_power(struct usbnet *dev, int on) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 	int rv = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, atomic_read(&info->pmcount), on); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if ((on && atomic_add_return(1, &info->pmcount) == 1) || (!on && atomic_dec_and_test(&info->pmcount))) { | 
					
						
							|  |  |  | 		/* need autopm_get/put here to ensure the usbcore sees the new value */ | 
					
						
							|  |  |  | 		rv = usb_autopm_get_interface(dev->intf); | 
					
						
							|  |  |  | 		dev->intf->needs_remote_wakeup = on; | 
					
						
							| 
									
										
										
										
											2013-11-01 14:18:52 +01:00
										 |  |  | 		if (!rv) | 
					
						
							|  |  |  | 			usb_autopm_put_interface(dev->intf); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2013-11-01 14:18:52 +01:00
										 |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct usbnet *dev = usb_get_intfdata(intf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* can be called while disconnecting */ | 
					
						
							|  |  |  | 	if (!dev) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return cdc_mbim_manage_power(dev, status); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cdc_ncm_ctx *ctx; | 
					
						
							|  |  |  | 	struct usb_driver *subdriver = ERR_PTR(-ENODEV); | 
					
						
							|  |  |  | 	int ret = -ENODEV; | 
					
						
							| 
									
										
										
										
											2013-03-14 01:05:13 +00:00
										 |  |  | 	u8 data_altsetting = cdc_ncm_select_altsetting(dev, intf); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Probably NCM, defer for cdc_ncm_bind */ | 
					
						
							|  |  |  | 	if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = cdc_ncm_bind_common(dev, intf, data_altsetting); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ctx = info->ctx; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* The MBIM descriptor and the status endpoint are required */ | 
					
						
							|  |  |  | 	if (ctx->mbim_desc && dev->status) | 
					
						
							|  |  |  | 		subdriver = usb_cdc_wdm_register(ctx->control, | 
					
						
							|  |  |  | 						 &dev->status->desc, | 
					
						
							|  |  |  | 						 le16_to_cpu(ctx->mbim_desc->wMaxControlMessage), | 
					
						
							|  |  |  | 						 cdc_mbim_wdm_manage_power); | 
					
						
							|  |  |  | 	if (IS_ERR(subdriver)) { | 
					
						
							|  |  |  | 		ret = PTR_ERR(subdriver); | 
					
						
							|  |  |  | 		cdc_ncm_unbind(dev, intf); | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* can't let usbnet use the interrupt endpoint */ | 
					
						
							|  |  |  | 	dev->status = NULL; | 
					
						
							|  |  |  | 	info->subdriver = subdriver; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* MBIM cannot do ARP */ | 
					
						
							|  |  |  | 	dev->net->flags |= IFF_NOARP; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* no need to put the VLAN tci in the packet headers */ | 
					
						
							| 
									
										
										
										
											2013-04-19 02:04:27 +00:00
										 |  |  | 	dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | err: | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void cdc_mbim_unbind(struct usbnet *dev, struct usb_interface *intf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 	struct cdc_ncm_ctx *ctx = info->ctx; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* disconnect subdriver from control interface */ | 
					
						
							|  |  |  | 	if (info->subdriver && info->subdriver->disconnect) | 
					
						
							|  |  |  | 		info->subdriver->disconnect(ctx->control); | 
					
						
							|  |  |  | 	info->subdriver = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* let NCM unbind clean up both control and data interface */ | 
					
						
							|  |  |  | 	cdc_ncm_unbind(dev, intf); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct sk_buff *skb_out; | 
					
						
							|  |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 	struct cdc_ncm_ctx *ctx = info->ctx; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 	__le32 sign = cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN); | 
					
						
							|  |  |  | 	u16 tci = 0; | 
					
						
							|  |  |  | 	u8 *c; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (!ctx) | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (skb) { | 
					
						
							| 
									
										
										
										
											2013-04-16 00:17:07 +00:00
										 |  |  | 		if (skb->len <= ETH_HLEN) | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 			goto error; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 		/* mapping VLANs to MBIM sessions:
 | 
					
						
							|  |  |  | 		 *   no tag     => IPS session <0> | 
					
						
							|  |  |  | 		 *   1 - 255    => IPS session <vlanid> | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 		 *   256 - 511  => DSS session <vlanid - 256> | 
					
						
							|  |  |  | 		 *   512 - 4095 => unsupported, drop | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 		 */ | 
					
						
							|  |  |  | 		vlan_get_tag(skb, &tci); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch (tci & 0x0f00) { | 
					
						
							|  |  |  | 		case 0x0000: /* VLAN ID 0 - 255 */ | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 			/* verify that datagram is IPv4 or IPv6 */ | 
					
						
							|  |  |  | 			skb_reset_mac_header(skb); | 
					
						
							|  |  |  | 			switch (eth_hdr(skb)->h_proto) { | 
					
						
							|  |  |  | 			case htons(ETH_P_IP): | 
					
						
							|  |  |  | 			case htons(ETH_P_IPV6): | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				goto error; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			c = (u8 *)&sign; | 
					
						
							|  |  |  | 			c[3] = tci; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		case 0x0100: /* VLAN ID 256 - 511 */ | 
					
						
							|  |  |  | 			sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 			c = (u8 *)&sign; | 
					
						
							|  |  |  | 			c[3] = tci; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			netif_err(dev, tx_err, dev->net, | 
					
						
							|  |  |  | 				  "unsupported tci=0x%04x\n", tci); | 
					
						
							|  |  |  | 			goto error; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 		skb_pull(skb, ETH_HLEN); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	spin_lock_bh(&ctx->mtx); | 
					
						
							| 
									
										
										
										
											2013-11-01 11:16:42 +01:00
										 |  |  | 	skb_out = cdc_ncm_fill_tx_frame(dev, skb, sign); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	spin_unlock_bh(&ctx->mtx); | 
					
						
							|  |  |  | 	return skb_out; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | error: | 
					
						
							|  |  |  | 	if (skb) | 
					
						
							|  |  |  | 		dev_kfree_skb_any(skb); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:10 +01:00
										 |  |  | /* Some devices are known to send Neigbor Solicitation messages and
 | 
					
						
							|  |  |  |  * require Neigbor Advertisement replies.  The IPv6 core will not | 
					
						
							|  |  |  |  * respond since IFF_NOARP is set, so we must handle them ourselves. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void do_neigh_solicit(struct usbnet *dev, u8 *buf, u16 tci) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ipv6hdr *iph = (void *)buf; | 
					
						
							|  |  |  | 	struct nd_msg *msg = (void *)(iph + 1); | 
					
						
							|  |  |  | 	struct net_device *netdev; | 
					
						
							|  |  |  | 	struct inet6_dev *in6_dev; | 
					
						
							|  |  |  | 	bool is_router; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* we'll only respond to requests from unicast addresses to
 | 
					
						
							|  |  |  | 	 * our solicited node addresses. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	if (!ipv6_addr_is_solict_mult(&iph->daddr) || | 
					
						
							|  |  |  | 	    !(ipv6_addr_type(&iph->saddr) & IPV6_ADDR_UNICAST)) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* need to send the NA on the VLAN dev, if any */ | 
					
						
							|  |  |  | 	if (tci) | 
					
						
							|  |  |  | 		netdev = __vlan_find_dev_deep(dev->net, htons(ETH_P_8021Q), | 
					
						
							|  |  |  | 					      tci); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		netdev = dev->net; | 
					
						
							|  |  |  | 	if (!netdev) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	in6_dev = in6_dev_get(netdev); | 
					
						
							|  |  |  | 	if (!in6_dev) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	is_router = !!in6_dev->cnf.forwarding; | 
					
						
							|  |  |  | 	in6_dev_put(in6_dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* ipv6_stub != NULL if in6_dev_get returned an inet6_dev */ | 
					
						
							|  |  |  | 	ipv6_stub->ndisc_send_na(netdev, NULL, &iph->saddr, &msg->target, | 
					
						
							|  |  |  | 				 is_router /* router */, | 
					
						
							|  |  |  | 				 true /* solicited */, | 
					
						
							|  |  |  | 				 false /* override */, | 
					
						
							|  |  |  | 				 true /* inc_opt */); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool is_neigh_solicit(u8 *buf, size_t len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ipv6hdr *iph = (void *)buf; | 
					
						
							|  |  |  | 	struct nd_msg *msg = (void *)(iph + 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return (len >= sizeof(struct ipv6hdr) + sizeof(struct nd_msg) && | 
					
						
							|  |  |  | 		iph->nexthdr == IPPROTO_ICMPV6 && | 
					
						
							|  |  |  | 		msg->icmph.icmp6_code == 0 && | 
					
						
							|  |  |  | 		msg->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci) | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 	__be16 proto = htons(ETH_P_802_3); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	struct sk_buff *skb = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 	if (tci < 256) { /* IPS session? */ | 
					
						
							|  |  |  | 		if (len < sizeof(struct iphdr)) | 
					
						
							|  |  |  | 			goto err; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 		switch (*buf & 0xf0) { | 
					
						
							|  |  |  | 		case 0x40: | 
					
						
							|  |  |  | 			proto = htons(ETH_P_IP); | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		case 0x60: | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:10 +01:00
										 |  |  | 			if (is_neigh_solicit(buf, len)) | 
					
						
							|  |  |  | 				do_neigh_solicit(dev, buf, tci); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 			proto = htons(ETH_P_IPV6); | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			goto err; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	skb = netdev_alloc_skb_ip_align(dev->net,  len + ETH_HLEN); | 
					
						
							|  |  |  | 	if (!skb) | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* add an ethernet header */ | 
					
						
							|  |  |  | 	skb_put(skb, ETH_HLEN); | 
					
						
							|  |  |  | 	skb_reset_mac_header(skb); | 
					
						
							|  |  |  | 	eth_hdr(skb)->h_proto = proto; | 
					
						
							|  |  |  | 	memset(eth_hdr(skb)->h_source, 0, ETH_ALEN); | 
					
						
							|  |  |  | 	memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* add datagram */ | 
					
						
							|  |  |  | 	memcpy(skb_put(skb, len), buf, len); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* map MBIM session to VLAN */ | 
					
						
							|  |  |  | 	if (tci) | 
					
						
							| 
									
										
										
										
											2013-04-19 02:04:30 +00:00
										 |  |  | 		vlan_put_tag(skb, htons(ETH_P_8021Q), tci); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | err: | 
					
						
							|  |  |  | 	return skb; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct sk_buff *skb; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 	struct cdc_ncm_ctx *ctx = info->ctx; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	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 */ | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 	u8 *c; | 
					
						
							|  |  |  | 	u16 tci; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in); | 
					
						
							|  |  |  | 	if (ndpoffset < 0) | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | next_ndp: | 
					
						
							|  |  |  | 	nframes = cdc_ncm_rx_verify_ndp16(skb_in, ndpoffset); | 
					
						
							|  |  |  | 	if (nframes < 0) | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 	switch (ndp16->dwSignature & cpu_to_le32(0x00ffffff)) { | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	case cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN): | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 		c = (u8 *)&ndp16->dwSignature; | 
					
						
							|  |  |  | 		tci = c[3]; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 		break; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 	case cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN): | 
					
						
							|  |  |  | 		c = (u8 *)&ndp16->dwSignature; | 
					
						
							|  |  |  | 		tci = c[3] + 256; | 
					
						
							|  |  |  | 		break; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		netif_dbg(dev, rx_err, dev->net, | 
					
						
							|  |  |  | 			  "unsupported NDP signature <0x%08x>\n", | 
					
						
							|  |  |  | 			  le32_to_cpu(ndp16->dwSignature)); | 
					
						
							|  |  |  | 		goto err_ndp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dpe16 = ndp16->dpe16; | 
					
						
							|  |  |  | 	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) || (len == 0)) { | 
					
						
							|  |  |  | 			if (!x) | 
					
						
							|  |  |  | 				goto err_ndp; /* empty NTB */ | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* sanity checking */ | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 		if (((offset + len) > skb_in->len) || (len > ctx->rx_max)) { | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 			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 err_ndp; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 			skb = cdc_mbim_process_dgram(dev, skb_in->data + offset, len, tci); | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 			if (!skb) | 
					
						
							|  |  |  | 				goto error; | 
					
						
							|  |  |  | 			usbnet_skb_return(dev, skb); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | err_ndp: | 
					
						
							|  |  |  | 	/* are there more NDPs to process? */ | 
					
						
							|  |  |  | 	ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); | 
					
						
							|  |  |  | 	if (ndpoffset && loopcount--) | 
					
						
							|  |  |  | 		goto next_ndp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 1; | 
					
						
							|  |  |  | error: | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2013-11-01 14:18:56 +01:00
										 |  |  | 	int ret = -ENODEV; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	struct usbnet *dev = usb_get_intfdata(intf); | 
					
						
							|  |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 	struct cdc_ncm_ctx *ctx = info->ctx; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-01 14:18:56 +01:00
										 |  |  | 	if (!ctx) | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 		goto error; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-03-15 12:08:56 +08:00
										 |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Both usbnet_suspend() and subdriver->suspend() MUST return 0 | 
					
						
							|  |  |  | 	 * in system sleep context, otherwise, the resume callback has | 
					
						
							|  |  |  | 	 * to recover device from previous suspend failure. | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	ret = usbnet_suspend(intf, message); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (intf == ctx->control && info->subdriver && info->subdriver->suspend) | 
					
						
							|  |  |  | 		ret = info->subdriver->suspend(intf, message); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		usbnet_resume(intf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | error: | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cdc_mbim_resume(struct usb_interface *intf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int  ret = 0; | 
					
						
							|  |  |  | 	struct usbnet *dev = usb_get_intfdata(intf); | 
					
						
							|  |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 	struct cdc_ncm_ctx *ctx = info->ctx; | 
					
						
							|  |  |  | 	bool callsub = (intf == ctx->control && info->subdriver && info->subdriver->resume); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (callsub) | 
					
						
							|  |  |  | 		ret = info->subdriver->resume(intf); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 	ret = usbnet_resume(intf); | 
					
						
							| 
									
										
										
										
											2013-11-01 14:18:55 +01:00
										 |  |  | 	if (ret < 0 && callsub) | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 		info->subdriver->suspend(intf, PMSG_SUSPEND); | 
					
						
							|  |  |  | err: | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct driver_info cdc_mbim_info = { | 
					
						
							| 
									
										
										
										
											2013-01-23 00:57:02 +00:00
										 |  |  | 	.description = "CDC MBIM", | 
					
						
							|  |  |  | 	.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, | 
					
						
							|  |  |  | 	.bind = cdc_mbim_bind, | 
					
						
							|  |  |  | 	.unbind = cdc_mbim_unbind, | 
					
						
							|  |  |  | 	.manage_power = cdc_mbim_manage_power, | 
					
						
							|  |  |  | 	.rx_fixup = cdc_mbim_rx_fixup, | 
					
						
							|  |  |  | 	.tx_fixup = cdc_mbim_tx_fixup, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* MBIM and NCM devices should not need a ZLP after NTBs with
 | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:11 +01:00
										 |  |  |  * dwNtbOutMaxSize length. Nevertheless, a number of devices from | 
					
						
							|  |  |  |  * different vendor IDs will fail unless we send ZLPs, forcing us | 
					
						
							|  |  |  |  * to make this the default. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This default may cause a performance penalty for spec conforming | 
					
						
							|  |  |  |  * devices wanting to take advantage of optimizations possible without | 
					
						
							|  |  |  |  * ZLPs.  A whitelist is added in an attempt to avoid this for devices | 
					
						
							|  |  |  |  * known to conform to the MBIM specification. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * All known devices supporting NCM compatibility mode are also | 
					
						
							|  |  |  |  * conforming to the NCM and MBIM specifications. For this reason, the | 
					
						
							|  |  |  |  * NCM subclass entry is also in the ZLP whitelist. | 
					
						
							| 
									
										
										
										
											2013-01-23 00:57:02 +00:00
										 |  |  |  */ | 
					
						
							|  |  |  | static const struct driver_info cdc_mbim_info_zlp = { | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	.description = "CDC MBIM", | 
					
						
							| 
									
										
										
										
											2013-01-21 05:50:39 +00:00
										 |  |  | 	.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN | FLAG_SEND_ZLP, | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	.bind = cdc_mbim_bind, | 
					
						
							|  |  |  | 	.unbind = cdc_mbim_unbind, | 
					
						
							|  |  |  | 	.manage_power = cdc_mbim_manage_power, | 
					
						
							|  |  |  | 	.rx_fixup = cdc_mbim_rx_fixup, | 
					
						
							|  |  |  | 	.tx_fixup = cdc_mbim_tx_fixup, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct usb_device_id mbim_devs[] = { | 
					
						
							|  |  |  | 	/* This duplicate NCM entry is intentional. MBIM devices can
 | 
					
						
							|  |  |  | 	 * be disguised as NCM by default, and this is necessary to | 
					
						
							|  |  |  | 	 * allow us to bind the correct driver_info to such devices. | 
					
						
							|  |  |  | 	 * | 
					
						
							|  |  |  | 	 * bind() will sort out this for us, selecting the correct | 
					
						
							|  |  |  | 	 * entry and reject the other | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), | 
					
						
							|  |  |  | 	  .driver_info = (unsigned long)&cdc_mbim_info, | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:11 +01:00
										 |  |  | 	/* ZLP conformance whitelist: All Ericsson MBIM devices */ | 
					
						
							|  |  |  | 	{ USB_VENDOR_AND_INTERFACE_INFO(0x0bdb, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), | 
					
						
							|  |  |  | 	  .driver_info = (unsigned long)&cdc_mbim_info, | 
					
						
							| 
									
										
										
										
											2013-08-25 16:02:23 -06:00
										 |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:11 +01:00
										 |  |  | 	/* default entry */ | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:11 +01:00
										 |  |  | 	  .driver_info = (unsigned long)&cdc_mbim_info_zlp, | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | MODULE_DEVICE_TABLE(usb, mbim_devs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct usb_driver cdc_mbim_driver = { | 
					
						
							|  |  |  | 	.name = "cdc_mbim", | 
					
						
							|  |  |  | 	.id_table = mbim_devs, | 
					
						
							|  |  |  | 	.probe = usbnet_probe, | 
					
						
							|  |  |  | 	.disconnect = usbnet_disconnect, | 
					
						
							|  |  |  | 	.suspend = cdc_mbim_suspend, | 
					
						
							|  |  |  | 	.resume = cdc_mbim_resume, | 
					
						
							|  |  |  | 	.reset_resume =	cdc_mbim_resume, | 
					
						
							|  |  |  | 	.supports_autosuspend = 1, | 
					
						
							|  |  |  | 	.disable_hub_initiated_lpm = 1, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | module_usb_driver(cdc_mbim_driver); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_AUTHOR("Greg Suarez <gsuarez@smithmicro.com>"); | 
					
						
							|  |  |  | MODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>"); | 
					
						
							|  |  |  | MODULE_DESCRIPTION("USB CDC MBIM host driver"); | 
					
						
							|  |  |  | MODULE_LICENSE("GPL"); |