diff options
Diffstat (limited to 'drivers/net/usb')
| -rw-r--r-- | drivers/net/usb/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/net/usb/cdc_mbim.c | 18 | ||||
| -rw-r--r-- | drivers/net/usb/cdc_ncm.c | 49 | ||||
| -rw-r--r-- | drivers/net/usb/hso.c | 20 | ||||
| -rw-r--r-- | drivers/net/usb/qmi_wwan.c | 158 | ||||
| -rw-r--r-- | drivers/net/usb/smsc75xx.c | 18 | ||||
| -rw-r--r-- | drivers/net/usb/smsc95xx.c | 6 | 
7 files changed, 186 insertions, 85 deletions
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index 3b6e9b83342..7c769d8e25a 100644 --- a/drivers/net/usb/Kconfig +++ b/drivers/net/usb/Kconfig @@ -268,7 +268,7 @@ config USB_NET_SMSC75XX  	select CRC16  	select CRC32  	help -	  This option adds support for SMSC LAN95XX based USB 2.0 +	  This option adds support for SMSC LAN75XX based USB 2.0  	  Gigabit Ethernet adapters.  config USB_NET_SMSC95XX diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c index 248d2dc765a..32a76059e7d 100644 --- a/drivers/net/usb/cdc_mbim.c +++ b/drivers/net/usb/cdc_mbim.c @@ -68,18 +68,9 @@ 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; -	u8 data_altsetting = CDC_NCM_DATA_ALTSETTING_NCM; +	u8 data_altsetting = cdc_ncm_select_altsetting(dev, intf);  	struct cdc_mbim_state *info = (void *)&dev->data; -	/* see if interface supports MBIM alternate setting */ -	if (intf->num_altsetting == 2) { -		if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) -			usb_set_interface(dev->udev, -					  intf->cur_altsetting->desc.bInterfaceNumber, -					  CDC_NCM_COMM_ALTSETTING_MBIM); -		data_altsetting = CDC_NCM_DATA_ALTSETTING_MBIM; -	} -  	/* Probably NCM, defer for cdc_ncm_bind */  	if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting))  		goto err; @@ -143,7 +134,7 @@ static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb  		goto error;  	if (skb) { -		if (skb->len <= sizeof(ETH_HLEN)) +		if (skb->len <= ETH_HLEN)  			goto error;  		/* mapping VLANs to MBIM sessions: @@ -332,6 +323,11 @@ static int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message)  		goto error;  	} +	/* +	 * 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. +	 */  	ret = usbnet_suspend(intf, message);  	if (ret < 0)  		goto error; diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 61b74a2b89a..4709fa3497c 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -55,6 +55,14 @@  #define	DRIVER_VERSION				"14-Mar-2012" +#if IS_ENABLED(CONFIG_USB_NET_CDC_MBIM) +static bool prefer_mbim = true; +#else +static bool prefer_mbim; +#endif +module_param(prefer_mbim, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(prefer_mbim, "Prefer MBIM setting on dual NCM/MBIM functions"); +  static void cdc_ncm_txpath_bh(unsigned long param);  static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx);  static enum hrtimer_restart cdc_ncm_tx_timer_cb(struct hrtimer *hr_timer); @@ -550,9 +558,12 @@ void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)  }  EXPORT_SYMBOL_GPL(cdc_ncm_unbind); -static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf) +/* Select the MBIM altsetting iff it is preferred and available, + * returning the number of the corresponding data interface altsetting + */ +u8 cdc_ncm_select_altsetting(struct usbnet *dev, struct usb_interface *intf)  { -	int ret; +	struct usb_host_interface *alt;  	/* The MBIM spec defines a NCM compatible default altsetting,  	 * which we may have matched: @@ -568,23 +579,27 @@ static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)  	 *   endpoint descriptors, shall be constructed according to  	 *   the rules given in section 6 (USB Device Model) of this  	 *   specification." -	 * -	 * Do not bind to such interfaces, allowing cdc_mbim to handle -	 * them  	 */ -#if IS_ENABLED(CONFIG_USB_NET_CDC_MBIM) -	if ((intf->num_altsetting == 2) && -	    !usb_set_interface(dev->udev, -			       intf->cur_altsetting->desc.bInterfaceNumber, -			       CDC_NCM_COMM_ALTSETTING_MBIM)) { -		if (cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) -			return -ENODEV; -		else -			usb_set_interface(dev->udev, -					  intf->cur_altsetting->desc.bInterfaceNumber, -					  CDC_NCM_COMM_ALTSETTING_NCM); +	if (prefer_mbim && intf->num_altsetting == 2) { +		alt = usb_altnum_to_altsetting(intf, CDC_NCM_COMM_ALTSETTING_MBIM); +		if (alt && cdc_ncm_comm_intf_is_mbim(alt) && +		    !usb_set_interface(dev->udev, +				       intf->cur_altsetting->desc.bInterfaceNumber, +				       CDC_NCM_COMM_ALTSETTING_MBIM)) +			return CDC_NCM_DATA_ALTSETTING_MBIM;  	} -#endif +	return CDC_NCM_DATA_ALTSETTING_NCM; +} +EXPORT_SYMBOL_GPL(cdc_ncm_select_altsetting); + +static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf) +{ +	int ret; + +	/* MBIM backwards compatible function? */ +	cdc_ncm_select_altsetting(dev, intf); +	if (cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) +		return -ENODEV;  	/* NCM data altsetting is always 1 */  	ret = cdc_ncm_bind_common(dev, intf, 1); diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index e2dd3249b6b..cba1d46e672 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -1925,7 +1925,6 @@ static void hso_std_serial_write_bulk_callback(struct urb *urb)  {  	struct hso_serial *serial = urb->context;  	int status = urb->status; -	struct tty_struct *tty;  	/* sanity check */  	if (!serial) { @@ -1941,11 +1940,7 @@ static void hso_std_serial_write_bulk_callback(struct urb *urb)  		return;  	}  	hso_put_activity(serial->parent); -	tty = tty_port_tty_get(&serial->port); -	if (tty) { -		tty_wakeup(tty); -		tty_kref_put(tty); -	} +	tty_port_tty_wakeup(&serial->port);  	hso_kick_transmit(serial);  	D1(" "); @@ -2008,12 +2003,8 @@ static void ctrl_callback(struct urb *urb)  		put_rxbuf_data_and_resubmit_ctrl_urb(serial);  		spin_unlock(&serial->serial_lock);  	} else { -		struct tty_struct *tty = tty_port_tty_get(&serial->port);  		hso_put_activity(serial->parent); -		if (tty) { -			tty_wakeup(tty); -			tty_kref_put(tty); -		} +		tty_port_tty_wakeup(&serial->port);  		/* response to a write command */  		hso_kick_transmit(serial);  	} @@ -3133,18 +3124,13 @@ static void hso_serial_ref_free(struct kref *ref)  static void hso_free_interface(struct usb_interface *interface)  {  	struct hso_serial *hso_dev; -	struct tty_struct *tty;  	int i;  	for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) {  		if (serial_table[i] &&  		    (serial_table[i]->interface == interface)) {  			hso_dev = dev2ser(serial_table[i]); -			tty = tty_port_tty_get(&hso_dev->port); -			if (tty) { -				tty_hangup(tty); -				tty_kref_put(tty); -			} +			tty_port_tty_hangup(&hso_dev->port, false);  			mutex_lock(&hso_dev->parent->mutex);  			hso_dev->parent->usb_gone = 1;  			mutex_unlock(&hso_dev->parent->mutex); diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index efb5c7c33a2..5a88e72090c 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -13,6 +13,7 @@  #include <linux/module.h>  #include <linux/netdevice.h>  #include <linux/ethtool.h> +#include <linux/etherdevice.h>  #include <linux/mii.h>  #include <linux/usb.h>  #include <linux/usb/cdc.h> @@ -52,6 +53,96 @@ struct qmi_wwan_state {  	struct usb_interface *data;  }; +/* default ethernet address used by the modem */ +static const u8 default_modem_addr[ETH_ALEN] = {0x02, 0x50, 0xf3}; + +/* Make up an ethernet header if the packet doesn't have one. + * + * A firmware bug common among several devices cause them to send raw + * IP packets under some circumstances.  There is no way for the + * driver/host to know when this will happen.  And even when the bug + * hits, some packets will still arrive with an intact header. + * + * The supported devices are only capably of sending IPv4, IPv6 and + * ARP packets on a point-to-point link. Any packet with an ethernet + * header will have either our address or a broadcast/multicast + * address as destination.  ARP packets will always have a header. + * + * This means that this function will reliably add the appropriate + * header iff necessary, provided our hardware address does not start + * with 4 or 6. + * + * Another common firmware bug results in all packets being addressed + * to 00:a0:c6:00:00:00 despite the host address being different. + * This function will also fixup such packets. + */ +static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ +	__be16 proto; + +	/* usbnet rx_complete guarantees that skb->len is at least +	 * hard_header_len, so we can inspect the dest address without +	 * checking skb->len +	 */ +	switch (skb->data[0] & 0xf0) { +	case 0x40: +		proto = htons(ETH_P_IP); +		break; +	case 0x60: +		proto = htons(ETH_P_IPV6); +		break; +	case 0x00: +		if (is_multicast_ether_addr(skb->data)) +			return 1; +		/* possibly bogus destination - rewrite just in case */ +		skb_reset_mac_header(skb); +		goto fix_dest; +	default: +		/* pass along other packets without modifications */ +		return 1; +	} +	if (skb_headroom(skb) < ETH_HLEN) +		return 0; +	skb_push(skb, ETH_HLEN); +	skb_reset_mac_header(skb); +	eth_hdr(skb)->h_proto = proto; +	memset(eth_hdr(skb)->h_source, 0, ETH_ALEN); +fix_dest: +	memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); +	return 1; +} + +/* very simplistic detection of IPv4 or IPv6 headers */ +static bool possibly_iphdr(const char *data) +{ +	return (data[0] & 0xd0) == 0x40; +} + +/* disallow addresses which may be confused with IP headers */ +static int qmi_wwan_mac_addr(struct net_device *dev, void *p) +{ +	int ret; +	struct sockaddr *addr = p; + +	ret = eth_prepare_mac_addr_change(dev, p); +	if (ret < 0) +		return ret; +	if (possibly_iphdr(addr->sa_data)) +		return -EADDRNOTAVAIL; +	eth_commit_mac_addr_change(dev, p); +	return 0; +} + +static const struct net_device_ops qmi_wwan_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	= qmi_wwan_mac_addr, +	.ndo_validate_addr	= eth_validate_addr, +}; +  /* using a counter to merge subdriver requests with our own into a combined state */  static int qmi_wwan_manage_power(struct usbnet *dev, int on)  { @@ -139,16 +230,9 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)  	BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data) < sizeof(struct qmi_wwan_state))); -	/* control and data is shared? */ -	if (intf->cur_altsetting->desc.bNumEndpoints == 3) { -		info->control = intf; -		info->data = intf; -		goto shared; -	} - -	/* else require a single interrupt status endpoint on control intf */ -	if (intf->cur_altsetting->desc.bNumEndpoints != 1) -		goto err; +	/* set up initial state */ +	info->control = intf; +	info->data = intf;  	/* and a number of CDC descriptors */  	while (len > 3) { @@ -207,25 +291,14 @@ next_desc:  		buf += h->bLength;  	} -	/* did we find all the required ones? */ -	if (!(found & (1 << USB_CDC_HEADER_TYPE)) || -	    !(found & (1 << USB_CDC_UNION_TYPE))) { -		dev_err(&intf->dev, "CDC functional descriptors missing\n"); -		goto err; -	} - -	/* verify CDC Union */ -	if (desc->bInterfaceNumber != cdc_union->bMasterInterface0) { -		dev_err(&intf->dev, "bogus CDC Union: master=%u\n", cdc_union->bMasterInterface0); -		goto err; -	} - -	/* need to save these for unbind */ -	info->control = intf; -	info->data = usb_ifnum_to_if(dev->udev,	cdc_union->bSlaveInterface0); -	if (!info->data) { -		dev_err(&intf->dev, "bogus CDC Union: slave=%u\n", cdc_union->bSlaveInterface0); -		goto err; +	/* Use separate control and data interfaces if we found a CDC Union */ +	if (cdc_union) { +		info->data = usb_ifnum_to_if(dev->udev, cdc_union->bSlaveInterface0); +		if (desc->bInterfaceNumber != cdc_union->bMasterInterface0 || !info->data) { +			dev_err(&intf->dev, "bogus CDC Union: master=%u, slave=%u\n", +				cdc_union->bMasterInterface0, cdc_union->bSlaveInterface0); +			goto err; +		}  	}  	/* errors aren't fatal - we can live with the dynamic address */ @@ -235,17 +308,30 @@ next_desc:  	}  	/* claim data interface and set it up */ -	status = usb_driver_claim_interface(driver, info->data, dev); -	if (status < 0) -		goto err; +	if (info->control != info->data) { +		status = usb_driver_claim_interface(driver, info->data, dev); +		if (status < 0) +			goto err; +	} -shared:  	status = qmi_wwan_register_subdriver(dev);  	if (status < 0 && info->control != info->data) {  		usb_set_intfdata(info->data, NULL);  		usb_driver_release_interface(driver, info->data);  	} +	/* Never use the same address on both ends of the link, even +	 * if the buggy firmware told us to. +	 */ +	if (!compare_ether_addr(dev->net->dev_addr, default_modem_addr)) +		eth_hw_addr_random(dev->net); + +	/* make MAC addr easily distinguishable from an IP header */ +	if (possibly_iphdr(dev->net->dev_addr)) { +		dev->net->dev_addr[0] |= 0x02;	/* set local assignment bit */ +		dev->net->dev_addr[0] &= 0xbf;	/* clear "IP" bit */ +	} +	dev->net->netdev_ops = &qmi_wwan_netdev_ops;  err:  	return status;  } @@ -288,6 +374,11 @@ static int qmi_wwan_suspend(struct usb_interface *intf, pm_message_t message)  	struct qmi_wwan_state *info = (void *)&dev->data;  	int ret; +	/* +	 * 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. +	 */  	ret = usbnet_suspend(intf, message);  	if (ret < 0)  		goto err; @@ -324,6 +415,7 @@ static const struct driver_info	qmi_wwan_info = {  	.bind		= qmi_wwan_bind,  	.unbind		= qmi_wwan_unbind,  	.manage_power	= qmi_wwan_manage_power, +	.rx_fixup       = qmi_wwan_rx_fixup,  };  #define HUAWEI_VENDOR_ID	0x12D1 diff --git a/drivers/net/usb/smsc75xx.c b/drivers/net/usb/smsc75xx.c index 9abe51710f2..75409748c77 100644 --- a/drivers/net/usb/smsc75xx.c +++ b/drivers/net/usb/smsc75xx.c @@ -914,8 +914,12 @@ static int smsc75xx_set_rx_max_frame_length(struct usbnet *dev, int size)  static int smsc75xx_change_mtu(struct net_device *netdev, int new_mtu)  {  	struct usbnet *dev = netdev_priv(netdev); +	int ret; + +	if (new_mtu > MAX_SINGLE_PACKET_SIZE) +		return -EINVAL; -	int ret = smsc75xx_set_rx_max_frame_length(dev, new_mtu); +	ret = smsc75xx_set_rx_max_frame_length(dev, new_mtu + ETH_HLEN);  	if (ret < 0) {  		netdev_warn(dev->net, "Failed to set mac rx frame length\n");  		return ret; @@ -1324,7 +1328,7 @@ static int smsc75xx_reset(struct usbnet *dev)  	netif_dbg(dev, ifup, dev->net, "FCT_TX_CTL set to 0x%08x\n", buf); -	ret = smsc75xx_set_rx_max_frame_length(dev, 1514); +	ret = smsc75xx_set_rx_max_frame_length(dev, dev->net->mtu + ETH_HLEN);  	if (ret < 0) {  		netdev_warn(dev->net, "Failed to set max rx frame length\n");  		return ret; @@ -2011,7 +2015,11 @@ static int smsc75xx_suspend(struct usb_interface *intf, pm_message_t message)  	ret = smsc75xx_enter_suspend0(dev);  done: -	if (ret) +	/* +	 * TODO: resume() might need to handle the suspend failure +	 * in system sleep +	 */ +	if (ret && PMSG_IS_AUTO(message))  		usbnet_resume(intf);  	return ret;  } @@ -2134,8 +2142,8 @@ static int smsc75xx_rx_fixup(struct usbnet *dev, struct sk_buff *skb)  			else if (rx_cmd_a & (RX_CMD_A_LONG | RX_CMD_A_RUNT))  				dev->net->stats.rx_frame_errors++;  		} else { -			/* ETH_FRAME_LEN + 4(CRC) + 2(COE) + 4(Vlan) */ -			if (unlikely(size > (ETH_FRAME_LEN + 12))) { +			/* MAX_SINGLE_PACKET_SIZE + 4(CRC) + 2(COE) + 4(Vlan) */ +			if (unlikely(size > (MAX_SINGLE_PACKET_SIZE + ETH_HLEN + 12))) {  				netif_dbg(dev, rx_err, dev->net,  					  "size err rx_cmd_a=0x%08x\n",  					  rx_cmd_a); diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c index e6d2dea1373..3f38ba868f6 100644 --- a/drivers/net/usb/smsc95xx.c +++ b/drivers/net/usb/smsc95xx.c @@ -1660,7 +1660,11 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message)  	ret = smsc95xx_enter_suspend0(dev);  done: -	if (ret) +	/* +	 * TODO: resume() might need to handle the suspend failure +	 * in system sleep +	 */ +	if (ret && PMSG_IS_AUTO(message))  		usbnet_resume(intf);  	return ret;  }  |