diff options
Diffstat (limited to 'drivers/net/usb/cdc_mbim.c')
| -rw-r--r-- | drivers/net/usb/cdc_mbim.c | 274 | 
1 files changed, 234 insertions, 40 deletions
diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c index 25ba7eca9a1..5ee7a1dbc02 100644 --- a/drivers/net/usb/cdc_mbim.c +++ b/drivers/net/usb/cdc_mbim.c @@ -21,14 +21,24 @@  #include <linux/usb/usbnet.h>  #include <linux/usb/cdc-wdm.h>  #include <linux/usb/cdc_ncm.h> +#include <net/ipv6.h> +#include <net/addrconf.h> + +/* alternative VLAN for IP session 0 if not untagged */ +#define MBIM_IPS0_VID	4094  /* 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; +	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  */  };  /* using a counter to merge subdriver requests with our own into a combined state */ @@ -42,13 +52,11 @@ static int cdc_mbim_manage_power(struct usbnet *dev, int 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); -		if (rv < 0) -			goto err;  		dev->intf->needs_remote_wakeup = on; -		usb_autopm_put_interface(dev->intf); +		if (!rv) +			usb_autopm_put_interface(dev->intf);  	} -err: -	return rv; +	return 0;  }  static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status) @@ -62,16 +70,91 @@ static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status)  	return cdc_mbim_manage_power(dev, status);  } +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, +}; + +/* 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; +}  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_select_altsetting(dev, intf); +	u8 data_altsetting = 1;  	struct cdc_mbim_state *info = (void *)&dev->data; -	/* Probably NCM, defer for cdc_ncm_bind */ +	/* 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 */  	if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting))  		goto err; @@ -101,7 +184,10 @@ static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf)  	dev->net->flags |= IFF_NOARP;  	/* no need to put the VLAN tci in the packet headers */ -	dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX; +	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;  err:  	return ret;  } @@ -120,6 +206,16 @@ static void cdc_mbim_unbind(struct usbnet *dev, struct usb_interface *intf)  	cdc_ncm_unbind(dev, intf);  } +/* 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; +}  static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)  { @@ -128,6 +224,7 @@ static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb  	struct cdc_ncm_ctx *ctx = info->ctx;  	__le32 sign = cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN);  	u16 tci = 0; +	bool is_ip;  	u8 *c;  	if (!ctx) @@ -137,29 +234,50 @@ static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb  		if (skb->len <= ETH_HLEN)  			goto error; +		/* 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); +		} + +		/* 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; +		} +  		/* mapping VLANs to MBIM sessions: -		 *   no tag     => IPS session <0> +		 *   no tag     => IPS session <0> if !FLAG_IPS0_VLAN  		 *   1 - 255    => IPS session <vlanid>  		 *   256 - 511  => DSS session <vlanid - 256> -		 *   512 - 4095 => unsupported, drop +		 *   512 - 4093 => unsupported, drop +		 *   4094       => IPS session <0> if FLAG_IPS0_VLAN  		 */ -		vlan_get_tag(skb, &tci);  		switch (tci & 0x0f00) {  		case 0x0000: /* VLAN ID 0 - 255 */ -			/* 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: +			if (!is_ip)  				goto error; -			}  			c = (u8 *)&sign;  			c[3] = tci;  			break;  		case 0x0100: /* VLAN ID 256 - 511 */ +			if (is_ip) +				goto error;  			sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN);  			c = (u8 *)&sign;  			c[3] = tci; @@ -169,11 +287,10 @@ static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb  				  "unsupported tci=0x%04x\n", tci);  			goto error;  		} -		skb_pull(skb, ETH_HLEN);  	}  	spin_lock_bh(&ctx->mtx); -	skb_out = cdc_ncm_fill_tx_frame(ctx, skb, sign); +	skb_out = cdc_ncm_fill_tx_frame(dev, skb, sign);  	spin_unlock_bh(&ctx->mtx);  	return skb_out; @@ -184,12 +301,74 @@ error:  	return NULL;  } +/* 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 */ +	rcu_read_lock(); +	if (tci) { +		netdev = __vlan_find_dev_deep_rcu(dev->net, htons(ETH_P_8021Q), +						  tci); +		if (!netdev) { +			rcu_read_unlock(); +			return; +		} +	} else { +		netdev = dev->net; +	} +	dev_hold(netdev); +	rcu_read_unlock(); + +	in6_dev = in6_dev_get(netdev); +	if (!in6_dev) +		goto out; +	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 */); +out: +	dev_put(netdev); +} + +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); +} + +  static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci)  {  	__be16 proto = htons(ETH_P_802_3);  	struct sk_buff *skb = NULL; -	if (tci < 256) { /* IPS session? */ +	if (tci < 256 || tci == MBIM_IPS0_VID) { /* IPS session? */  		if (len < sizeof(struct iphdr))  			goto err; @@ -198,6 +377,8 @@ static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_  			proto = htons(ETH_P_IP);  			break;  		case 0x60: +			if (is_neigh_solicit(buf, len)) +				do_neigh_solicit(dev, buf, tci);  			proto = htons(ETH_P_IPV6);  			break;  		default: @@ -239,6 +420,7 @@ static int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)  	struct usb_cdc_ncm_dpe16 *dpe16;  	int ndpoffset;  	int loopcount = 50; /* arbitrary max preventing infinite loop */ +	u32 payload = 0;  	u8 *c;  	u16 tci; @@ -257,6 +439,9 @@ next_ndp:  	case cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN):  		c = (u8 *)&ndp16->dwSignature;  		tci = c[3]; +		/* tag IPS<0> packets too if MBIM_IPS0_VID exists */ +		if (!tci && info->flags & FLAG_IPS0_VLAN) +			tci = MBIM_IPS0_VID;  		break;  	case cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN):  		c = (u8 *)&ndp16->dwSignature; @@ -298,6 +483,7 @@ next_ndp:  			if (!skb)  				goto error;  			usbnet_skb_return(dev, skb); +			payload += len;	/* count payload bytes in this NTB */  		}  	}  err_ndp: @@ -306,6 +492,10 @@ err_ndp:  	if (ndpoffset && loopcount--)  		goto next_ndp; +	/* update stats */ +	ctx->rx_overhead += skb_in->len - payload; +	ctx->rx_ntbs++; +  	return 1;  error:  	return 0; @@ -313,15 +503,13 @@ error:  static int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message)  { -	int ret = 0; +	int ret = -ENODEV;  	struct usbnet *dev = usb_get_intfdata(intf);  	struct cdc_mbim_state *info = (void *)&dev->data;  	struct cdc_ncm_ctx *ctx = info->ctx; -	if (ctx == NULL) { -		ret = -1; +	if (!ctx)  		goto error; -	}  	/*  	 * Both usbnet_suspend() and subdriver->suspend() MUST return 0 @@ -354,7 +542,7 @@ static int cdc_mbim_resume(struct usb_interface *intf)  	if (ret < 0)  		goto err;  	ret = usbnet_resume(intf); -	if (ret < 0 && callsub && info->subdriver->suspend) +	if (ret < 0 && callsub)  		info->subdriver->suspend(intf, PMSG_SUSPEND);  err:  	return ret; @@ -371,9 +559,18 @@ static const struct driver_info cdc_mbim_info = {  };  /* MBIM and NCM devices should not need a ZLP after NTBs with - * dwNtbOutMaxSize length. This driver_info is for the exceptional - * devices requiring it anyway, allowing them to be supported without - * forcing the performance penalty on all the sane devices. + * 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.   */  static const struct driver_info cdc_mbim_info_zlp = {  	.description = "CDC MBIM", @@ -396,16 +593,13 @@ static const struct usb_device_id mbim_devs[] = {  	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE),  	  .driver_info = (unsigned long)&cdc_mbim_info,  	}, -	/* Sierra Wireless MC7710 need ZLPs */ -	{ USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x68a2, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), -	  .driver_info = (unsigned long)&cdc_mbim_info_zlp, -	}, -	/* HP hs2434 Mobile Broadband Module needs ZLPs */ -	{ USB_DEVICE_AND_INTERFACE_INFO(0x3f0, 0x4b1d, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), -	  .driver_info = (unsigned long)&cdc_mbim_info_zlp, +	/* 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,  	}, +	/* default entry */  	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), -	  .driver_info = (unsigned long)&cdc_mbim_info, +	  .driver_info = (unsigned long)&cdc_mbim_info_zlp,  	},  	{  	},  | 
