18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2012 Smith Micro Software, Inc. 48c2ecf20Sopenharmony_ci * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * This driver is based on and reuse most of cdc_ncm, which is 78c2ecf20Sopenharmony_ci * Copyright (C) ST-Ericsson 2010-2012 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/netdevice.h> 128c2ecf20Sopenharmony_ci#include <linux/ethtool.h> 138c2ecf20Sopenharmony_ci#include <linux/if_vlan.h> 148c2ecf20Sopenharmony_ci#include <linux/ip.h> 158c2ecf20Sopenharmony_ci#include <linux/mii.h> 168c2ecf20Sopenharmony_ci#include <linux/usb.h> 178c2ecf20Sopenharmony_ci#include <linux/usb/cdc.h> 188c2ecf20Sopenharmony_ci#include <linux/usb/usbnet.h> 198c2ecf20Sopenharmony_ci#include <linux/usb/cdc-wdm.h> 208c2ecf20Sopenharmony_ci#include <linux/usb/cdc_ncm.h> 218c2ecf20Sopenharmony_ci#include <net/ipv6.h> 228c2ecf20Sopenharmony_ci#include <net/addrconf.h> 238c2ecf20Sopenharmony_ci#include <net/ipv6_stubs.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* alternative VLAN for IP session 0 if not untagged */ 268c2ecf20Sopenharmony_ci#define MBIM_IPS0_VID 4094 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* driver specific data - must match cdc_ncm usage */ 298c2ecf20Sopenharmony_cistruct cdc_mbim_state { 308c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx; 318c2ecf20Sopenharmony_ci atomic_t pmcount; 328c2ecf20Sopenharmony_ci struct usb_driver *subdriver; 338c2ecf20Sopenharmony_ci unsigned long _unused; 348c2ecf20Sopenharmony_ci unsigned long flags; 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* flags for the cdc_mbim_state.flags field */ 388c2ecf20Sopenharmony_cienum cdc_mbim_flags { 398c2ecf20Sopenharmony_ci FLAG_IPS0_VLAN = 1 << 0, /* IP session 0 is tagged */ 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci/* using a counter to merge subdriver requests with our own into a combined state */ 438c2ecf20Sopenharmony_cistatic int cdc_mbim_manage_power(struct usbnet *dev, int on) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci struct cdc_mbim_state *info = (void *)&dev->data; 468c2ecf20Sopenharmony_ci int rv = 0; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, atomic_read(&info->pmcount), on); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci if ((on && atomic_add_return(1, &info->pmcount) == 1) || (!on && atomic_dec_and_test(&info->pmcount))) { 518c2ecf20Sopenharmony_ci /* need autopm_get/put here to ensure the usbcore sees the new value */ 528c2ecf20Sopenharmony_ci rv = usb_autopm_get_interface(dev->intf); 538c2ecf20Sopenharmony_ci dev->intf->needs_remote_wakeup = on; 548c2ecf20Sopenharmony_ci if (!rv) 558c2ecf20Sopenharmony_ci usb_autopm_put_interface(dev->intf); 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci return 0; 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct usbnet *dev = usb_get_intfdata(intf); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci /* can be called while disconnecting */ 658c2ecf20Sopenharmony_ci if (!dev) 668c2ecf20Sopenharmony_ci return 0; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci return cdc_mbim_manage_power(dev, status); 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic int cdc_mbim_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci struct usbnet *dev = netdev_priv(netdev); 748c2ecf20Sopenharmony_ci struct cdc_mbim_state *info = (void *)&dev->data; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci /* creation of this VLAN is a request to tag IP session 0 */ 778c2ecf20Sopenharmony_ci if (vid == MBIM_IPS0_VID) 788c2ecf20Sopenharmony_ci info->flags |= FLAG_IPS0_VLAN; 798c2ecf20Sopenharmony_ci else 808c2ecf20Sopenharmony_ci if (vid >= 512) /* we don't map these to MBIM session */ 818c2ecf20Sopenharmony_ci return -EINVAL; 828c2ecf20Sopenharmony_ci return 0; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic int cdc_mbim_rx_kill_vid(struct net_device *netdev, __be16 proto, u16 vid) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci struct usbnet *dev = netdev_priv(netdev); 888c2ecf20Sopenharmony_ci struct cdc_mbim_state *info = (void *)&dev->data; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci /* this is a request for an untagged IP session 0 */ 918c2ecf20Sopenharmony_ci if (vid == MBIM_IPS0_VID) 928c2ecf20Sopenharmony_ci info->flags &= ~FLAG_IPS0_VLAN; 938c2ecf20Sopenharmony_ci return 0; 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic const struct net_device_ops cdc_mbim_netdev_ops = { 978c2ecf20Sopenharmony_ci .ndo_open = usbnet_open, 988c2ecf20Sopenharmony_ci .ndo_stop = usbnet_stop, 998c2ecf20Sopenharmony_ci .ndo_start_xmit = usbnet_start_xmit, 1008c2ecf20Sopenharmony_ci .ndo_tx_timeout = usbnet_tx_timeout, 1018c2ecf20Sopenharmony_ci .ndo_get_stats64 = usbnet_get_stats64, 1028c2ecf20Sopenharmony_ci .ndo_change_mtu = cdc_ncm_change_mtu, 1038c2ecf20Sopenharmony_ci .ndo_set_mac_address = eth_mac_addr, 1048c2ecf20Sopenharmony_ci .ndo_validate_addr = eth_validate_addr, 1058c2ecf20Sopenharmony_ci .ndo_vlan_rx_add_vid = cdc_mbim_rx_add_vid, 1068c2ecf20Sopenharmony_ci .ndo_vlan_rx_kill_vid = cdc_mbim_rx_kill_vid, 1078c2ecf20Sopenharmony_ci}; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci/* Change the control interface altsetting and update the .driver_info 1108c2ecf20Sopenharmony_ci * pointer if the matching entry after changing class codes points to 1118c2ecf20Sopenharmony_ci * a different struct 1128c2ecf20Sopenharmony_ci */ 1138c2ecf20Sopenharmony_cistatic int cdc_mbim_set_ctrlalt(struct usbnet *dev, struct usb_interface *intf, u8 alt) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct usb_driver *driver = to_usb_driver(intf->dev.driver); 1168c2ecf20Sopenharmony_ci const struct usb_device_id *id; 1178c2ecf20Sopenharmony_ci struct driver_info *info; 1188c2ecf20Sopenharmony_ci int ret; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci ret = usb_set_interface(dev->udev, 1218c2ecf20Sopenharmony_ci intf->cur_altsetting->desc.bInterfaceNumber, 1228c2ecf20Sopenharmony_ci alt); 1238c2ecf20Sopenharmony_ci if (ret) 1248c2ecf20Sopenharmony_ci return ret; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci id = usb_match_id(intf, driver->id_table); 1278c2ecf20Sopenharmony_ci if (!id) 1288c2ecf20Sopenharmony_ci return -ENODEV; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci info = (struct driver_info *)id->driver_info; 1318c2ecf20Sopenharmony_ci if (info != dev->driver_info) { 1328c2ecf20Sopenharmony_ci dev_dbg(&intf->dev, "driver_info updated to '%s'\n", 1338c2ecf20Sopenharmony_ci info->description); 1348c2ecf20Sopenharmony_ci dev->driver_info = info; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci return 0; 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistatic int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx; 1428c2ecf20Sopenharmony_ci struct usb_driver *subdriver = ERR_PTR(-ENODEV); 1438c2ecf20Sopenharmony_ci int ret = -ENODEV; 1448c2ecf20Sopenharmony_ci u8 data_altsetting = 1; 1458c2ecf20Sopenharmony_ci struct cdc_mbim_state *info = (void *)&dev->data; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* should we change control altsetting on a NCM/MBIM function? */ 1488c2ecf20Sopenharmony_ci if (cdc_ncm_select_altsetting(intf) == CDC_NCM_COMM_ALTSETTING_MBIM) { 1498c2ecf20Sopenharmony_ci data_altsetting = CDC_NCM_DATA_ALTSETTING_MBIM; 1508c2ecf20Sopenharmony_ci ret = cdc_mbim_set_ctrlalt(dev, intf, CDC_NCM_COMM_ALTSETTING_MBIM); 1518c2ecf20Sopenharmony_ci if (ret) 1528c2ecf20Sopenharmony_ci goto err; 1538c2ecf20Sopenharmony_ci ret = -ENODEV; 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci /* we will hit this for NCM/MBIM functions if prefer_mbim is false */ 1578c2ecf20Sopenharmony_ci if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) 1588c2ecf20Sopenharmony_ci goto err; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci ret = cdc_ncm_bind_common(dev, intf, data_altsetting, dev->driver_info->data); 1618c2ecf20Sopenharmony_ci if (ret) 1628c2ecf20Sopenharmony_ci goto err; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci ctx = info->ctx; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci /* The MBIM descriptor and the status endpoint are required */ 1678c2ecf20Sopenharmony_ci if (ctx->mbim_desc && dev->status) 1688c2ecf20Sopenharmony_ci subdriver = usb_cdc_wdm_register(ctx->control, 1698c2ecf20Sopenharmony_ci &dev->status->desc, 1708c2ecf20Sopenharmony_ci le16_to_cpu(ctx->mbim_desc->wMaxControlMessage), 1718c2ecf20Sopenharmony_ci cdc_mbim_wdm_manage_power); 1728c2ecf20Sopenharmony_ci if (IS_ERR(subdriver)) { 1738c2ecf20Sopenharmony_ci ret = PTR_ERR(subdriver); 1748c2ecf20Sopenharmony_ci cdc_ncm_unbind(dev, intf); 1758c2ecf20Sopenharmony_ci goto err; 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci /* can't let usbnet use the interrupt endpoint */ 1798c2ecf20Sopenharmony_ci dev->status = NULL; 1808c2ecf20Sopenharmony_ci info->subdriver = subdriver; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci /* MBIM cannot do ARP */ 1838c2ecf20Sopenharmony_ci dev->net->flags |= IFF_NOARP; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci /* no need to put the VLAN tci in the packet headers */ 1868c2ecf20Sopenharmony_ci dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_FILTER; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci /* monitor VLAN additions and removals */ 1898c2ecf20Sopenharmony_ci dev->net->netdev_ops = &cdc_mbim_netdev_ops; 1908c2ecf20Sopenharmony_cierr: 1918c2ecf20Sopenharmony_ci return ret; 1928c2ecf20Sopenharmony_ci} 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic void cdc_mbim_unbind(struct usbnet *dev, struct usb_interface *intf) 1958c2ecf20Sopenharmony_ci{ 1968c2ecf20Sopenharmony_ci struct cdc_mbim_state *info = (void *)&dev->data; 1978c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx = info->ctx; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci /* disconnect subdriver from control interface */ 2008c2ecf20Sopenharmony_ci if (info->subdriver && info->subdriver->disconnect) 2018c2ecf20Sopenharmony_ci info->subdriver->disconnect(ctx->control); 2028c2ecf20Sopenharmony_ci info->subdriver = NULL; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci /* let NCM unbind clean up both control and data interface */ 2058c2ecf20Sopenharmony_ci cdc_ncm_unbind(dev, intf); 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci/* verify that the ethernet protocol is IPv4 or IPv6 */ 2098c2ecf20Sopenharmony_cistatic bool is_ip_proto(__be16 proto) 2108c2ecf20Sopenharmony_ci{ 2118c2ecf20Sopenharmony_ci switch (proto) { 2128c2ecf20Sopenharmony_ci case htons(ETH_P_IP): 2138c2ecf20Sopenharmony_ci case htons(ETH_P_IPV6): 2148c2ecf20Sopenharmony_ci return true; 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci return false; 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci struct sk_buff *skb_out; 2228c2ecf20Sopenharmony_ci struct cdc_mbim_state *info = (void *)&dev->data; 2238c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx = info->ctx; 2248c2ecf20Sopenharmony_ci __le32 sign = cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN); 2258c2ecf20Sopenharmony_ci u16 tci = 0; 2268c2ecf20Sopenharmony_ci bool is_ip; 2278c2ecf20Sopenharmony_ci u8 *c; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci if (!ctx) 2308c2ecf20Sopenharmony_ci goto error; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci if (skb) { 2338c2ecf20Sopenharmony_ci if (skb->len <= ETH_HLEN) 2348c2ecf20Sopenharmony_ci goto error; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci /* Some applications using e.g. packet sockets will 2378c2ecf20Sopenharmony_ci * bypass the VLAN acceleration and create tagged 2388c2ecf20Sopenharmony_ci * ethernet frames directly. We primarily look for 2398c2ecf20Sopenharmony_ci * the accelerated out-of-band tag, but fall back if 2408c2ecf20Sopenharmony_ci * required 2418c2ecf20Sopenharmony_ci */ 2428c2ecf20Sopenharmony_ci skb_reset_mac_header(skb); 2438c2ecf20Sopenharmony_ci if (vlan_get_tag(skb, &tci) < 0 && skb->len > VLAN_ETH_HLEN && 2448c2ecf20Sopenharmony_ci __vlan_get_tag(skb, &tci) == 0) { 2458c2ecf20Sopenharmony_ci is_ip = is_ip_proto(vlan_eth_hdr(skb)->h_vlan_encapsulated_proto); 2468c2ecf20Sopenharmony_ci skb_pull(skb, VLAN_ETH_HLEN); 2478c2ecf20Sopenharmony_ci } else { 2488c2ecf20Sopenharmony_ci is_ip = is_ip_proto(eth_hdr(skb)->h_proto); 2498c2ecf20Sopenharmony_ci skb_pull(skb, ETH_HLEN); 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci /* Is IP session <0> tagged too? */ 2538c2ecf20Sopenharmony_ci if (info->flags & FLAG_IPS0_VLAN) { 2548c2ecf20Sopenharmony_ci /* drop all untagged packets */ 2558c2ecf20Sopenharmony_ci if (!tci) 2568c2ecf20Sopenharmony_ci goto error; 2578c2ecf20Sopenharmony_ci /* map MBIM_IPS0_VID to IPS<0> */ 2588c2ecf20Sopenharmony_ci if (tci == MBIM_IPS0_VID) 2598c2ecf20Sopenharmony_ci tci = 0; 2608c2ecf20Sopenharmony_ci } 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci /* mapping VLANs to MBIM sessions: 2638c2ecf20Sopenharmony_ci * no tag => IPS session <0> if !FLAG_IPS0_VLAN 2648c2ecf20Sopenharmony_ci * 1 - 255 => IPS session <vlanid> 2658c2ecf20Sopenharmony_ci * 256 - 511 => DSS session <vlanid - 256> 2668c2ecf20Sopenharmony_ci * 512 - 4093 => unsupported, drop 2678c2ecf20Sopenharmony_ci * 4094 => IPS session <0> if FLAG_IPS0_VLAN 2688c2ecf20Sopenharmony_ci */ 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci switch (tci & 0x0f00) { 2718c2ecf20Sopenharmony_ci case 0x0000: /* VLAN ID 0 - 255 */ 2728c2ecf20Sopenharmony_ci if (!is_ip) 2738c2ecf20Sopenharmony_ci goto error; 2748c2ecf20Sopenharmony_ci c = (u8 *)&sign; 2758c2ecf20Sopenharmony_ci c[3] = tci; 2768c2ecf20Sopenharmony_ci break; 2778c2ecf20Sopenharmony_ci case 0x0100: /* VLAN ID 256 - 511 */ 2788c2ecf20Sopenharmony_ci if (is_ip) 2798c2ecf20Sopenharmony_ci goto error; 2808c2ecf20Sopenharmony_ci sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN); 2818c2ecf20Sopenharmony_ci c = (u8 *)&sign; 2828c2ecf20Sopenharmony_ci c[3] = tci; 2838c2ecf20Sopenharmony_ci break; 2848c2ecf20Sopenharmony_ci default: 2858c2ecf20Sopenharmony_ci netif_err(dev, tx_err, dev->net, 2868c2ecf20Sopenharmony_ci "unsupported tci=0x%04x\n", tci); 2878c2ecf20Sopenharmony_ci goto error; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci } 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci spin_lock_bh(&ctx->mtx); 2928c2ecf20Sopenharmony_ci skb_out = cdc_ncm_fill_tx_frame(dev, skb, sign); 2938c2ecf20Sopenharmony_ci spin_unlock_bh(&ctx->mtx); 2948c2ecf20Sopenharmony_ci return skb_out; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cierror: 2978c2ecf20Sopenharmony_ci if (skb) 2988c2ecf20Sopenharmony_ci dev_kfree_skb_any(skb); 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci return NULL; 3018c2ecf20Sopenharmony_ci} 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci/* Some devices are known to send Neigbor Solicitation messages and 3048c2ecf20Sopenharmony_ci * require Neigbor Advertisement replies. The IPv6 core will not 3058c2ecf20Sopenharmony_ci * respond since IFF_NOARP is set, so we must handle them ourselves. 3068c2ecf20Sopenharmony_ci */ 3078c2ecf20Sopenharmony_cistatic void do_neigh_solicit(struct usbnet *dev, u8 *buf, u16 tci) 3088c2ecf20Sopenharmony_ci{ 3098c2ecf20Sopenharmony_ci struct ipv6hdr *iph = (void *)buf; 3108c2ecf20Sopenharmony_ci struct nd_msg *msg = (void *)(iph + 1); 3118c2ecf20Sopenharmony_ci struct net_device *netdev; 3128c2ecf20Sopenharmony_ci struct inet6_dev *in6_dev; 3138c2ecf20Sopenharmony_ci bool is_router; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci /* we'll only respond to requests from unicast addresses to 3168c2ecf20Sopenharmony_ci * our solicited node addresses. 3178c2ecf20Sopenharmony_ci */ 3188c2ecf20Sopenharmony_ci if (!ipv6_addr_is_solict_mult(&iph->daddr) || 3198c2ecf20Sopenharmony_ci !(ipv6_addr_type(&iph->saddr) & IPV6_ADDR_UNICAST)) 3208c2ecf20Sopenharmony_ci return; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci /* need to send the NA on the VLAN dev, if any */ 3238c2ecf20Sopenharmony_ci rcu_read_lock(); 3248c2ecf20Sopenharmony_ci if (tci) { 3258c2ecf20Sopenharmony_ci netdev = __vlan_find_dev_deep_rcu(dev->net, htons(ETH_P_8021Q), 3268c2ecf20Sopenharmony_ci tci); 3278c2ecf20Sopenharmony_ci if (!netdev) { 3288c2ecf20Sopenharmony_ci rcu_read_unlock(); 3298c2ecf20Sopenharmony_ci return; 3308c2ecf20Sopenharmony_ci } 3318c2ecf20Sopenharmony_ci } else { 3328c2ecf20Sopenharmony_ci netdev = dev->net; 3338c2ecf20Sopenharmony_ci } 3348c2ecf20Sopenharmony_ci dev_hold(netdev); 3358c2ecf20Sopenharmony_ci rcu_read_unlock(); 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci in6_dev = in6_dev_get(netdev); 3388c2ecf20Sopenharmony_ci if (!in6_dev) 3398c2ecf20Sopenharmony_ci goto out; 3408c2ecf20Sopenharmony_ci is_router = !!in6_dev->cnf.forwarding; 3418c2ecf20Sopenharmony_ci in6_dev_put(in6_dev); 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci /* ipv6_stub != NULL if in6_dev_get returned an inet6_dev */ 3448c2ecf20Sopenharmony_ci ipv6_stub->ndisc_send_na(netdev, &iph->saddr, &msg->target, 3458c2ecf20Sopenharmony_ci is_router /* router */, 3468c2ecf20Sopenharmony_ci true /* solicited */, 3478c2ecf20Sopenharmony_ci false /* override */, 3488c2ecf20Sopenharmony_ci true /* inc_opt */); 3498c2ecf20Sopenharmony_ciout: 3508c2ecf20Sopenharmony_ci dev_put(netdev); 3518c2ecf20Sopenharmony_ci} 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_cistatic bool is_neigh_solicit(u8 *buf, size_t len) 3548c2ecf20Sopenharmony_ci{ 3558c2ecf20Sopenharmony_ci struct ipv6hdr *iph = (void *)buf; 3568c2ecf20Sopenharmony_ci struct nd_msg *msg = (void *)(iph + 1); 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci return (len >= sizeof(struct ipv6hdr) + sizeof(struct nd_msg) && 3598c2ecf20Sopenharmony_ci iph->nexthdr == IPPROTO_ICMPV6 && 3608c2ecf20Sopenharmony_ci msg->icmph.icmp6_code == 0 && 3618c2ecf20Sopenharmony_ci msg->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION); 3628c2ecf20Sopenharmony_ci} 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_cistatic struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci) 3668c2ecf20Sopenharmony_ci{ 3678c2ecf20Sopenharmony_ci __be16 proto = htons(ETH_P_802_3); 3688c2ecf20Sopenharmony_ci struct sk_buff *skb = NULL; 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci if (tci < 256 || tci == MBIM_IPS0_VID) { /* IPS session? */ 3718c2ecf20Sopenharmony_ci if (len < sizeof(struct iphdr)) 3728c2ecf20Sopenharmony_ci goto err; 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci switch (*buf & 0xf0) { 3758c2ecf20Sopenharmony_ci case 0x40: 3768c2ecf20Sopenharmony_ci proto = htons(ETH_P_IP); 3778c2ecf20Sopenharmony_ci break; 3788c2ecf20Sopenharmony_ci case 0x60: 3798c2ecf20Sopenharmony_ci if (is_neigh_solicit(buf, len)) 3808c2ecf20Sopenharmony_ci do_neigh_solicit(dev, buf, tci); 3818c2ecf20Sopenharmony_ci proto = htons(ETH_P_IPV6); 3828c2ecf20Sopenharmony_ci break; 3838c2ecf20Sopenharmony_ci default: 3848c2ecf20Sopenharmony_ci goto err; 3858c2ecf20Sopenharmony_ci } 3868c2ecf20Sopenharmony_ci } 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci skb = netdev_alloc_skb_ip_align(dev->net, len + ETH_HLEN); 3898c2ecf20Sopenharmony_ci if (!skb) 3908c2ecf20Sopenharmony_ci goto err; 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci /* add an ethernet header */ 3938c2ecf20Sopenharmony_ci skb_put(skb, ETH_HLEN); 3948c2ecf20Sopenharmony_ci skb_reset_mac_header(skb); 3958c2ecf20Sopenharmony_ci eth_hdr(skb)->h_proto = proto; 3968c2ecf20Sopenharmony_ci eth_zero_addr(eth_hdr(skb)->h_source); 3978c2ecf20Sopenharmony_ci memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_ci /* add datagram */ 4008c2ecf20Sopenharmony_ci skb_put_data(skb, buf, len); 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci /* map MBIM session to VLAN */ 4038c2ecf20Sopenharmony_ci if (tci) 4048c2ecf20Sopenharmony_ci __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tci); 4058c2ecf20Sopenharmony_cierr: 4068c2ecf20Sopenharmony_ci return skb; 4078c2ecf20Sopenharmony_ci} 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_cistatic int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in) 4108c2ecf20Sopenharmony_ci{ 4118c2ecf20Sopenharmony_ci struct sk_buff *skb; 4128c2ecf20Sopenharmony_ci struct cdc_mbim_state *info = (void *)&dev->data; 4138c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx = info->ctx; 4148c2ecf20Sopenharmony_ci int len; 4158c2ecf20Sopenharmony_ci int nframes; 4168c2ecf20Sopenharmony_ci int x; 4178c2ecf20Sopenharmony_ci int offset; 4188c2ecf20Sopenharmony_ci struct usb_cdc_ncm_ndp16 *ndp16; 4198c2ecf20Sopenharmony_ci struct usb_cdc_ncm_dpe16 *dpe16; 4208c2ecf20Sopenharmony_ci int ndpoffset; 4218c2ecf20Sopenharmony_ci int loopcount = 50; /* arbitrary max preventing infinite loop */ 4228c2ecf20Sopenharmony_ci u32 payload = 0; 4238c2ecf20Sopenharmony_ci u8 *c; 4248c2ecf20Sopenharmony_ci u16 tci; 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in); 4278c2ecf20Sopenharmony_ci if (ndpoffset < 0) 4288c2ecf20Sopenharmony_ci goto error; 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_cinext_ndp: 4318c2ecf20Sopenharmony_ci nframes = cdc_ncm_rx_verify_ndp16(skb_in, ndpoffset); 4328c2ecf20Sopenharmony_ci if (nframes < 0) 4338c2ecf20Sopenharmony_ci goto error; 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset); 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci switch (ndp16->dwSignature & cpu_to_le32(0x00ffffff)) { 4388c2ecf20Sopenharmony_ci case cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN): 4398c2ecf20Sopenharmony_ci c = (u8 *)&ndp16->dwSignature; 4408c2ecf20Sopenharmony_ci tci = c[3]; 4418c2ecf20Sopenharmony_ci /* tag IPS<0> packets too if MBIM_IPS0_VID exists */ 4428c2ecf20Sopenharmony_ci if (!tci && info->flags & FLAG_IPS0_VLAN) 4438c2ecf20Sopenharmony_ci tci = MBIM_IPS0_VID; 4448c2ecf20Sopenharmony_ci break; 4458c2ecf20Sopenharmony_ci case cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN): 4468c2ecf20Sopenharmony_ci c = (u8 *)&ndp16->dwSignature; 4478c2ecf20Sopenharmony_ci tci = c[3] + 256; 4488c2ecf20Sopenharmony_ci break; 4498c2ecf20Sopenharmony_ci default: 4508c2ecf20Sopenharmony_ci netif_dbg(dev, rx_err, dev->net, 4518c2ecf20Sopenharmony_ci "unsupported NDP signature <0x%08x>\n", 4528c2ecf20Sopenharmony_ci le32_to_cpu(ndp16->dwSignature)); 4538c2ecf20Sopenharmony_ci goto err_ndp; 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci } 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci dpe16 = ndp16->dpe16; 4588c2ecf20Sopenharmony_ci for (x = 0; x < nframes; x++, dpe16++) { 4598c2ecf20Sopenharmony_ci offset = le16_to_cpu(dpe16->wDatagramIndex); 4608c2ecf20Sopenharmony_ci len = le16_to_cpu(dpe16->wDatagramLength); 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci /* 4638c2ecf20Sopenharmony_ci * CDC NCM ch. 3.7 4648c2ecf20Sopenharmony_ci * All entries after first NULL entry are to be ignored 4658c2ecf20Sopenharmony_ci */ 4668c2ecf20Sopenharmony_ci if ((offset == 0) || (len == 0)) { 4678c2ecf20Sopenharmony_ci if (!x) 4688c2ecf20Sopenharmony_ci goto err_ndp; /* empty NTB */ 4698c2ecf20Sopenharmony_ci break; 4708c2ecf20Sopenharmony_ci } 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci /* sanity checking */ 4738c2ecf20Sopenharmony_ci if (((offset + len) > skb_in->len) || (len > ctx->rx_max)) { 4748c2ecf20Sopenharmony_ci netif_dbg(dev, rx_err, dev->net, 4758c2ecf20Sopenharmony_ci "invalid frame detected (ignored) offset[%u]=%u, length=%u, skb=%p\n", 4768c2ecf20Sopenharmony_ci x, offset, len, skb_in); 4778c2ecf20Sopenharmony_ci if (!x) 4788c2ecf20Sopenharmony_ci goto err_ndp; 4798c2ecf20Sopenharmony_ci break; 4808c2ecf20Sopenharmony_ci } else { 4818c2ecf20Sopenharmony_ci skb = cdc_mbim_process_dgram(dev, skb_in->data + offset, len, tci); 4828c2ecf20Sopenharmony_ci if (!skb) 4838c2ecf20Sopenharmony_ci goto error; 4848c2ecf20Sopenharmony_ci usbnet_skb_return(dev, skb); 4858c2ecf20Sopenharmony_ci payload += len; /* count payload bytes in this NTB */ 4868c2ecf20Sopenharmony_ci } 4878c2ecf20Sopenharmony_ci } 4888c2ecf20Sopenharmony_cierr_ndp: 4898c2ecf20Sopenharmony_ci /* are there more NDPs to process? */ 4908c2ecf20Sopenharmony_ci ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); 4918c2ecf20Sopenharmony_ci if (ndpoffset && loopcount--) 4928c2ecf20Sopenharmony_ci goto next_ndp; 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci /* update stats */ 4958c2ecf20Sopenharmony_ci ctx->rx_overhead += skb_in->len - payload; 4968c2ecf20Sopenharmony_ci ctx->rx_ntbs++; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci return 1; 4998c2ecf20Sopenharmony_cierror: 5008c2ecf20Sopenharmony_ci return 0; 5018c2ecf20Sopenharmony_ci} 5028c2ecf20Sopenharmony_ci 5038c2ecf20Sopenharmony_cistatic int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message) 5048c2ecf20Sopenharmony_ci{ 5058c2ecf20Sopenharmony_ci int ret = -ENODEV; 5068c2ecf20Sopenharmony_ci struct usbnet *dev = usb_get_intfdata(intf); 5078c2ecf20Sopenharmony_ci struct cdc_mbim_state *info = (void *)&dev->data; 5088c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx = info->ctx; 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci if (!ctx) 5118c2ecf20Sopenharmony_ci goto error; 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci /* 5148c2ecf20Sopenharmony_ci * Both usbnet_suspend() and subdriver->suspend() MUST return 0 5158c2ecf20Sopenharmony_ci * in system sleep context, otherwise, the resume callback has 5168c2ecf20Sopenharmony_ci * to recover device from previous suspend failure. 5178c2ecf20Sopenharmony_ci */ 5188c2ecf20Sopenharmony_ci ret = usbnet_suspend(intf, message); 5198c2ecf20Sopenharmony_ci if (ret < 0) 5208c2ecf20Sopenharmony_ci goto error; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci if (intf == ctx->control && info->subdriver && info->subdriver->suspend) 5238c2ecf20Sopenharmony_ci ret = info->subdriver->suspend(intf, message); 5248c2ecf20Sopenharmony_ci if (ret < 0) 5258c2ecf20Sopenharmony_ci usbnet_resume(intf); 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_cierror: 5288c2ecf20Sopenharmony_ci return ret; 5298c2ecf20Sopenharmony_ci} 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_cistatic int cdc_mbim_resume(struct usb_interface *intf) 5328c2ecf20Sopenharmony_ci{ 5338c2ecf20Sopenharmony_ci int ret = 0; 5348c2ecf20Sopenharmony_ci struct usbnet *dev = usb_get_intfdata(intf); 5358c2ecf20Sopenharmony_ci struct cdc_mbim_state *info = (void *)&dev->data; 5368c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx = info->ctx; 5378c2ecf20Sopenharmony_ci bool callsub = (intf == ctx->control && info->subdriver && info->subdriver->resume); 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci if (callsub) 5408c2ecf20Sopenharmony_ci ret = info->subdriver->resume(intf); 5418c2ecf20Sopenharmony_ci if (ret < 0) 5428c2ecf20Sopenharmony_ci goto err; 5438c2ecf20Sopenharmony_ci ret = usbnet_resume(intf); 5448c2ecf20Sopenharmony_ci if (ret < 0 && callsub) 5458c2ecf20Sopenharmony_ci info->subdriver->suspend(intf, PMSG_SUSPEND); 5468c2ecf20Sopenharmony_cierr: 5478c2ecf20Sopenharmony_ci return ret; 5488c2ecf20Sopenharmony_ci} 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_cistatic const struct driver_info cdc_mbim_info = { 5518c2ecf20Sopenharmony_ci .description = "CDC MBIM", 5528c2ecf20Sopenharmony_ci .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, 5538c2ecf20Sopenharmony_ci .bind = cdc_mbim_bind, 5548c2ecf20Sopenharmony_ci .unbind = cdc_mbim_unbind, 5558c2ecf20Sopenharmony_ci .manage_power = cdc_mbim_manage_power, 5568c2ecf20Sopenharmony_ci .rx_fixup = cdc_mbim_rx_fixup, 5578c2ecf20Sopenharmony_ci .tx_fixup = cdc_mbim_tx_fixup, 5588c2ecf20Sopenharmony_ci}; 5598c2ecf20Sopenharmony_ci 5608c2ecf20Sopenharmony_ci/* MBIM and NCM devices should not need a ZLP after NTBs with 5618c2ecf20Sopenharmony_ci * dwNtbOutMaxSize length. Nevertheless, a number of devices from 5628c2ecf20Sopenharmony_ci * different vendor IDs will fail unless we send ZLPs, forcing us 5638c2ecf20Sopenharmony_ci * to make this the default. 5648c2ecf20Sopenharmony_ci * 5658c2ecf20Sopenharmony_ci * This default may cause a performance penalty for spec conforming 5668c2ecf20Sopenharmony_ci * devices wanting to take advantage of optimizations possible without 5678c2ecf20Sopenharmony_ci * ZLPs. A whitelist is added in an attempt to avoid this for devices 5688c2ecf20Sopenharmony_ci * known to conform to the MBIM specification. 5698c2ecf20Sopenharmony_ci * 5708c2ecf20Sopenharmony_ci * All known devices supporting NCM compatibility mode are also 5718c2ecf20Sopenharmony_ci * conforming to the NCM and MBIM specifications. For this reason, the 5728c2ecf20Sopenharmony_ci * NCM subclass entry is also in the ZLP whitelist. 5738c2ecf20Sopenharmony_ci */ 5748c2ecf20Sopenharmony_cistatic const struct driver_info cdc_mbim_info_zlp = { 5758c2ecf20Sopenharmony_ci .description = "CDC MBIM", 5768c2ecf20Sopenharmony_ci .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN | FLAG_SEND_ZLP, 5778c2ecf20Sopenharmony_ci .bind = cdc_mbim_bind, 5788c2ecf20Sopenharmony_ci .unbind = cdc_mbim_unbind, 5798c2ecf20Sopenharmony_ci .manage_power = cdc_mbim_manage_power, 5808c2ecf20Sopenharmony_ci .rx_fixup = cdc_mbim_rx_fixup, 5818c2ecf20Sopenharmony_ci .tx_fixup = cdc_mbim_tx_fixup, 5828c2ecf20Sopenharmony_ci}; 5838c2ecf20Sopenharmony_ci 5848c2ecf20Sopenharmony_ci/* The spefication explicitly allows NDPs to be placed anywhere in the 5858c2ecf20Sopenharmony_ci * frame, but some devices fail unless the NDP is placed after the IP 5868c2ecf20Sopenharmony_ci * packets. Using the CDC_NCM_FLAG_NDP_TO_END flags to force this 5878c2ecf20Sopenharmony_ci * behaviour. 5888c2ecf20Sopenharmony_ci * 5898c2ecf20Sopenharmony_ci * Note: The current implementation of this feature restricts each NTB 5908c2ecf20Sopenharmony_ci * to a single NDP, implying that multiplexed sessions cannot share an 5918c2ecf20Sopenharmony_ci * NTB. This might affect performace for multiplexed sessions. 5928c2ecf20Sopenharmony_ci */ 5938c2ecf20Sopenharmony_cistatic const struct driver_info cdc_mbim_info_ndp_to_end = { 5948c2ecf20Sopenharmony_ci .description = "CDC MBIM", 5958c2ecf20Sopenharmony_ci .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, 5968c2ecf20Sopenharmony_ci .bind = cdc_mbim_bind, 5978c2ecf20Sopenharmony_ci .unbind = cdc_mbim_unbind, 5988c2ecf20Sopenharmony_ci .manage_power = cdc_mbim_manage_power, 5998c2ecf20Sopenharmony_ci .rx_fixup = cdc_mbim_rx_fixup, 6008c2ecf20Sopenharmony_ci .tx_fixup = cdc_mbim_tx_fixup, 6018c2ecf20Sopenharmony_ci .data = CDC_NCM_FLAG_NDP_TO_END, 6028c2ecf20Sopenharmony_ci}; 6038c2ecf20Sopenharmony_ci 6048c2ecf20Sopenharmony_ci/* Some modems (e.g. Telit LE922A6) do not work properly with altsetting 6058c2ecf20Sopenharmony_ci * toggle done in cdc_ncm_bind_common. CDC_MBIM_FLAG_AVOID_ALTSETTING_TOGGLE 6068c2ecf20Sopenharmony_ci * flag is used to avoid this procedure. 6078c2ecf20Sopenharmony_ci */ 6088c2ecf20Sopenharmony_cistatic const struct driver_info cdc_mbim_info_avoid_altsetting_toggle = { 6098c2ecf20Sopenharmony_ci .description = "CDC MBIM", 6108c2ecf20Sopenharmony_ci .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN | FLAG_SEND_ZLP, 6118c2ecf20Sopenharmony_ci .bind = cdc_mbim_bind, 6128c2ecf20Sopenharmony_ci .unbind = cdc_mbim_unbind, 6138c2ecf20Sopenharmony_ci .manage_power = cdc_mbim_manage_power, 6148c2ecf20Sopenharmony_ci .rx_fixup = cdc_mbim_rx_fixup, 6158c2ecf20Sopenharmony_ci .tx_fixup = cdc_mbim_tx_fixup, 6168c2ecf20Sopenharmony_ci .data = CDC_MBIM_FLAG_AVOID_ALTSETTING_TOGGLE, 6178c2ecf20Sopenharmony_ci}; 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_cistatic const struct usb_device_id mbim_devs[] = { 6208c2ecf20Sopenharmony_ci /* This duplicate NCM entry is intentional. MBIM devices can 6218c2ecf20Sopenharmony_ci * be disguised as NCM by default, and this is necessary to 6228c2ecf20Sopenharmony_ci * allow us to bind the correct driver_info to such devices. 6238c2ecf20Sopenharmony_ci * 6248c2ecf20Sopenharmony_ci * bind() will sort out this for us, selecting the correct 6258c2ecf20Sopenharmony_ci * entry and reject the other 6268c2ecf20Sopenharmony_ci */ 6278c2ecf20Sopenharmony_ci { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), 6288c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&cdc_mbim_info, 6298c2ecf20Sopenharmony_ci }, 6308c2ecf20Sopenharmony_ci /* ZLP conformance whitelist: All Ericsson MBIM devices */ 6318c2ecf20Sopenharmony_ci { USB_VENDOR_AND_INTERFACE_INFO(0x0bdb, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), 6328c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&cdc_mbim_info, 6338c2ecf20Sopenharmony_ci }, 6348c2ecf20Sopenharmony_ci 6358c2ecf20Sopenharmony_ci /* Some Huawei devices, ME906s-158 (12d1:15c1) and E3372 6368c2ecf20Sopenharmony_ci * (12d1:157d), are known to fail unless the NDP is placed 6378c2ecf20Sopenharmony_ci * after the IP packets. Applying the quirk to all Huawei 6388c2ecf20Sopenharmony_ci * devices is broader than necessary, but harmless. 6398c2ecf20Sopenharmony_ci */ 6408c2ecf20Sopenharmony_ci { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), 6418c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&cdc_mbim_info_ndp_to_end, 6428c2ecf20Sopenharmony_ci }, 6438c2ecf20Sopenharmony_ci 6448c2ecf20Sopenharmony_ci /* The HP lt4132 (03f0:a31d) is a rebranded Huawei ME906s-158, 6458c2ecf20Sopenharmony_ci * therefore it too requires the above "NDP to end" quirk. 6468c2ecf20Sopenharmony_ci */ 6478c2ecf20Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), 6488c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&cdc_mbim_info_ndp_to_end, 6498c2ecf20Sopenharmony_ci }, 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_ci /* Telit LE922A6 in MBIM composition */ 6528c2ecf20Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x1bc7, 0x1041, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), 6538c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&cdc_mbim_info_avoid_altsetting_toggle, 6548c2ecf20Sopenharmony_ci }, 6558c2ecf20Sopenharmony_ci 6568c2ecf20Sopenharmony_ci /* Telit LN920 */ 6578c2ecf20Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x1bc7, 0x1061, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), 6588c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&cdc_mbim_info_avoid_altsetting_toggle, 6598c2ecf20Sopenharmony_ci }, 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci /* Telit FN990 */ 6628c2ecf20Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x1bc7, 0x1071, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), 6638c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&cdc_mbim_info_avoid_altsetting_toggle, 6648c2ecf20Sopenharmony_ci }, 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_ci /* Telit FE990 */ 6678c2ecf20Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x1bc7, 0x1081, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), 6688c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&cdc_mbim_info_avoid_altsetting_toggle, 6698c2ecf20Sopenharmony_ci }, 6708c2ecf20Sopenharmony_ci 6718c2ecf20Sopenharmony_ci /* default entry */ 6728c2ecf20Sopenharmony_ci { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), 6738c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&cdc_mbim_info_zlp, 6748c2ecf20Sopenharmony_ci }, 6758c2ecf20Sopenharmony_ci { 6768c2ecf20Sopenharmony_ci }, 6778c2ecf20Sopenharmony_ci}; 6788c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, mbim_devs); 6798c2ecf20Sopenharmony_ci 6808c2ecf20Sopenharmony_cistatic struct usb_driver cdc_mbim_driver = { 6818c2ecf20Sopenharmony_ci .name = "cdc_mbim", 6828c2ecf20Sopenharmony_ci .id_table = mbim_devs, 6838c2ecf20Sopenharmony_ci .probe = usbnet_probe, 6848c2ecf20Sopenharmony_ci .disconnect = usbnet_disconnect, 6858c2ecf20Sopenharmony_ci .suspend = cdc_mbim_suspend, 6868c2ecf20Sopenharmony_ci .resume = cdc_mbim_resume, 6878c2ecf20Sopenharmony_ci .reset_resume = cdc_mbim_resume, 6888c2ecf20Sopenharmony_ci .supports_autosuspend = 1, 6898c2ecf20Sopenharmony_ci .disable_hub_initiated_lpm = 1, 6908c2ecf20Sopenharmony_ci}; 6918c2ecf20Sopenharmony_cimodule_usb_driver(cdc_mbim_driver); 6928c2ecf20Sopenharmony_ci 6938c2ecf20Sopenharmony_ciMODULE_AUTHOR("Greg Suarez <gsuarez@smithmicro.com>"); 6948c2ecf20Sopenharmony_ciMODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>"); 6958c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("USB CDC MBIM host driver"); 6968c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 697