--- zzzz-none-000/linux-3.10.107/drivers/net/usb/cdc_mbim.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/drivers/net/usb/cdc_mbim.c 2021-02-04 17:41:59.000000000 +0000 @@ -21,14 +21,37 @@ #include #include #include +#include +#include + +/* alternative VLAN for IP session 0 if not untagged */ +#define MBIM_IPS0_VID 4094 + +#define AVM_VLAN_MAC_MAPPING + +#ifdef AVM_VLAN_MAC_MAPPING + +#define AVM_VLAN_ID_MAX 3 +static unsigned char avm_vlan_mac_addr[AVM_VLAN_ID_MAX+1][ETH_ALEN]; + +inline bool avm_is_mac_empty(unsigned char mac_addr[ETH_ALEN]) { + /* check first two bytes of mac address */ + return !((uint16_t *)mac_addr)[0]; +} +#endif /* driver specific data - must match cdc_ncm usage */ struct cdc_mbim_state { struct cdc_ncm_ctx *ctx; atomic_t pmcount; struct usb_driver *subdriver; - struct usb_interface *control; - struct usb_interface *data; + unsigned long _unused; + unsigned long flags; +}; + +/* flags for the cdc_mbim_state.flags field */ +enum cdc_mbim_flags { + FLAG_IPS0_VLAN = 1 << 0, /* IP session 0 is tagged */ }; /* using a counter to merge subdriver requests with our own into a combined state */ @@ -42,13 +65,11 @@ if ((on && atomic_add_return(1, &info->pmcount) == 1) || (!on && atomic_dec_and_test(&info->pmcount))) { /* need autopm_get/put here to ensure the usbcore sees the new value */ rv = usb_autopm_get_interface(dev->intf); - if (rv < 0) - goto err; dev->intf->needs_remote_wakeup = on; - usb_autopm_put_interface(dev->intf); + if (!rv) + usb_autopm_put_interface(dev->intf); } -err: - return rv; + return 0; } static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status) @@ -62,20 +83,95 @@ return cdc_mbim_manage_power(dev, status); } +static int cdc_mbim_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid) +{ + struct usbnet *dev = netdev_priv(netdev); + struct cdc_mbim_state *info = (void *)&dev->data; + + /* creation of this VLAN is a request to tag IP session 0 */ + if (vid == MBIM_IPS0_VID) + info->flags |= FLAG_IPS0_VLAN; + else + if (vid >= 512) /* we don't map these to MBIM session */ + return -EINVAL; + return 0; +} + +static int cdc_mbim_rx_kill_vid(struct net_device *netdev, __be16 proto, u16 vid) +{ + struct usbnet *dev = netdev_priv(netdev); + struct cdc_mbim_state *info = (void *)&dev->data; + + /* this is a request for an untagged IP session 0 */ + if (vid == MBIM_IPS0_VID) + info->flags &= ~FLAG_IPS0_VLAN; + return 0; +} + +static const struct net_device_ops cdc_mbim_netdev_ops = { + .ndo_open = usbnet_open, + .ndo_stop = usbnet_stop, + .ndo_start_xmit = usbnet_start_xmit, + .ndo_tx_timeout = usbnet_tx_timeout, + .ndo_change_mtu = cdc_ncm_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_vlan_rx_add_vid = cdc_mbim_rx_add_vid, + .ndo_vlan_rx_kill_vid = cdc_mbim_rx_kill_vid, +}; + +/* Change the control interface altsetting and update the .driver_info + * pointer if the matching entry after changing class codes points to + * a different struct + */ +static int cdc_mbim_set_ctrlalt(struct usbnet *dev, struct usb_interface *intf, u8 alt) +{ + struct usb_driver *driver = to_usb_driver(intf->dev.driver); + const struct usb_device_id *id; + struct driver_info *info; + int ret; + + ret = usb_set_interface(dev->udev, + intf->cur_altsetting->desc.bInterfaceNumber, + alt); + if (ret) + return ret; + + id = usb_match_id(intf, driver->id_table); + if (!id) + return -ENODEV; + + info = (struct driver_info *)id->driver_info; + if (info != dev->driver_info) { + dev_dbg(&intf->dev, "driver_info updated to '%s'\n", + info->description); + dev->driver_info = info; + } + return 0; +} static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf) { struct cdc_ncm_ctx *ctx; struct usb_driver *subdriver = ERR_PTR(-ENODEV); int ret = -ENODEV; - u8 data_altsetting = cdc_ncm_select_altsetting(dev, intf); + u8 data_altsetting = 1; struct cdc_mbim_state *info = (void *)&dev->data; - /* Probably NCM, defer for cdc_ncm_bind */ + /* should we change control altsetting on a NCM/MBIM function? */ + if (cdc_ncm_select_altsetting(intf) == CDC_NCM_COMM_ALTSETTING_MBIM) { + data_altsetting = CDC_NCM_DATA_ALTSETTING_MBIM; + ret = cdc_mbim_set_ctrlalt(dev, intf, CDC_NCM_COMM_ALTSETTING_MBIM); + if (ret) + goto err; + ret = -ENODEV; + } + + /* we will hit this for NCM/MBIM functions if prefer_mbim is false */ if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) goto err; - ret = cdc_ncm_bind_common(dev, intf, data_altsetting); + ret = cdc_ncm_bind_common(dev, intf, data_altsetting, dev->driver_info->data); if (ret) goto err; @@ -93,6 +189,11 @@ goto err; } +#ifdef AVM_VLAN_MAC_MAPPING + /* clear old mapping */ + memset(avm_vlan_mac_addr, 0, sizeof avm_vlan_mac_addr); +#endif + /* can't let usbnet use the interrupt endpoint */ dev->status = NULL; info->subdriver = subdriver; @@ -101,7 +202,10 @@ dev->net->flags |= IFF_NOARP; /* no need to put the VLAN tci in the packet headers */ - dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX; + dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_FILTER; + + /* monitor VLAN additions and removals */ + dev->net->netdev_ops = &cdc_mbim_netdev_ops; err: return ret; } @@ -158,18 +262,36 @@ if (vlan_get_tag(skb, &tci) < 0 && skb->len > VLAN_ETH_HLEN && __vlan_get_tag(skb, &tci) == 0) { is_ip = is_ip_proto(vlan_eth_hdr(skb)->h_vlan_encapsulated_proto); +#ifdef AVM_VLAN_MAC_MAPPING + /* save mac_addr of first incoming VLAN packet */ + if (tci && (tci <= AVM_VLAN_ID_MAX) && avm_is_mac_empty(avm_vlan_mac_addr[tci])) { + memcpy(avm_vlan_mac_addr[tci], eth_hdr(skb)->h_source, ETH_ALEN); + } +#endif skb_pull(skb, VLAN_ETH_HLEN); } else { is_ip = is_ip_proto(eth_hdr(skb)->h_proto); skb_pull(skb, ETH_HLEN); } + /* Is IP session <0> tagged too? */ + if (info->flags & FLAG_IPS0_VLAN) { + /* drop all untagged packets */ + if (!tci) + goto error; + /* map MBIM_IPS0_VID to IPS<0> */ + if (tci == MBIM_IPS0_VID) + tci = 0; + } + /* mapping VLANs to MBIM sessions: - * no tag => IPS session <0> + * no tag => IPS session <0> if !FLAG_IPS0_VLAN * 1 - 255 => IPS session * 256 - 511 => DSS session - * 512 - 4095 => unsupported, drop + * 512 - 4093 => unsupported, drop + * 4094 => IPS session <0> if FLAG_IPS0_VLAN */ + switch (tci & 0x0f00) { case 0x0000: /* VLAN ID 0 - 255 */ if (!is_ip) @@ -178,6 +300,8 @@ c[3] = tci; break; case 0x0100: /* VLAN ID 256 - 511 */ + if (is_ip) + goto error; sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN); c = (u8 *)&sign; c[3] = tci; @@ -190,7 +314,7 @@ } spin_lock_bh(&ctx->mtx); - skb_out = cdc_ncm_fill_tx_frame(ctx, skb, sign); + skb_out = cdc_ncm_fill_tx_frame(dev, skb, sign); spin_unlock_bh(&ctx->mtx); return skb_out; @@ -201,12 +325,74 @@ return NULL; } +/* Some devices are known to send Neigbor Solicitation messages and + * require Neigbor Advertisement replies. The IPv6 core will not + * respond since IFF_NOARP is set, so we must handle them ourselves. + */ +static void do_neigh_solicit(struct usbnet *dev, u8 *buf, u16 tci) +{ + struct ipv6hdr *iph = (void *)buf; + struct nd_msg *msg = (void *)(iph + 1); + struct net_device *netdev; + struct inet6_dev *in6_dev; + bool is_router; + + /* we'll only respond to requests from unicast addresses to + * our solicited node addresses. + */ + if (!ipv6_addr_is_solict_mult(&iph->daddr) || + !(ipv6_addr_type(&iph->saddr) & IPV6_ADDR_UNICAST)) + return; + + /* need to send the NA on the VLAN dev, if any */ + rcu_read_lock(); + if (tci) { + netdev = __vlan_find_dev_deep_rcu(dev->net, htons(ETH_P_8021Q), + tci); + if (!netdev) { + rcu_read_unlock(); + return; + } + } else { + netdev = dev->net; + } + dev_hold(netdev); + rcu_read_unlock(); + + in6_dev = in6_dev_get(netdev); + if (!in6_dev) + goto out; + is_router = !!in6_dev->cnf.forwarding; + in6_dev_put(in6_dev); + + /* ipv6_stub != NULL if in6_dev_get returned an inet6_dev */ + ipv6_stub->ndisc_send_na(netdev, &iph->saddr, &msg->target, + is_router /* router */, + true /* solicited */, + false /* override */, + true /* inc_opt */); +out: + dev_put(netdev); +} + +static bool is_neigh_solicit(u8 *buf, size_t len) +{ + struct ipv6hdr *iph = (void *)buf; + struct nd_msg *msg = (void *)(iph + 1); + + return (len >= sizeof(struct ipv6hdr) + sizeof(struct nd_msg) && + iph->nexthdr == IPPROTO_ICMPV6 && + msg->icmph.icmp6_code == 0 && + msg->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION); +} + + static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci) { __be16 proto = htons(ETH_P_802_3); struct sk_buff *skb = NULL; - if (tci < 256) { /* IPS session? */ + if (tci < 256 || tci == MBIM_IPS0_VID) { /* IPS session? */ if (len < sizeof(struct iphdr)) goto err; @@ -215,6 +401,8 @@ proto = htons(ETH_P_IP); break; case 0x60: + if (is_neigh_solicit(buf, len)) + do_neigh_solicit(dev, buf, tci); proto = htons(ETH_P_IPV6); break; default: @@ -230,7 +418,12 @@ skb_put(skb, ETH_HLEN); skb_reset_mac_header(skb); eth_hdr(skb)->h_proto = proto; - memset(eth_hdr(skb)->h_source, 0, ETH_ALEN); + eth_zero_addr(eth_hdr(skb)->h_source); +#ifdef AVM_VLAN_MAC_MAPPING + if (tci && (tci <= AVM_VLAN_ID_MAX)) { + memcpy(eth_hdr(skb)->h_dest, avm_vlan_mac_addr[tci], ETH_ALEN); + } else +#endif memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); /* add datagram */ @@ -238,7 +431,7 @@ /* map MBIM session to VLAN */ if (tci) - vlan_put_tag(skb, htons(ETH_P_8021Q), tci); + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tci); err: return skb; } @@ -256,6 +449,7 @@ struct usb_cdc_ncm_dpe16 *dpe16; int ndpoffset; int loopcount = 50; /* arbitrary max preventing infinite loop */ + u32 payload = 0; u8 *c; u16 tci; @@ -274,6 +468,9 @@ case cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN): c = (u8 *)&ndp16->dwSignature; tci = c[3]; + /* tag IPS<0> packets too if MBIM_IPS0_VID exists */ + if (!tci && info->flags & FLAG_IPS0_VLAN) + tci = MBIM_IPS0_VID; break; case cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN): c = (u8 *)&ndp16->dwSignature; @@ -315,6 +512,7 @@ if (!skb) goto error; usbnet_skb_return(dev, skb); + payload += len; /* count payload bytes in this NTB */ } } err_ndp: @@ -323,6 +521,10 @@ if (ndpoffset && loopcount--) goto next_ndp; + /* update stats */ + ctx->rx_overhead += skb_in->len - payload; + ctx->rx_ntbs++; + return 1; error: return 0; @@ -330,15 +532,13 @@ static int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message) { - int ret = 0; + int ret = -ENODEV; struct usbnet *dev = usb_get_intfdata(intf); struct cdc_mbim_state *info = (void *)&dev->data; struct cdc_ncm_ctx *ctx = info->ctx; - if (ctx == NULL) { - ret = -1; + if (!ctx) goto error; - } /* * Both usbnet_suspend() and subdriver->suspend() MUST return 0 @@ -371,7 +571,7 @@ if (ret < 0) goto err; ret = usbnet_resume(intf); - if (ret < 0 && callsub && info->subdriver->suspend) + if (ret < 0 && callsub) info->subdriver->suspend(intf, PMSG_SUSPEND); err: return ret; @@ -388,9 +588,18 @@ }; /* MBIM and NCM devices should not need a ZLP after NTBs with - * dwNtbOutMaxSize length. This driver_info is for the exceptional - * devices requiring it anyway, allowing them to be supported without - * forcing the performance penalty on all the sane devices. + * dwNtbOutMaxSize length. Nevertheless, a number of devices from + * different vendor IDs will fail unless we send ZLPs, forcing us + * to make this the default. + * + * This default may cause a performance penalty for spec conforming + * devices wanting to take advantage of optimizations possible without + * ZLPs. A whitelist is added in an attempt to avoid this for devices + * known to conform to the MBIM specification. + * + * All known devices supporting NCM compatibility mode are also + * conforming to the NCM and MBIM specifications. For this reason, the + * NCM subclass entry is also in the ZLP whitelist. */ static const struct driver_info cdc_mbim_info_zlp = { .description = "CDC MBIM", @@ -402,6 +611,26 @@ .tx_fixup = cdc_mbim_tx_fixup, }; +/* The spefication explicitly allows NDPs to be placed anywhere in the + * frame, but some devices fail unless the NDP is placed after the IP + * packets. Using the CDC_NCM_FLAG_NDP_TO_END flags to force this + * behaviour. + * + * Note: The current implementation of this feature restricts each NTB + * to a single NDP, implying that multiplexed sessions cannot share an + * NTB. This might affect performace for multiplexed sessions. + */ +static const struct driver_info cdc_mbim_info_ndp_to_end = { + .description = "CDC MBIM", + .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, + .bind = cdc_mbim_bind, + .unbind = cdc_mbim_unbind, + .manage_power = cdc_mbim_manage_power, + .rx_fixup = cdc_mbim_rx_fixup, + .tx_fixup = cdc_mbim_tx_fixup, + .data = CDC_NCM_FLAG_NDP_TO_END, +}; + static const struct usb_device_id mbim_devs[] = { /* This duplicate NCM entry is intentional. MBIM devices can * be disguised as NCM by default, and this is necessary to @@ -413,16 +642,22 @@ { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), .driver_info = (unsigned long)&cdc_mbim_info, }, - /* Sierra Wireless MC7710 need ZLPs */ - { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x68a2, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long)&cdc_mbim_info_zlp, + /* ZLP conformance whitelist: All Ericsson MBIM devices */ + { USB_VENDOR_AND_INTERFACE_INFO(0x0bdb, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info, }, - /* HP hs2434 Mobile Broadband Module needs ZLPs */ - { USB_DEVICE_AND_INTERFACE_INFO(0x3f0, 0x4b1d, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long)&cdc_mbim_info_zlp, + + /* Some Huawei devices, ME906s-158 (12d1:15c1) and E3372 + * (12d1:157d), are known to fail unless the NDP is placed + * after the IP packets. Applying the quirk to all Huawei + * devices is broader than necessary, but harmless. + */ + { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info_ndp_to_end, }, + /* default entry */ { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long)&cdc_mbim_info, + .driver_info = (unsigned long)&cdc_mbim_info_zlp, }, { },