162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2012  Smith Micro Software, Inc.
462306a36Sopenharmony_ci * Copyright (c) 2012  Bjørn Mork <bjorn@mork.no>
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This driver is based on and reuse most of cdc_ncm, which is
762306a36Sopenharmony_ci * Copyright (C) ST-Ericsson 2010-2012
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/netdevice.h>
1262306a36Sopenharmony_ci#include <linux/ethtool.h>
1362306a36Sopenharmony_ci#include <linux/if_vlan.h>
1462306a36Sopenharmony_ci#include <linux/ip.h>
1562306a36Sopenharmony_ci#include <linux/mii.h>
1662306a36Sopenharmony_ci#include <linux/usb.h>
1762306a36Sopenharmony_ci#include <linux/usb/cdc.h>
1862306a36Sopenharmony_ci#include <linux/usb/usbnet.h>
1962306a36Sopenharmony_ci#include <linux/usb/cdc-wdm.h>
2062306a36Sopenharmony_ci#include <linux/usb/cdc_ncm.h>
2162306a36Sopenharmony_ci#include <net/ipv6.h>
2262306a36Sopenharmony_ci#include <net/addrconf.h>
2362306a36Sopenharmony_ci#include <net/ipv6_stubs.h>
2462306a36Sopenharmony_ci#include <net/ndisc.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* alternative VLAN for IP session 0 if not untagged */
2762306a36Sopenharmony_ci#define MBIM_IPS0_VID	4094
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/* driver specific data - must match cdc_ncm usage */
3062306a36Sopenharmony_cistruct cdc_mbim_state {
3162306a36Sopenharmony_ci	struct cdc_ncm_ctx *ctx;
3262306a36Sopenharmony_ci	atomic_t pmcount;
3362306a36Sopenharmony_ci	struct usb_driver *subdriver;
3462306a36Sopenharmony_ci	unsigned long _unused;
3562306a36Sopenharmony_ci	unsigned long flags;
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/* flags for the cdc_mbim_state.flags field */
3962306a36Sopenharmony_cienum cdc_mbim_flags {
4062306a36Sopenharmony_ci	FLAG_IPS0_VLAN = 1 << 0,	/* IP session 0 is tagged  */
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/* using a counter to merge subdriver requests with our own into a combined state */
4462306a36Sopenharmony_cistatic int cdc_mbim_manage_power(struct usbnet *dev, int on)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	struct cdc_mbim_state *info = (void *)&dev->data;
4762306a36Sopenharmony_ci	int rv = 0;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, atomic_read(&info->pmcount), on);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	if ((on && atomic_add_return(1, &info->pmcount) == 1) || (!on && atomic_dec_and_test(&info->pmcount))) {
5262306a36Sopenharmony_ci		/* need autopm_get/put here to ensure the usbcore sees the new value */
5362306a36Sopenharmony_ci		rv = usb_autopm_get_interface(dev->intf);
5462306a36Sopenharmony_ci		dev->intf->needs_remote_wakeup = on;
5562306a36Sopenharmony_ci		if (!rv)
5662306a36Sopenharmony_ci			usb_autopm_put_interface(dev->intf);
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci	return 0;
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	struct usbnet *dev = usb_get_intfdata(intf);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	/* can be called while disconnecting */
6662306a36Sopenharmony_ci	if (!dev)
6762306a36Sopenharmony_ci		return 0;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	return cdc_mbim_manage_power(dev, status);
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic int cdc_mbim_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct usbnet *dev = netdev_priv(netdev);
7562306a36Sopenharmony_ci	struct cdc_mbim_state *info = (void *)&dev->data;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	/* creation of this VLAN is a request to tag IP session 0 */
7862306a36Sopenharmony_ci	if (vid == MBIM_IPS0_VID)
7962306a36Sopenharmony_ci		info->flags |= FLAG_IPS0_VLAN;
8062306a36Sopenharmony_ci	else
8162306a36Sopenharmony_ci		if (vid >= 512)	/* we don't map these to MBIM session */
8262306a36Sopenharmony_ci			return -EINVAL;
8362306a36Sopenharmony_ci	return 0;
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic int cdc_mbim_rx_kill_vid(struct net_device *netdev, __be16 proto, u16 vid)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	struct usbnet *dev = netdev_priv(netdev);
8962306a36Sopenharmony_ci	struct cdc_mbim_state *info = (void *)&dev->data;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	/* this is a request for an untagged IP session 0 */
9262306a36Sopenharmony_ci	if (vid == MBIM_IPS0_VID)
9362306a36Sopenharmony_ci		info->flags &= ~FLAG_IPS0_VLAN;
9462306a36Sopenharmony_ci	return 0;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic const struct net_device_ops cdc_mbim_netdev_ops = {
9862306a36Sopenharmony_ci	.ndo_open             = usbnet_open,
9962306a36Sopenharmony_ci	.ndo_stop             = usbnet_stop,
10062306a36Sopenharmony_ci	.ndo_start_xmit       = usbnet_start_xmit,
10162306a36Sopenharmony_ci	.ndo_tx_timeout       = usbnet_tx_timeout,
10262306a36Sopenharmony_ci	.ndo_get_stats64      = dev_get_tstats64,
10362306a36Sopenharmony_ci	.ndo_change_mtu       = cdc_ncm_change_mtu,
10462306a36Sopenharmony_ci	.ndo_set_mac_address  = eth_mac_addr,
10562306a36Sopenharmony_ci	.ndo_validate_addr    = eth_validate_addr,
10662306a36Sopenharmony_ci	.ndo_vlan_rx_add_vid  = cdc_mbim_rx_add_vid,
10762306a36Sopenharmony_ci	.ndo_vlan_rx_kill_vid = cdc_mbim_rx_kill_vid,
10862306a36Sopenharmony_ci};
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci/* Change the control interface altsetting and update the .driver_info
11162306a36Sopenharmony_ci * pointer if the matching entry after changing class codes points to
11262306a36Sopenharmony_ci * a different struct
11362306a36Sopenharmony_ci */
11462306a36Sopenharmony_cistatic int cdc_mbim_set_ctrlalt(struct usbnet *dev, struct usb_interface *intf, u8 alt)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct usb_driver *driver = to_usb_driver(intf->dev.driver);
11762306a36Sopenharmony_ci	const struct usb_device_id *id;
11862306a36Sopenharmony_ci	struct driver_info *info;
11962306a36Sopenharmony_ci	int ret;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	ret = usb_set_interface(dev->udev,
12262306a36Sopenharmony_ci				intf->cur_altsetting->desc.bInterfaceNumber,
12362306a36Sopenharmony_ci				alt);
12462306a36Sopenharmony_ci	if (ret)
12562306a36Sopenharmony_ci		return ret;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	id = usb_match_id(intf, driver->id_table);
12862306a36Sopenharmony_ci	if (!id)
12962306a36Sopenharmony_ci		return -ENODEV;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	info = (struct driver_info *)id->driver_info;
13262306a36Sopenharmony_ci	if (info != dev->driver_info) {
13362306a36Sopenharmony_ci		dev_dbg(&intf->dev, "driver_info updated to '%s'\n",
13462306a36Sopenharmony_ci			info->description);
13562306a36Sopenharmony_ci		dev->driver_info = info;
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci	return 0;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct cdc_ncm_ctx *ctx;
14362306a36Sopenharmony_ci	struct usb_driver *subdriver = ERR_PTR(-ENODEV);
14462306a36Sopenharmony_ci	int ret = -ENODEV;
14562306a36Sopenharmony_ci	u8 data_altsetting = 1;
14662306a36Sopenharmony_ci	struct cdc_mbim_state *info = (void *)&dev->data;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	/* should we change control altsetting on a NCM/MBIM function? */
14962306a36Sopenharmony_ci	if (cdc_ncm_select_altsetting(intf) == CDC_NCM_COMM_ALTSETTING_MBIM) {
15062306a36Sopenharmony_ci		data_altsetting = CDC_NCM_DATA_ALTSETTING_MBIM;
15162306a36Sopenharmony_ci		ret = cdc_mbim_set_ctrlalt(dev, intf, CDC_NCM_COMM_ALTSETTING_MBIM);
15262306a36Sopenharmony_ci		if (ret)
15362306a36Sopenharmony_ci			goto err;
15462306a36Sopenharmony_ci		ret = -ENODEV;
15562306a36Sopenharmony_ci	}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	/* we will hit this for NCM/MBIM functions if prefer_mbim is false */
15862306a36Sopenharmony_ci	if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting))
15962306a36Sopenharmony_ci		goto err;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	ret = cdc_ncm_bind_common(dev, intf, data_altsetting, dev->driver_info->data);
16262306a36Sopenharmony_ci	if (ret)
16362306a36Sopenharmony_ci		goto err;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	ctx = info->ctx;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/* The MBIM descriptor and the status endpoint are required */
16862306a36Sopenharmony_ci	if (ctx->mbim_desc && dev->status)
16962306a36Sopenharmony_ci		subdriver = usb_cdc_wdm_register(ctx->control,
17062306a36Sopenharmony_ci						 &dev->status->desc,
17162306a36Sopenharmony_ci						 le16_to_cpu(ctx->mbim_desc->wMaxControlMessage),
17262306a36Sopenharmony_ci						 WWAN_PORT_MBIM,
17362306a36Sopenharmony_ci						 cdc_mbim_wdm_manage_power);
17462306a36Sopenharmony_ci	if (IS_ERR(subdriver)) {
17562306a36Sopenharmony_ci		ret = PTR_ERR(subdriver);
17662306a36Sopenharmony_ci		cdc_ncm_unbind(dev, intf);
17762306a36Sopenharmony_ci		goto err;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/* can't let usbnet use the interrupt endpoint */
18162306a36Sopenharmony_ci	dev->status = NULL;
18262306a36Sopenharmony_ci	info->subdriver = subdriver;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	/* MBIM cannot do ARP */
18562306a36Sopenharmony_ci	dev->net->flags |= IFF_NOARP;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/* no need to put the VLAN tci in the packet headers */
18862306a36Sopenharmony_ci	dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_FILTER;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	/* monitor VLAN additions and removals */
19162306a36Sopenharmony_ci	dev->net->netdev_ops = &cdc_mbim_netdev_ops;
19262306a36Sopenharmony_cierr:
19362306a36Sopenharmony_ci	return ret;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic void cdc_mbim_unbind(struct usbnet *dev, struct usb_interface *intf)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	struct cdc_mbim_state *info = (void *)&dev->data;
19962306a36Sopenharmony_ci	struct cdc_ncm_ctx *ctx = info->ctx;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	/* disconnect subdriver from control interface */
20262306a36Sopenharmony_ci	if (info->subdriver && info->subdriver->disconnect)
20362306a36Sopenharmony_ci		info->subdriver->disconnect(ctx->control);
20462306a36Sopenharmony_ci	info->subdriver = NULL;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	/* let NCM unbind clean up both control and data interface */
20762306a36Sopenharmony_ci	cdc_ncm_unbind(dev, intf);
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci/* verify that the ethernet protocol is IPv4 or IPv6 */
21162306a36Sopenharmony_cistatic bool is_ip_proto(__be16 proto)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	switch (proto) {
21462306a36Sopenharmony_ci	case htons(ETH_P_IP):
21562306a36Sopenharmony_ci	case htons(ETH_P_IPV6):
21662306a36Sopenharmony_ci		return true;
21762306a36Sopenharmony_ci	}
21862306a36Sopenharmony_ci	return false;
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	struct sk_buff *skb_out;
22462306a36Sopenharmony_ci	struct cdc_mbim_state *info = (void *)&dev->data;
22562306a36Sopenharmony_ci	struct cdc_ncm_ctx *ctx = info->ctx;
22662306a36Sopenharmony_ci	__le32 sign = cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN);
22762306a36Sopenharmony_ci	u16 tci = 0;
22862306a36Sopenharmony_ci	bool is_ip;
22962306a36Sopenharmony_ci	u8 *c;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	if (!ctx)
23262306a36Sopenharmony_ci		goto error;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	if (skb) {
23562306a36Sopenharmony_ci		if (skb->len <= ETH_HLEN)
23662306a36Sopenharmony_ci			goto error;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci		/* Some applications using e.g. packet sockets will
23962306a36Sopenharmony_ci		 * bypass the VLAN acceleration and create tagged
24062306a36Sopenharmony_ci		 * ethernet frames directly.  We primarily look for
24162306a36Sopenharmony_ci		 * the accelerated out-of-band tag, but fall back if
24262306a36Sopenharmony_ci		 * required
24362306a36Sopenharmony_ci		 */
24462306a36Sopenharmony_ci		skb_reset_mac_header(skb);
24562306a36Sopenharmony_ci		if (vlan_get_tag(skb, &tci) < 0 && skb->len > VLAN_ETH_HLEN &&
24662306a36Sopenharmony_ci		    __vlan_get_tag(skb, &tci) == 0) {
24762306a36Sopenharmony_ci			is_ip = is_ip_proto(vlan_eth_hdr(skb)->h_vlan_encapsulated_proto);
24862306a36Sopenharmony_ci			skb_pull(skb, VLAN_ETH_HLEN);
24962306a36Sopenharmony_ci		} else {
25062306a36Sopenharmony_ci			is_ip = is_ip_proto(eth_hdr(skb)->h_proto);
25162306a36Sopenharmony_ci			skb_pull(skb, ETH_HLEN);
25262306a36Sopenharmony_ci		}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci		/* Is IP session <0> tagged too? */
25562306a36Sopenharmony_ci		if (info->flags & FLAG_IPS0_VLAN) {
25662306a36Sopenharmony_ci			/* drop all untagged packets */
25762306a36Sopenharmony_ci			if (!tci)
25862306a36Sopenharmony_ci				goto error;
25962306a36Sopenharmony_ci			/* map MBIM_IPS0_VID to IPS<0> */
26062306a36Sopenharmony_ci			if (tci == MBIM_IPS0_VID)
26162306a36Sopenharmony_ci				tci = 0;
26262306a36Sopenharmony_ci		}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci		/* mapping VLANs to MBIM sessions:
26562306a36Sopenharmony_ci		 *   no tag     => IPS session <0> if !FLAG_IPS0_VLAN
26662306a36Sopenharmony_ci		 *   1 - 255    => IPS session <vlanid>
26762306a36Sopenharmony_ci		 *   256 - 511  => DSS session <vlanid - 256>
26862306a36Sopenharmony_ci		 *   512 - 4093 => unsupported, drop
26962306a36Sopenharmony_ci		 *   4094       => IPS session <0> if FLAG_IPS0_VLAN
27062306a36Sopenharmony_ci		 */
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci		switch (tci & 0x0f00) {
27362306a36Sopenharmony_ci		case 0x0000: /* VLAN ID 0 - 255 */
27462306a36Sopenharmony_ci			if (!is_ip)
27562306a36Sopenharmony_ci				goto error;
27662306a36Sopenharmony_ci			c = (u8 *)&sign;
27762306a36Sopenharmony_ci			c[3] = tci;
27862306a36Sopenharmony_ci			break;
27962306a36Sopenharmony_ci		case 0x0100: /* VLAN ID 256 - 511 */
28062306a36Sopenharmony_ci			if (is_ip)
28162306a36Sopenharmony_ci				goto error;
28262306a36Sopenharmony_ci			sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN);
28362306a36Sopenharmony_ci			c = (u8 *)&sign;
28462306a36Sopenharmony_ci			c[3] = tci;
28562306a36Sopenharmony_ci			break;
28662306a36Sopenharmony_ci		default:
28762306a36Sopenharmony_ci			netif_err(dev, tx_err, dev->net,
28862306a36Sopenharmony_ci				  "unsupported tci=0x%04x\n", tci);
28962306a36Sopenharmony_ci			goto error;
29062306a36Sopenharmony_ci		}
29162306a36Sopenharmony_ci	}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	spin_lock_bh(&ctx->mtx);
29462306a36Sopenharmony_ci	skb_out = cdc_ncm_fill_tx_frame(dev, skb, sign);
29562306a36Sopenharmony_ci	spin_unlock_bh(&ctx->mtx);
29662306a36Sopenharmony_ci	return skb_out;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cierror:
29962306a36Sopenharmony_ci	if (skb)
30062306a36Sopenharmony_ci		dev_kfree_skb_any(skb);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	return NULL;
30362306a36Sopenharmony_ci}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci/* Some devices are known to send Neighbor Solicitation messages and
30662306a36Sopenharmony_ci * require Neighbor Advertisement replies.  The IPv6 core will not
30762306a36Sopenharmony_ci * respond since IFF_NOARP is set, so we must handle them ourselves.
30862306a36Sopenharmony_ci */
30962306a36Sopenharmony_cistatic void do_neigh_solicit(struct usbnet *dev, u8 *buf, u16 tci)
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	struct ipv6hdr *iph = (void *)buf;
31262306a36Sopenharmony_ci	struct nd_msg *msg = (void *)(iph + 1);
31362306a36Sopenharmony_ci	struct net_device *netdev;
31462306a36Sopenharmony_ci	struct inet6_dev *in6_dev;
31562306a36Sopenharmony_ci	bool is_router;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	/* we'll only respond to requests from unicast addresses to
31862306a36Sopenharmony_ci	 * our solicited node addresses.
31962306a36Sopenharmony_ci	 */
32062306a36Sopenharmony_ci	if (!ipv6_addr_is_solict_mult(&iph->daddr) ||
32162306a36Sopenharmony_ci	    !(ipv6_addr_type(&iph->saddr) & IPV6_ADDR_UNICAST))
32262306a36Sopenharmony_ci		return;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	/* need to send the NA on the VLAN dev, if any */
32562306a36Sopenharmony_ci	rcu_read_lock();
32662306a36Sopenharmony_ci	if (tci) {
32762306a36Sopenharmony_ci		netdev = __vlan_find_dev_deep_rcu(dev->net, htons(ETH_P_8021Q),
32862306a36Sopenharmony_ci						  tci);
32962306a36Sopenharmony_ci		if (!netdev) {
33062306a36Sopenharmony_ci			rcu_read_unlock();
33162306a36Sopenharmony_ci			return;
33262306a36Sopenharmony_ci		}
33362306a36Sopenharmony_ci	} else {
33462306a36Sopenharmony_ci		netdev = dev->net;
33562306a36Sopenharmony_ci	}
33662306a36Sopenharmony_ci	dev_hold(netdev);
33762306a36Sopenharmony_ci	rcu_read_unlock();
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	in6_dev = in6_dev_get(netdev);
34062306a36Sopenharmony_ci	if (!in6_dev)
34162306a36Sopenharmony_ci		goto out;
34262306a36Sopenharmony_ci	is_router = !!in6_dev->cnf.forwarding;
34362306a36Sopenharmony_ci	in6_dev_put(in6_dev);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/* ipv6_stub != NULL if in6_dev_get returned an inet6_dev */
34662306a36Sopenharmony_ci	ipv6_stub->ndisc_send_na(netdev, &iph->saddr, &msg->target,
34762306a36Sopenharmony_ci				 is_router /* router */,
34862306a36Sopenharmony_ci				 true /* solicited */,
34962306a36Sopenharmony_ci				 false /* override */,
35062306a36Sopenharmony_ci				 true /* inc_opt */);
35162306a36Sopenharmony_ciout:
35262306a36Sopenharmony_ci	dev_put(netdev);
35362306a36Sopenharmony_ci}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_cistatic bool is_neigh_solicit(u8 *buf, size_t len)
35662306a36Sopenharmony_ci{
35762306a36Sopenharmony_ci	struct ipv6hdr *iph = (void *)buf;
35862306a36Sopenharmony_ci	struct nd_msg *msg = (void *)(iph + 1);
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	return (len >= sizeof(struct ipv6hdr) + sizeof(struct nd_msg) &&
36162306a36Sopenharmony_ci		iph->nexthdr == IPPROTO_ICMPV6 &&
36262306a36Sopenharmony_ci		msg->icmph.icmp6_code == 0 &&
36362306a36Sopenharmony_ci		msg->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION);
36462306a36Sopenharmony_ci}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_cistatic struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci)
36862306a36Sopenharmony_ci{
36962306a36Sopenharmony_ci	__be16 proto = htons(ETH_P_802_3);
37062306a36Sopenharmony_ci	struct sk_buff *skb = NULL;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	if (tci < 256 || tci == MBIM_IPS0_VID) { /* IPS session? */
37362306a36Sopenharmony_ci		if (len < sizeof(struct iphdr))
37462306a36Sopenharmony_ci			goto err;
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci		switch (*buf & 0xf0) {
37762306a36Sopenharmony_ci		case 0x40:
37862306a36Sopenharmony_ci			proto = htons(ETH_P_IP);
37962306a36Sopenharmony_ci			break;
38062306a36Sopenharmony_ci		case 0x60:
38162306a36Sopenharmony_ci			if (is_neigh_solicit(buf, len))
38262306a36Sopenharmony_ci				do_neigh_solicit(dev, buf, tci);
38362306a36Sopenharmony_ci			proto = htons(ETH_P_IPV6);
38462306a36Sopenharmony_ci			break;
38562306a36Sopenharmony_ci		default:
38662306a36Sopenharmony_ci			goto err;
38762306a36Sopenharmony_ci		}
38862306a36Sopenharmony_ci	}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	skb = netdev_alloc_skb_ip_align(dev->net,  len + ETH_HLEN);
39162306a36Sopenharmony_ci	if (!skb)
39262306a36Sopenharmony_ci		goto err;
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	/* add an ethernet header */
39562306a36Sopenharmony_ci	skb_put(skb, ETH_HLEN);
39662306a36Sopenharmony_ci	skb_reset_mac_header(skb);
39762306a36Sopenharmony_ci	eth_hdr(skb)->h_proto = proto;
39862306a36Sopenharmony_ci	eth_zero_addr(eth_hdr(skb)->h_source);
39962306a36Sopenharmony_ci	memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	/* add datagram */
40262306a36Sopenharmony_ci	skb_put_data(skb, buf, len);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	/* map MBIM session to VLAN */
40562306a36Sopenharmony_ci	if (tci)
40662306a36Sopenharmony_ci		__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tci);
40762306a36Sopenharmony_cierr:
40862306a36Sopenharmony_ci	return skb;
40962306a36Sopenharmony_ci}
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cistatic int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
41262306a36Sopenharmony_ci{
41362306a36Sopenharmony_ci	struct sk_buff *skb;
41462306a36Sopenharmony_ci	struct cdc_mbim_state *info = (void *)&dev->data;
41562306a36Sopenharmony_ci	struct cdc_ncm_ctx *ctx = info->ctx;
41662306a36Sopenharmony_ci	int len;
41762306a36Sopenharmony_ci	int nframes;
41862306a36Sopenharmony_ci	int x;
41962306a36Sopenharmony_ci	int offset;
42062306a36Sopenharmony_ci	struct usb_cdc_ncm_ndp16 *ndp16;
42162306a36Sopenharmony_ci	struct usb_cdc_ncm_dpe16 *dpe16;
42262306a36Sopenharmony_ci	int ndpoffset;
42362306a36Sopenharmony_ci	int loopcount = 50; /* arbitrary max preventing infinite loop */
42462306a36Sopenharmony_ci	u32 payload = 0;
42562306a36Sopenharmony_ci	u8 *c;
42662306a36Sopenharmony_ci	u16 tci;
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in);
42962306a36Sopenharmony_ci	if (ndpoffset < 0)
43062306a36Sopenharmony_ci		goto error;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_cinext_ndp:
43362306a36Sopenharmony_ci	nframes = cdc_ncm_rx_verify_ndp16(skb_in, ndpoffset);
43462306a36Sopenharmony_ci	if (nframes < 0)
43562306a36Sopenharmony_ci		goto error;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset);
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	switch (ndp16->dwSignature & cpu_to_le32(0x00ffffff)) {
44062306a36Sopenharmony_ci	case cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN):
44162306a36Sopenharmony_ci		c = (u8 *)&ndp16->dwSignature;
44262306a36Sopenharmony_ci		tci = c[3];
44362306a36Sopenharmony_ci		/* tag IPS<0> packets too if MBIM_IPS0_VID exists */
44462306a36Sopenharmony_ci		if (!tci && info->flags & FLAG_IPS0_VLAN)
44562306a36Sopenharmony_ci			tci = MBIM_IPS0_VID;
44662306a36Sopenharmony_ci		break;
44762306a36Sopenharmony_ci	case cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN):
44862306a36Sopenharmony_ci		c = (u8 *)&ndp16->dwSignature;
44962306a36Sopenharmony_ci		tci = c[3] + 256;
45062306a36Sopenharmony_ci		break;
45162306a36Sopenharmony_ci	default:
45262306a36Sopenharmony_ci		netif_dbg(dev, rx_err, dev->net,
45362306a36Sopenharmony_ci			  "unsupported NDP signature <0x%08x>\n",
45462306a36Sopenharmony_ci			  le32_to_cpu(ndp16->dwSignature));
45562306a36Sopenharmony_ci		goto err_ndp;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	}
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	dpe16 = ndp16->dpe16;
46062306a36Sopenharmony_ci	for (x = 0; x < nframes; x++, dpe16++) {
46162306a36Sopenharmony_ci		offset = le16_to_cpu(dpe16->wDatagramIndex);
46262306a36Sopenharmony_ci		len = le16_to_cpu(dpe16->wDatagramLength);
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci		/*
46562306a36Sopenharmony_ci		 * CDC NCM ch. 3.7
46662306a36Sopenharmony_ci		 * All entries after first NULL entry are to be ignored
46762306a36Sopenharmony_ci		 */
46862306a36Sopenharmony_ci		if ((offset == 0) || (len == 0)) {
46962306a36Sopenharmony_ci			if (!x)
47062306a36Sopenharmony_ci				goto err_ndp; /* empty NTB */
47162306a36Sopenharmony_ci			break;
47262306a36Sopenharmony_ci		}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci		/* sanity checking */
47562306a36Sopenharmony_ci		if (((offset + len) > skb_in->len) || (len > ctx->rx_max)) {
47662306a36Sopenharmony_ci			netif_dbg(dev, rx_err, dev->net,
47762306a36Sopenharmony_ci				  "invalid frame detected (ignored) offset[%u]=%u, length=%u, skb=%p\n",
47862306a36Sopenharmony_ci				  x, offset, len, skb_in);
47962306a36Sopenharmony_ci			if (!x)
48062306a36Sopenharmony_ci				goto err_ndp;
48162306a36Sopenharmony_ci			break;
48262306a36Sopenharmony_ci		} else {
48362306a36Sopenharmony_ci			skb = cdc_mbim_process_dgram(dev, skb_in->data + offset, len, tci);
48462306a36Sopenharmony_ci			if (!skb)
48562306a36Sopenharmony_ci				goto error;
48662306a36Sopenharmony_ci			usbnet_skb_return(dev, skb);
48762306a36Sopenharmony_ci			payload += len;	/* count payload bytes in this NTB */
48862306a36Sopenharmony_ci		}
48962306a36Sopenharmony_ci	}
49062306a36Sopenharmony_cierr_ndp:
49162306a36Sopenharmony_ci	/* are there more NDPs to process? */
49262306a36Sopenharmony_ci	ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex);
49362306a36Sopenharmony_ci	if (ndpoffset && loopcount--)
49462306a36Sopenharmony_ci		goto next_ndp;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	/* update stats */
49762306a36Sopenharmony_ci	ctx->rx_overhead += skb_in->len - payload;
49862306a36Sopenharmony_ci	ctx->rx_ntbs++;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	return 1;
50162306a36Sopenharmony_cierror:
50262306a36Sopenharmony_ci	return 0;
50362306a36Sopenharmony_ci}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_cistatic int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message)
50662306a36Sopenharmony_ci{
50762306a36Sopenharmony_ci	int ret = -ENODEV;
50862306a36Sopenharmony_ci	struct usbnet *dev = usb_get_intfdata(intf);
50962306a36Sopenharmony_ci	struct cdc_mbim_state *info = (void *)&dev->data;
51062306a36Sopenharmony_ci	struct cdc_ncm_ctx *ctx = info->ctx;
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	if (!ctx)
51362306a36Sopenharmony_ci		goto error;
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	/*
51662306a36Sopenharmony_ci	 * Both usbnet_suspend() and subdriver->suspend() MUST return 0
51762306a36Sopenharmony_ci	 * in system sleep context, otherwise, the resume callback has
51862306a36Sopenharmony_ci	 * to recover device from previous suspend failure.
51962306a36Sopenharmony_ci	 */
52062306a36Sopenharmony_ci	ret = usbnet_suspend(intf, message);
52162306a36Sopenharmony_ci	if (ret < 0)
52262306a36Sopenharmony_ci		goto error;
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	if (intf == ctx->control && info->subdriver && info->subdriver->suspend)
52562306a36Sopenharmony_ci		ret = info->subdriver->suspend(intf, message);
52662306a36Sopenharmony_ci	if (ret < 0)
52762306a36Sopenharmony_ci		usbnet_resume(intf);
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_cierror:
53062306a36Sopenharmony_ci	return ret;
53162306a36Sopenharmony_ci}
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_cistatic int cdc_mbim_resume(struct usb_interface *intf)
53462306a36Sopenharmony_ci{
53562306a36Sopenharmony_ci	int  ret = 0;
53662306a36Sopenharmony_ci	struct usbnet *dev = usb_get_intfdata(intf);
53762306a36Sopenharmony_ci	struct cdc_mbim_state *info = (void *)&dev->data;
53862306a36Sopenharmony_ci	struct cdc_ncm_ctx *ctx = info->ctx;
53962306a36Sopenharmony_ci	bool callsub = (intf == ctx->control && info->subdriver && info->subdriver->resume);
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	if (callsub)
54262306a36Sopenharmony_ci		ret = info->subdriver->resume(intf);
54362306a36Sopenharmony_ci	if (ret < 0)
54462306a36Sopenharmony_ci		goto err;
54562306a36Sopenharmony_ci	ret = usbnet_resume(intf);
54662306a36Sopenharmony_ci	if (ret < 0 && callsub)
54762306a36Sopenharmony_ci		info->subdriver->suspend(intf, PMSG_SUSPEND);
54862306a36Sopenharmony_cierr:
54962306a36Sopenharmony_ci	return ret;
55062306a36Sopenharmony_ci}
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_cistatic const struct driver_info cdc_mbim_info = {
55362306a36Sopenharmony_ci	.description = "CDC MBIM",
55462306a36Sopenharmony_ci	.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN,
55562306a36Sopenharmony_ci	.bind = cdc_mbim_bind,
55662306a36Sopenharmony_ci	.unbind = cdc_mbim_unbind,
55762306a36Sopenharmony_ci	.manage_power = cdc_mbim_manage_power,
55862306a36Sopenharmony_ci	.rx_fixup = cdc_mbim_rx_fixup,
55962306a36Sopenharmony_ci	.tx_fixup = cdc_mbim_tx_fixup,
56062306a36Sopenharmony_ci};
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci/* MBIM and NCM devices should not need a ZLP after NTBs with
56362306a36Sopenharmony_ci * dwNtbOutMaxSize length. Nevertheless, a number of devices from
56462306a36Sopenharmony_ci * different vendor IDs will fail unless we send ZLPs, forcing us
56562306a36Sopenharmony_ci * to make this the default.
56662306a36Sopenharmony_ci *
56762306a36Sopenharmony_ci * This default may cause a performance penalty for spec conforming
56862306a36Sopenharmony_ci * devices wanting to take advantage of optimizations possible without
56962306a36Sopenharmony_ci * ZLPs.  A whitelist is added in an attempt to avoid this for devices
57062306a36Sopenharmony_ci * known to conform to the MBIM specification.
57162306a36Sopenharmony_ci *
57262306a36Sopenharmony_ci * All known devices supporting NCM compatibility mode are also
57362306a36Sopenharmony_ci * conforming to the NCM and MBIM specifications. For this reason, the
57462306a36Sopenharmony_ci * NCM subclass entry is also in the ZLP whitelist.
57562306a36Sopenharmony_ci */
57662306a36Sopenharmony_cistatic const struct driver_info cdc_mbim_info_zlp = {
57762306a36Sopenharmony_ci	.description = "CDC MBIM",
57862306a36Sopenharmony_ci	.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN | FLAG_SEND_ZLP,
57962306a36Sopenharmony_ci	.bind = cdc_mbim_bind,
58062306a36Sopenharmony_ci	.unbind = cdc_mbim_unbind,
58162306a36Sopenharmony_ci	.manage_power = cdc_mbim_manage_power,
58262306a36Sopenharmony_ci	.rx_fixup = cdc_mbim_rx_fixup,
58362306a36Sopenharmony_ci	.tx_fixup = cdc_mbim_tx_fixup,
58462306a36Sopenharmony_ci};
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci/* The spefication explicitly allows NDPs to be placed anywhere in the
58762306a36Sopenharmony_ci * frame, but some devices fail unless the NDP is placed after the IP
58862306a36Sopenharmony_ci * packets.  Using the CDC_NCM_FLAG_NDP_TO_END flags to force this
58962306a36Sopenharmony_ci * behaviour.
59062306a36Sopenharmony_ci *
59162306a36Sopenharmony_ci * Note: The current implementation of this feature restricts each NTB
59262306a36Sopenharmony_ci * to a single NDP, implying that multiplexed sessions cannot share an
59362306a36Sopenharmony_ci * NTB. This might affect performance for multiplexed sessions.
59462306a36Sopenharmony_ci */
59562306a36Sopenharmony_cistatic const struct driver_info cdc_mbim_info_ndp_to_end = {
59662306a36Sopenharmony_ci	.description = "CDC MBIM",
59762306a36Sopenharmony_ci	.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN,
59862306a36Sopenharmony_ci	.bind = cdc_mbim_bind,
59962306a36Sopenharmony_ci	.unbind = cdc_mbim_unbind,
60062306a36Sopenharmony_ci	.manage_power = cdc_mbim_manage_power,
60162306a36Sopenharmony_ci	.rx_fixup = cdc_mbim_rx_fixup,
60262306a36Sopenharmony_ci	.tx_fixup = cdc_mbim_tx_fixup,
60362306a36Sopenharmony_ci	.data = CDC_NCM_FLAG_NDP_TO_END,
60462306a36Sopenharmony_ci};
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_ci/* Some modems (e.g. Telit LE922A6) do not work properly with altsetting
60762306a36Sopenharmony_ci * toggle done in cdc_ncm_bind_common. CDC_MBIM_FLAG_AVOID_ALTSETTING_TOGGLE
60862306a36Sopenharmony_ci * flag is used to avoid this procedure.
60962306a36Sopenharmony_ci */
61062306a36Sopenharmony_cistatic const struct driver_info cdc_mbim_info_avoid_altsetting_toggle = {
61162306a36Sopenharmony_ci	.description = "CDC MBIM",
61262306a36Sopenharmony_ci	.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN | FLAG_SEND_ZLP,
61362306a36Sopenharmony_ci	.bind = cdc_mbim_bind,
61462306a36Sopenharmony_ci	.unbind = cdc_mbim_unbind,
61562306a36Sopenharmony_ci	.manage_power = cdc_mbim_manage_power,
61662306a36Sopenharmony_ci	.rx_fixup = cdc_mbim_rx_fixup,
61762306a36Sopenharmony_ci	.tx_fixup = cdc_mbim_tx_fixup,
61862306a36Sopenharmony_ci	.data = CDC_MBIM_FLAG_AVOID_ALTSETTING_TOGGLE,
61962306a36Sopenharmony_ci};
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_cistatic const struct usb_device_id mbim_devs[] = {
62262306a36Sopenharmony_ci	/* This duplicate NCM entry is intentional. MBIM devices can
62362306a36Sopenharmony_ci	 * be disguised as NCM by default, and this is necessary to
62462306a36Sopenharmony_ci	 * allow us to bind the correct driver_info to such devices.
62562306a36Sopenharmony_ci	 *
62662306a36Sopenharmony_ci	 * bind() will sort out this for us, selecting the correct
62762306a36Sopenharmony_ci	 * entry and reject the other
62862306a36Sopenharmony_ci	 */
62962306a36Sopenharmony_ci	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE),
63062306a36Sopenharmony_ci	  .driver_info = (unsigned long)&cdc_mbim_info,
63162306a36Sopenharmony_ci	},
63262306a36Sopenharmony_ci	/* ZLP conformance whitelist: All Ericsson MBIM devices */
63362306a36Sopenharmony_ci	{ USB_VENDOR_AND_INTERFACE_INFO(0x0bdb, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
63462306a36Sopenharmony_ci	  .driver_info = (unsigned long)&cdc_mbim_info,
63562306a36Sopenharmony_ci	},
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	/* Some Huawei devices, ME906s-158 (12d1:15c1) and E3372
63862306a36Sopenharmony_ci	 * (12d1:157d), are known to fail unless the NDP is placed
63962306a36Sopenharmony_ci	 * after the IP packets.  Applying the quirk to all Huawei
64062306a36Sopenharmony_ci	 * devices is broader than necessary, but harmless.
64162306a36Sopenharmony_ci	 */
64262306a36Sopenharmony_ci	{ USB_VENDOR_AND_INTERFACE_INFO(0x12d1, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
64362306a36Sopenharmony_ci	  .driver_info = (unsigned long)&cdc_mbim_info_ndp_to_end,
64462306a36Sopenharmony_ci	},
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	/* The HP lt4132 (03f0:a31d) is a rebranded Huawei ME906s-158,
64762306a36Sopenharmony_ci	 * therefore it too requires the above "NDP to end" quirk.
64862306a36Sopenharmony_ci	 */
64962306a36Sopenharmony_ci	{ USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
65062306a36Sopenharmony_ci	  .driver_info = (unsigned long)&cdc_mbim_info_ndp_to_end,
65162306a36Sopenharmony_ci	},
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	/* Telit LE922A6 in MBIM composition */
65462306a36Sopenharmony_ci	{ USB_DEVICE_AND_INTERFACE_INFO(0x1bc7, 0x1041, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
65562306a36Sopenharmony_ci	  .driver_info = (unsigned long)&cdc_mbim_info_avoid_altsetting_toggle,
65662306a36Sopenharmony_ci	},
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	/* Telit LN920 */
65962306a36Sopenharmony_ci	{ USB_DEVICE_AND_INTERFACE_INFO(0x1bc7, 0x1061, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
66062306a36Sopenharmony_ci	  .driver_info = (unsigned long)&cdc_mbim_info_avoid_altsetting_toggle,
66162306a36Sopenharmony_ci	},
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci	/* Telit FN990 */
66462306a36Sopenharmony_ci	{ USB_DEVICE_AND_INTERFACE_INFO(0x1bc7, 0x1071, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
66562306a36Sopenharmony_ci	  .driver_info = (unsigned long)&cdc_mbim_info_avoid_altsetting_toggle,
66662306a36Sopenharmony_ci	},
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_ci	/* Telit FE990 */
66962306a36Sopenharmony_ci	{ USB_DEVICE_AND_INTERFACE_INFO(0x1bc7, 0x1081, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
67062306a36Sopenharmony_ci	  .driver_info = (unsigned long)&cdc_mbim_info_avoid_altsetting_toggle,
67162306a36Sopenharmony_ci	},
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	/* default entry */
67462306a36Sopenharmony_ci	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
67562306a36Sopenharmony_ci	  .driver_info = (unsigned long)&cdc_mbim_info_zlp,
67662306a36Sopenharmony_ci	},
67762306a36Sopenharmony_ci	{
67862306a36Sopenharmony_ci	},
67962306a36Sopenharmony_ci};
68062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, mbim_devs);
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_cistatic struct usb_driver cdc_mbim_driver = {
68362306a36Sopenharmony_ci	.name = "cdc_mbim",
68462306a36Sopenharmony_ci	.id_table = mbim_devs,
68562306a36Sopenharmony_ci	.probe = usbnet_probe,
68662306a36Sopenharmony_ci	.disconnect = usbnet_disconnect,
68762306a36Sopenharmony_ci	.suspend = cdc_mbim_suspend,
68862306a36Sopenharmony_ci	.resume = cdc_mbim_resume,
68962306a36Sopenharmony_ci	.reset_resume =	cdc_mbim_resume,
69062306a36Sopenharmony_ci	.supports_autosuspend = 1,
69162306a36Sopenharmony_ci	.disable_hub_initiated_lpm = 1,
69262306a36Sopenharmony_ci};
69362306a36Sopenharmony_cimodule_usb_driver(cdc_mbim_driver);
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ciMODULE_AUTHOR("Greg Suarez <gsuarez@smithmicro.com>");
69662306a36Sopenharmony_ciMODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>");
69762306a36Sopenharmony_ciMODULE_DESCRIPTION("USB CDC MBIM host driver");
69862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
699