diff options
Diffstat (limited to 'drivers/net/usb/cdc_ether.c')
| -rw-r--r-- | drivers/net/usb/cdc_ether.c | 298 | 
1 files changed, 222 insertions, 76 deletions
diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c index b3fe0de4046..2a32d9167d3 100644 --- a/drivers/net/usb/cdc_ether.c +++ b/drivers/net/usb/cdc_ether.c @@ -14,15 +14,13 @@   * GNU General Public License for more details.   *   * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * along with this program; if not, see <http://www.gnu.org/licenses/>.   */  // #define	DEBUG			// error path messages, extra info  // #define	VERBOSE			// more; success messages  #include <linux/module.h> -#include <linux/init.h>  #include <linux/netdevice.h>  #include <linux/etherdevice.h>  #include <linux/ethtool.h> @@ -33,7 +31,7 @@  #include <linux/usb/usbnet.h> -#if defined(CONFIG_USB_NET_RNDIS_HOST) || defined(CONFIG_USB_NET_RNDIS_HOST_MODULE) +#if IS_ENABLED(CONFIG_USB_NET_RNDIS_HOST)  static int is_rndis(struct usb_interface_descriptor *desc)  { @@ -69,8 +67,7 @@ static const u8 mbm_guid[16] = {  	0xa6, 0x07, 0xc0, 0xff, 0xcb, 0x7e, 0x39, 0x2a,  }; -/* - * probes control interface, claims data interface, collects the bulk +/* probes control interface, claims data interface, collects the bulk   * endpoints, activates data interface (if needed), maybe sets MTU.   * all pure cdc, except for certain firmware workarounds, and knowing   * that rndis uses one different rule. @@ -83,11 +80,12 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  	struct cdc_state		*info = (void *) &dev->data;  	int				status;  	int				rndis; +	bool				android_rndis_quirk = false;  	struct usb_driver		*driver = driver_of(intf);  	struct usb_cdc_mdlm_desc	*desc = NULL;  	struct usb_cdc_mdlm_detail_desc *detail = NULL; -	if (sizeof dev->data < sizeof *info) +	if (sizeof(dev->data) < sizeof(*info))  		return -EDOM;  	/* expect strict spec conformance for the descriptors, but @@ -99,9 +97,7 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  		 */  		buf = dev->udev->actconfig->extra;  		len = dev->udev->actconfig->extralen; -		if (len) -			dev_dbg(&intf->dev, -				"CDC descriptors on config\n"); +		dev_dbg(&intf->dev, "CDC descriptors on config\n");  	}  	/* Maybe CDC descriptors are after the endpoint?  This bug has @@ -127,10 +123,10 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  		 is_activesync(&intf->cur_altsetting->desc) ||  		 is_wireless_rndis(&intf->cur_altsetting->desc)); -	memset(info, 0, sizeof *info); +	memset(info, 0, sizeof(*info));  	info->control = intf;  	while (len > 3) { -		if (buf [1] != USB_DT_CS_INTERFACE) +		if (buf[1] != USB_DT_CS_INTERFACE)  			goto next_desc;  		/* use bDescriptorSubType to identify the CDC descriptors. @@ -140,14 +136,14 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  		 * in favor of a complicated OID-based RPC scheme doing what  		 * CDC Ethernet achieves with a simple descriptor.  		 */ -		switch (buf [2]) { +		switch (buf[2]) {  		case USB_CDC_HEADER_TYPE:  			if (info->header) {  				dev_dbg(&intf->dev, "extra CDC header\n");  				goto bad_desc;  			}  			info->header = (void *) buf; -			if (info->header->bLength != sizeof *info->header) { +			if (info->header->bLength != sizeof(*info->header)) {  				dev_dbg(&intf->dev, "CDC header len %u\n",  					info->header->bLength);  				goto bad_desc; @@ -176,7 +172,7 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  				goto bad_desc;  			}  			info->u = (void *) buf; -			if (info->u->bLength != sizeof *info->u) { +			if (info->u->bLength != sizeof(*info->u)) {  				dev_dbg(&intf->dev, "CDC union len %u\n",  					info->u->bLength);  				goto bad_desc; @@ -197,6 +193,11 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  					info->control,  					info->u->bSlaveInterface0,  					info->data); +				/* fall back to hard-wiring for RNDIS */ +				if (rndis) { +					android_rndis_quirk = true; +					goto next_desc; +				}  				goto bad_desc;  			}  			if (info->control != intf) { @@ -211,6 +212,10 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  					goto bad_desc;  			} +			/* some devices merge these - skip class check */ +			if (info->control == info->data) +				goto next_desc; +  			/* a data interface altsetting does the real i/o */  			d = &info->data->cur_altsetting->desc;  			if (d->bInterfaceClass != USB_CLASS_CDC_DATA) { @@ -225,7 +230,7 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  				goto bad_desc;  			}  			info->ether = (void *) buf; -			if (info->ether->bLength != sizeof *info->ether) { +			if (info->ether->bLength != sizeof(*info->ether)) {  				dev_dbg(&intf->dev, "CDC ether len %u\n",  					info->ether->bLength);  				goto bad_desc; @@ -266,18 +271,22 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  			break;  		}  next_desc: -		len -= buf [0];	/* bLength */ -		buf += buf [0]; +		len -= buf[0];	/* bLength */ +		buf += buf[0];  	}  	/* Microsoft ActiveSync based and some regular RNDIS devices lack the  	 * CDC descriptors, so we'll hard-wire the interfaces and not check  	 * for descriptors. +	 * +	 * Some Android RNDIS devices have a CDC Union descriptor pointing +	 * to non-existing interfaces.  Ignore that and attempt the same +	 * hard-wired 0 and 1 interfaces.  	 */ -	if (rndis && !info->u) { +	if (rndis && (!info->u || android_rndis_quirk)) {  		info->control = usb_ifnum_to_if(dev->udev, 0);  		info->data = usb_ifnum_to_if(dev->udev, 1); -		if (!info->control || !info->data) { +		if (!info->control || !info->data || info->control != intf) {  			dev_dbg(&intf->dev,  				"rndis: master #0/%p slave #1/%p\n",  				info->control, @@ -296,19 +305,23 @@ next_desc:  	/* claim data interface and set it up ... with side effects.  	 * network traffic can't flow until an altsetting is enabled.  	 */ -	status = usb_driver_claim_interface(driver, info->data, dev); -	if (status < 0) -		return status; +	if (info->data != info->control) { +		status = usb_driver_claim_interface(driver, info->data, dev); +		if (status < 0) +			return status; +	}  	status = usbnet_get_endpoints(dev, info->data);  	if (status < 0) {  		/* ensure immediate exit from usbnet_disconnect */  		usb_set_intfdata(info->data, NULL); -		usb_driver_release_interface(driver, info->data); +		if (info->data != info->control) +			usb_driver_release_interface(driver, info->data);  		return status;  	}  	/* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */ -	dev->status = NULL; +	if (info->data != info->control) +		dev->status = NULL;  	if (info->control->cur_altsetting->desc.bNumEndpoints == 1) {  		struct usb_endpoint_descriptor	*desc; @@ -328,6 +341,22 @@ next_desc:  		usb_driver_release_interface(driver, info->data);  		return -ENODEV;  	} + +	/* Some devices don't initialise properly. In particular +	 * the packet filter is not reset. There are devices that +	 * don't do reset all the way. So the packet filter should +	 * be set to a sane initial value. +	 */ +	usb_control_msg(dev->udev, +			usb_sndctrlpipe(dev->udev, 0), +			USB_CDC_SET_ETHERNET_PACKET_FILTER, +			USB_TYPE_CLASS | USB_RECIP_INTERFACE, +			USB_CDC_PACKET_TYPE_ALL_MULTICAST | USB_CDC_PACKET_TYPE_DIRECTED | USB_CDC_PACKET_TYPE_BROADCAST, +			intf->cur_altsetting->desc.bInterfaceNumber, +			NULL, +			0, +			USB_CTRL_SET_TIMEOUT +		);  	return 0;  bad_desc: @@ -341,6 +370,10 @@ void usbnet_cdc_unbind(struct usbnet *dev, struct usb_interface *intf)  	struct cdc_state		*info = (void *) &dev->data;  	struct usb_driver		*driver = driver_of(intf); +	/* combined interface - nothing  to do */ +	if (info->data == info->control) +		return; +  	/* disconnect master --> disconnect slave */  	if (intf == info->control && info->data) {  		/* ensure immediate exit from usbnet_disconnect */ @@ -359,9 +392,7 @@ void usbnet_cdc_unbind(struct usbnet *dev, struct usb_interface *intf)  }  EXPORT_SYMBOL_GPL(usbnet_cdc_unbind); -/*------------------------------------------------------------------------- - * - * Communications Device Class, Ethernet Control model +/* Communications Device Class, Ethernet Control model   *   * Takes two interfaces.  The DATA interface is inactive till an altsetting   * is selected.  Configuration data includes class descriptors.  There's @@ -369,8 +400,7 @@ EXPORT_SYMBOL_GPL(usbnet_cdc_unbind);   *   * This should interop with whatever the 2.4 "CDCEther.c" driver   * (by Brad Hards) talked with, with more functionality. - * - *-------------------------------------------------------------------------*/ + */  static void dumpspeed(struct usbnet *dev, __le32 *speeds)  { @@ -380,11 +410,11 @@ static void dumpspeed(struct usbnet *dev, __le32 *speeds)  		   __le32_to_cpu(speeds[1]) / 1000);  } -static void cdc_status(struct usbnet *dev, struct urb *urb) +void usbnet_cdc_status(struct usbnet *dev, struct urb *urb)  {  	struct usb_cdc_notification	*event; -	if (urb->actual_length < sizeof *event) +	if (urb->actual_length < sizeof(*event))  		return;  	/* SPEED_CHANGE can get split into two 8-byte packets */ @@ -398,15 +428,12 @@ static void cdc_status(struct usbnet *dev, struct urb *urb)  	case USB_CDC_NOTIFY_NETWORK_CONNECTION:  		netif_dbg(dev, timer, dev->net, "CDC: carrier %s\n",  			  event->wValue ? "on" : "off"); -		if (event->wValue) -			netif_carrier_on(dev->net); -		else -			netif_carrier_off(dev->net); +		usbnet_link_change(dev, !!event->wValue, 0);  		break;  	case USB_CDC_NOTIFY_SPEED_CHANGE:	/* tx/rx rates */  		netif_dbg(dev, timer, dev->net, "CDC: speed change (len %d)\n",  			  urb->actual_length); -		if (urb->actual_length != (sizeof *event + 8)) +		if (urb->actual_length != (sizeof(*event) + 8))  			set_bit(EVENT_STS_SPLIT, &dev->flags);  		else  			dumpspeed(dev, (__le32 *) &event[1]); @@ -420,12 +447,16 @@ static void cdc_status(struct usbnet *dev, struct urb *urb)  		break;  	}  } +EXPORT_SYMBOL_GPL(usbnet_cdc_status); -static int cdc_bind(struct usbnet *dev, struct usb_interface *intf) +int usbnet_cdc_bind(struct usbnet *dev, struct usb_interface *intf)  {  	int				status;  	struct cdc_state		*info = (void *) &dev->data; +	BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data) +			< sizeof(struct cdc_state))); +  	status = usbnet_generic_cdc_bind(dev, intf);  	if (status < 0)  		return status; @@ -443,38 +474,37 @@ static int cdc_bind(struct usbnet *dev, struct usb_interface *intf)  	 */  	return 0;  } - -static int cdc_manage_power(struct usbnet *dev, int on) -{ -	dev->intf->needs_remote_wakeup = on; -	return 0; -} +EXPORT_SYMBOL_GPL(usbnet_cdc_bind);  static const struct driver_info	cdc_info = {  	.description =	"CDC Ethernet Device", -	.flags =	FLAG_ETHER, -	// .check_connect = cdc_check_connect, -	.bind =		cdc_bind, +	.flags =	FLAG_ETHER | FLAG_POINTTOPOINT, +	.bind =		usbnet_cdc_bind,  	.unbind =	usbnet_cdc_unbind, -	.status =	cdc_status, -	.manage_power =	cdc_manage_power, +	.status =	usbnet_cdc_status, +	.manage_power =	usbnet_manage_power,  }; -static const struct driver_info mbm_info = { +static const struct driver_info wwan_info = {  	.description =	"Mobile Broadband Network Device",  	.flags =	FLAG_WWAN, -	.bind = 	cdc_bind, +	.bind =		usbnet_cdc_bind,  	.unbind =	usbnet_cdc_unbind, -	.status =	cdc_status, -	.manage_power =	cdc_manage_power, +	.status =	usbnet_cdc_status, +	.manage_power =	usbnet_manage_power,  };  /*-------------------------------------------------------------------------*/ +#define HUAWEI_VENDOR_ID	0x12D1 +#define NOVATEL_VENDOR_ID	0x1410 +#define ZTE_VENDOR_ID		0x19D2 +#define DELL_VENDOR_ID		0x413C +#define REALTEK_VENDOR_ID	0x0bda +#define SAMSUNG_VENDOR_ID	0x04e8 -static const struct usb_device_id	products [] = { -/* - * BLACKLIST !! +static const struct usb_device_id	products[] = { +/* BLACKLIST !!   *   * First blacklist any products that are egregiously nonconformant   * with the CDC Ethernet specs.  Minor braindamage we cope with; when @@ -521,7 +551,7 @@ static const struct usb_device_id	products [] = {  	.driver_info		= 0,  }, {  	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO -	          | USB_DEVICE_ID_MATCH_DEVICE, +			  | USB_DEVICE_ID_MATCH_DEVICE,  	.idVendor		= 0x04DD,  	.idProduct		= 0x8007,	/* C-700 */  	ZAURUS_MASTER_INTERFACE, @@ -562,8 +592,97 @@ static const struct usb_device_id	products [] = {  	.driver_info		= 0,  }, -/* - * WHITELIST!!! +/* LG Electronics VL600 wants additional headers on every frame */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(0x1004, 0x61aa, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* Logitech Harmony 900 - uses the pseudo-MDLM (BLAN) driver */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(0x046d, 0xc11f, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), +	.driver_info		= 0, +}, + +/* Novatel USB551L and MC551 - handled by qmi_wwan */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(NOVATEL_VENDOR_ID, 0xB001, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* Novatel E362 - handled by qmi_wwan */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(NOVATEL_VENDOR_ID, 0x9010, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* Dell Wireless 5800 (Novatel E362) - handled by qmi_wwan */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, 0x8195, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* Dell Wireless 5800 (Novatel E362) - handled by qmi_wwan */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, 0x8196, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* Dell Wireless 5804 (Novatel E371) - handled by qmi_wwan */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, 0x819b, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* Novatel Expedite E371 - handled by qmi_wwan */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(NOVATEL_VENDOR_ID, 0x9011, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* AnyDATA ADU960S - handled by qmi_wwan */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(0x16d5, 0x650a, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* Huawei E1820 - handled by qmi_wwan */ +{ +	USB_DEVICE_INTERFACE_NUMBER(HUAWEI_VENDOR_ID, 0x14ac, 1), +	.driver_info = 0, +}, + +/* Realtek RTL8152 Based USB 2.0 Ethernet Adapters */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(REALTEK_VENDOR_ID, 0x8152, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* Realtek RTL8153 Based USB 3.0 Ethernet Adapters */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(REALTEK_VENDOR_ID, 0x8153, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* Samsung USB Ethernet Adapters */ +{ +	USB_DEVICE_AND_INTERFACE_INFO(SAMSUNG_VENDOR_ID, 0xa101, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = 0, +}, + +/* WHITELIST!!!   *   * CDC Ether uses two interfaces, not necessarily consecutive.   * We match the main interface, ignoring the optional device @@ -574,16 +693,56 @@ static const struct usb_device_id	products [] = {   * because of bugs/quirks in a given product (like Zaurus, above).   */  { +	/* ZTE (Vodafone) K3805-Z */ +	USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1003, USB_CLASS_COMM, +				      USB_CDC_SUBCLASS_ETHERNET, +				      USB_CDC_PROTO_NONE), +	.driver_info = (unsigned long)&wwan_info, +}, { +	/* ZTE (Vodafone) K3806-Z */ +	USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1015, USB_CLASS_COMM, +				      USB_CDC_SUBCLASS_ETHERNET, +				      USB_CDC_PROTO_NONE), +	.driver_info = (unsigned long)&wwan_info, +}, { +	/* ZTE (Vodafone) K4510-Z */ +	USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1173, USB_CLASS_COMM, +				      USB_CDC_SUBCLASS_ETHERNET, +				      USB_CDC_PROTO_NONE), +	.driver_info = (unsigned long)&wwan_info, +}, { +	/* ZTE (Vodafone) K3770-Z */ +	USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1177, USB_CLASS_COMM, +				      USB_CDC_SUBCLASS_ETHERNET, +				      USB_CDC_PROTO_NONE), +	.driver_info = (unsigned long)&wwan_info, +}, { +	/* ZTE (Vodafone) K3772-Z */ +	USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1181, USB_CLASS_COMM, +				      USB_CDC_SUBCLASS_ETHERNET, +				      USB_CDC_PROTO_NONE), +	.driver_info = (unsigned long)&wwan_info, +}, { +	/* Telit modules */ +	USB_VENDOR_AND_INTERFACE_INFO(0x1bc7, USB_CLASS_COMM, +			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), +	.driver_info = (kernel_ulong_t) &wwan_info, +}, {  	USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ETHERNET,  			USB_CDC_PROTO_NONE),  	.driver_info = (unsigned long) &cdc_info,  }, {  	USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MDLM,  			USB_CDC_PROTO_NONE), -	.driver_info = (unsigned long)&mbm_info, +	.driver_info = (unsigned long)&wwan_info, +}, { +	/* Various Huawei modems with a network port like the UMG1831 */ +	USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_COMM, +				      USB_CDC_SUBCLASS_ETHERNET, 255), +	.driver_info = (unsigned long)&wwan_info,  }, -	{ },		// END +	{ },		/* END */  };  MODULE_DEVICE_TABLE(usb, products); @@ -596,23 +755,10 @@ static struct usb_driver cdc_driver = {  	.resume =	usbnet_resume,  	.reset_resume =	usbnet_resume,  	.supports_autosuspend = 1, +	.disable_hub_initiated_lpm = 1,  }; - -static int __init cdc_init(void) -{ -	BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data) -			< sizeof(struct cdc_state))); - - 	return usb_register(&cdc_driver); -} -module_init(cdc_init); - -static void __exit cdc_exit(void) -{ - 	usb_deregister(&cdc_driver); -} -module_exit(cdc_exit); +module_usb_driver(cdc_driver);  MODULE_AUTHOR("David Brownell");  MODULE_DESCRIPTION("USB CDC Ethernet devices");  | 
