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