| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | /* alternative VLAN for IP session 0 if not untagged */ | 
					
						
							|  |  |  | #define MBIM_IPS0_VID	4094
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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; | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | 	unsigned long _unused; | 
					
						
							|  |  |  | 	unsigned long flags; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* flags for the cdc_mbim_state.flags field */ | 
					
						
							|  |  |  | enum cdc_mbim_flags { | 
					
						
							|  |  |  | 	FLAG_IPS0_VLAN = 1 << 0,	/* IP session 0 is tagged  */ | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | static int cdc_mbim_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct usbnet *dev = netdev_priv(netdev); | 
					
						
							|  |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* creation of this VLAN is a request to tag IP session 0 */ | 
					
						
							|  |  |  | 	if (vid == MBIM_IPS0_VID) | 
					
						
							|  |  |  | 		info->flags |= FLAG_IPS0_VLAN; | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		if (vid >= 512)	/* we don't map these to MBIM session */ | 
					
						
							|  |  |  | 			return -EINVAL; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cdc_mbim_rx_kill_vid(struct net_device *netdev, __be16 proto, u16 vid) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct usbnet *dev = netdev_priv(netdev); | 
					
						
							|  |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* this is a request for an untagged IP session 0 */ | 
					
						
							|  |  |  | 	if (vid == MBIM_IPS0_VID) | 
					
						
							|  |  |  | 		info->flags &= ~FLAG_IPS0_VLAN; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct net_device_ops cdc_mbim_netdev_ops = { | 
					
						
							|  |  |  | 	.ndo_open             = usbnet_open, | 
					
						
							|  |  |  | 	.ndo_stop             = usbnet_stop, | 
					
						
							|  |  |  | 	.ndo_start_xmit       = usbnet_start_xmit, | 
					
						
							|  |  |  | 	.ndo_tx_timeout       = usbnet_tx_timeout, | 
					
						
							|  |  |  | 	.ndo_change_mtu       = usbnet_change_mtu, | 
					
						
							|  |  |  | 	.ndo_set_mac_address  = eth_mac_addr, | 
					
						
							|  |  |  | 	.ndo_validate_addr    = eth_validate_addr, | 
					
						
							|  |  |  | 	.ndo_vlan_rx_add_vid  = cdc_mbim_rx_add_vid, | 
					
						
							|  |  |  | 	.ndo_vlan_rx_kill_vid = cdc_mbim_rx_kill_vid, | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:15 +02:00
										 |  |  | /* Change the control interface altsetting and update the .driver_info
 | 
					
						
							|  |  |  |  * pointer if the matching entry after changing class codes points to | 
					
						
							|  |  |  |  * a different struct | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int cdc_mbim_set_ctrlalt(struct usbnet *dev, struct usb_interface *intf, u8 alt) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct usb_driver *driver = to_usb_driver(intf->dev.driver); | 
					
						
							|  |  |  | 	const struct usb_device_id *id; | 
					
						
							|  |  |  | 	struct driver_info *info; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = usb_set_interface(dev->udev, | 
					
						
							|  |  |  | 				intf->cur_altsetting->desc.bInterfaceNumber, | 
					
						
							|  |  |  | 				alt); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	id = usb_match_id(intf, driver->id_table); | 
					
						
							|  |  |  | 	if (!id) | 
					
						
							|  |  |  | 		return -ENODEV; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	info = (struct driver_info *)id->driver_info; | 
					
						
							|  |  |  | 	if (info != dev->driver_info) { | 
					
						
							|  |  |  | 		dev_dbg(&intf->dev, "driver_info updated to '%s'\n", | 
					
						
							|  |  |  | 			info->description); | 
					
						
							|  |  |  | 		dev->driver_info = info; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 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; | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:15 +02:00
										 |  |  | 	u8 data_altsetting = 1; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	struct cdc_mbim_state *info = (void *)&dev->data; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:15 +02:00
										 |  |  | 	/* should we change control altsetting on a NCM/MBIM function? */ | 
					
						
							|  |  |  | 	if (cdc_ncm_select_altsetting(intf) == CDC_NCM_COMM_ALTSETTING_MBIM) { | 
					
						
							|  |  |  | 		data_altsetting = CDC_NCM_DATA_ALTSETTING_MBIM; | 
					
						
							|  |  |  | 		ret = cdc_mbim_set_ctrlalt(dev, intf, CDC_NCM_COMM_ALTSETTING_MBIM); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			goto err; | 
					
						
							|  |  |  | 		ret = -ENODEV; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* we will hit this for NCM/MBIM functions if prefer_mbim is false */ | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	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 */ | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | 	dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_FILTER; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* monitor VLAN additions and removals */ | 
					
						
							|  |  |  | 	dev->net->netdev_ops = &cdc_mbim_netdev_ops; | 
					
						
							| 
									
										
										
										
											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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-09 14:45:00 +02:00
										 |  |  | /* verify that the ethernet protocol is IPv4 or IPv6 */ | 
					
						
							|  |  |  | static bool is_ip_proto(__be16 proto) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	switch (proto) { | 
					
						
							|  |  |  | 	case htons(ETH_P_IP): | 
					
						
							|  |  |  | 	case htons(ETH_P_IPV6): | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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; | 
					
						
							| 
									
										
										
										
											2014-05-09 14:45:00 +02:00
										 |  |  | 	bool is_ip; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 	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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-09 14:45:00 +02:00
										 |  |  | 		/* Some applications using e.g. packet sockets will
 | 
					
						
							|  |  |  | 		 * bypass the VLAN acceleration and create tagged | 
					
						
							|  |  |  | 		 * ethernet frames directly.  We primarily look for | 
					
						
							|  |  |  | 		 * the accelerated out-of-band tag, but fall back if | 
					
						
							|  |  |  | 		 * required | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		skb_reset_mac_header(skb); | 
					
						
							|  |  |  | 		if (vlan_get_tag(skb, &tci) < 0 && skb->len > VLAN_ETH_HLEN && | 
					
						
							|  |  |  | 		    __vlan_get_tag(skb, &tci) == 0) { | 
					
						
							|  |  |  | 			is_ip = is_ip_proto(vlan_eth_hdr(skb)->h_vlan_encapsulated_proto); | 
					
						
							|  |  |  | 			skb_pull(skb, VLAN_ETH_HLEN); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			is_ip = is_ip_proto(eth_hdr(skb)->h_proto); | 
					
						
							|  |  |  | 			skb_pull(skb, ETH_HLEN); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | 		/* Is IP session <0> tagged too? */ | 
					
						
							|  |  |  | 		if (info->flags & FLAG_IPS0_VLAN) { | 
					
						
							|  |  |  | 			/* drop all untagged packets */ | 
					
						
							|  |  |  | 			if (!tci) | 
					
						
							|  |  |  | 				goto error; | 
					
						
							|  |  |  | 			/* map MBIM_IPS0_VID to IPS<0> */ | 
					
						
							|  |  |  | 			if (tci == MBIM_IPS0_VID) | 
					
						
							|  |  |  | 				tci = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 		/* mapping VLANs to MBIM sessions:
 | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | 		 *   no tag     => IPS session <0> if !FLAG_IPS0_VLAN | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 		 *   1 - 255    => IPS session <vlanid> | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 		 *   256 - 511  => DSS session <vlanid - 256> | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | 		 *   512 - 4093 => unsupported, drop | 
					
						
							|  |  |  | 		 *   4094       => IPS session <0> if FLAG_IPS0_VLAN | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 		 */ | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:39 +00:00
										 |  |  | 		switch (tci & 0x0f00) { | 
					
						
							|  |  |  | 		case 0x0000: /* VLAN ID 0 - 255 */ | 
					
						
							| 
									
										
										
										
											2014-05-09 14:45:00 +02:00
										 |  |  | 			if (!is_ip) | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 				goto error; | 
					
						
							|  |  |  | 			c = (u8 *)&sign; | 
					
						
							|  |  |  | 			c[3] = tci; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		case 0x0100: /* VLAN ID 256 - 511 */ | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:13 +02:00
										 |  |  | 			if (is_ip) | 
					
						
							|  |  |  | 				goto error; | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 			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: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 */ | 
					
						
							| 
									
										
										
										
											2014-05-03 16:12:47 +02:00
										 |  |  | 	rcu_read_lock(); | 
					
						
							|  |  |  | 	if (tci) { | 
					
						
							| 
									
										
										
										
											2014-05-09 14:58:05 +08:00
										 |  |  | 		netdev = __vlan_find_dev_deep_rcu(dev->net, htons(ETH_P_8021Q), | 
					
						
							|  |  |  | 						  tci); | 
					
						
							| 
									
										
										
										
											2014-05-03 16:12:47 +02:00
										 |  |  | 		if (!netdev) { | 
					
						
							|  |  |  | 			rcu_read_unlock(); | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:10 +01:00
										 |  |  | 		netdev = dev->net; | 
					
						
							| 
									
										
										
										
											2014-05-03 16:12:47 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	dev_hold(netdev); | 
					
						
							|  |  |  | 	rcu_read_unlock(); | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:10 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	in6_dev = in6_dev_get(netdev); | 
					
						
							|  |  |  | 	if (!in6_dev) | 
					
						
							| 
									
										
										
										
											2014-05-03 16:12:47 +02:00
										 |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:10 +01:00
										 |  |  | 	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 */); | 
					
						
							| 
									
										
										
										
											2014-05-03 16:12:47 +02:00
										 |  |  | out: | 
					
						
							|  |  |  | 	dev_put(netdev); | 
					
						
							| 
									
										
										
										
											2013-10-31 15:56:10 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | 	if (tci < 256 || tci == MBIM_IPS0_VID) { /* IPS session? */ | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:40 +00:00
										 |  |  | 		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 */ | 
					
						
							| 
									
										
										
										
											2014-05-16 21:48:25 +02:00
										 |  |  | 	u32 payload = 0; | 
					
						
							| 
									
										
										
										
											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]; | 
					
						
							| 
									
										
										
										
											2014-05-11 10:47:12 +02:00
										 |  |  | 		/* tag IPS<0> packets too if MBIM_IPS0_VID exists */ | 
					
						
							|  |  |  | 		if (!tci && info->flags & FLAG_IPS0_VLAN) | 
					
						
							|  |  |  | 			tci = MBIM_IPS0_VID; | 
					
						
							| 
									
										
										
										
											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); | 
					
						
							| 
									
										
										
										
											2014-05-16 21:48:25 +02:00
										 |  |  | 			payload += len;	/* count payload bytes in this NTB */ | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | err_ndp: | 
					
						
							|  |  |  | 	/* are there more NDPs to process? */ | 
					
						
							|  |  |  | 	ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); | 
					
						
							|  |  |  | 	if (ndpoffset && loopcount--) | 
					
						
							|  |  |  | 		goto next_ndp; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-16 21:48:25 +02:00
										 |  |  | 	/* update stats */ | 
					
						
							|  |  |  | 	ctx->rx_overhead += skb_in->len - payload; | 
					
						
							|  |  |  | 	ctx->rx_ntbs++; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-22 10:56:36 +00:00
										 |  |  | 	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"); |