// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2012 Smith Micro Software, Inc. * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no> * * This driver is based on and reuse most of cdc_ncm, which is * Copyright (C) ST-Ericsson 2010-2012
*/
/* alternative VLAN for IP session 0 if not untagged */ #define MBIM_IPS0_VID 4094
/* driver specific data - must match cdc_ncm usage */ struct cdc_mbim_state { struct cdc_ncm_ctx *ctx;
atomic_t pmcount; struct usb_driver *subdriver; unsignedlong _unused; unsignedlong 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 */ staticint cdc_mbim_manage_power(struct usbnet *dev, int on)
{ struct cdc_mbim_state *info = (void *)&dev->data; int rv = 0;
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);
dev->intf->needs_remote_wakeup = on; if (!rv)
usb_autopm_put_interface(dev->intf);
} return 0;
}
/* 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;
}
/* Change the control interface altsetting and update the .driver_info * pointer if the matching entry after changing class codes points to * a different struct
*/ staticint cdc_mbim_set_ctrlalt(struct usbnet *dev, struct usb_interface *intf, u8 alt)
{ struct usb_driver *driver = to_usb_driver(intf->dev.driver); conststruct 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;
}
/* 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, dev->driver_info->data); if (ret) goto err;
ctx = info->ctx;
/* The MBIM descriptor and the status endpoint are required */ if (ctx->mbim_desc && dev->status)
subdriver = usb_cdc_wdm_register(ctx->control,
&dev->status->desc,
le16_to_cpu(ctx->mbim_desc->wMaxControlMessage),
WWAN_PORT_MBIM,
cdc_mbim_wdm_manage_power); if (IS_ERR(subdriver)) {
ret = PTR_ERR(subdriver);
cdc_ncm_unbind(dev, intf); goto err;
}
/* can't let usbnet use the interrupt endpoint */
dev->status = NULL;
info->subdriver = subdriver;
/* MBIM cannot do ARP */
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 | NETIF_F_HW_VLAN_CTAG_FILTER;
/* disconnect subdriver from control interface */ if (info->subdriver && info->subdriver->disconnect)
info->subdriver->disconnect(ctx->control);
info->subdriver = NULL;
/* let NCM unbind clean up both control and data interface */
cdc_ncm_unbind(dev, intf);
}
/* verify that the ethernet protocol is IPv4 or IPv6 */ staticbool is_ip_proto(__be16 proto)
{ switch (proto) { case htons(ETH_P_IP): case htons(ETH_P_IPV6): returntrue;
} returnfalse;
}
/* Some applications using e.g. packet sockets will * bypass the VLAN acceleration and create tagged * ethernet frames directly. We primarily look for * the accelerated out-of-band tag, but fall back if * required
*/
skb_reset_mac_header(skb); if (vlan_get_tag(skb, &tci) < 0 && skb->len > VLAN_ETH_HLEN &&
__vlan_get_tag(skb, &tci) == 0) {
is_ip = is_ip_proto(vlan_eth_hdr(skb)->h_vlan_encapsulated_proto);
skb_pull(skb, VLAN_ETH_HLEN);
} else {
is_ip = is_ip_proto(eth_hdr(skb)->h_proto);
skb_pull(skb, ETH_HLEN);
}
/* Is IP session <0> tagged too? */ if (info->flags & FLAG_IPS0_VLAN) { /* drop all untagged packets */ if (!tci) goto error; /* map MBIM_IPS0_VID to IPS<0> */ if (tci == MBIM_IPS0_VID)
tci = 0;
}
/* mapping VLANs to MBIM sessions: * no tag => IPS session <0> if !FLAG_IPS0_VLAN * 1 - 255 => IPS session <vlanid> * 256 - 511 => DSS session <vlanid - 256> * 512 - 4093 => unsupported, drop * 4094 => IPS session <0> if FLAG_IPS0_VLAN
*/
switch (tci & 0x0f00) { case 0x0000: /* VLAN ID 0 - 255 */ if (!is_ip) goto error;
c = (u8 *)&sign;
c[3] = tci; break; case 0x0100: /* VLAN ID 256 - 511 */ if (is_ip) goto error;
sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN);
c = (u8 *)&sign;
c[3] = tci; break; default:
netif_err(dev, tx_err, dev->net, "unsupported tci=0x%04x\n", tci); goto error;
}
}
/* Some devices are known to send Neighbor Solicitation messages and * require Neighbor Advertisement replies. The IPv6 core will not * respond since IFF_NOARP is set, so we must handle them ourselves.
*/ staticvoid 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();
switch (ndp16->dwSignature & cpu_to_le32(0x00ffffff)) { 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;
tci = c[3] + 256; break; default:
netif_dbg(dev, rx_err, dev->net, "unsupported NDP signature <0x%08x>\n",
le32_to_cpu(ndp16->dwSignature)); goto err_ndp;
}
dpe16 = ndp16->dpe16; for (x = 0; x < nframes; x++, dpe16++) {
offset = le16_to_cpu(dpe16->wDatagramIndex);
len = le16_to_cpu(dpe16->wDatagramLength);
/* * CDC NCM ch. 3.7 * All entries after first NULL entry are to be ignored
*/ if ((offset == 0) || (len == 0)) { if (!x) goto err_ndp; /* empty NTB */ break;
}
/* sanity checking */ if (((offset + len) > skb_in->len) || (len > ctx->rx_max)) {
netif_dbg(dev, rx_err, dev->net, "invalid frame detected (ignored) offset[%u]=%u, length=%u, skb=%p\n",
x, offset, len, skb_in); if (!x) goto err_ndp; break;
} else {
skb = cdc_mbim_process_dgram(dev, skb_in->data + offset, len, tci); if (!skb) goto error;
usbnet_skb_return(dev, skb);
payload += len; /* count payload bytes in this NTB */
}
}
err_ndp: /* are there more NDPs to process? */
ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); if (ndpoffset && loopcount--) goto next_ndp;
/* * Both usbnet_suspend() and subdriver->suspend() MUST return 0 * in system sleep context, otherwise, the resume callback has * to recover device from previous suspend failure.
*/
ret = usbnet_suspend(intf, message); if (ret < 0) goto error;
if (intf == ctx->control && info->subdriver && info->subdriver->suspend)
ret = info->subdriver->suspend(intf, message); if (ret < 0)
usbnet_resume(intf);
/* MBIM and NCM devices should not need a ZLP after NTBs with * 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.
*/ staticconststruct driver_info cdc_mbim_info_zlp = {
.description = "CDC MBIM",
.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN | FLAG_SEND_ZLP,
.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,
};
/* 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 performance for multiplexed sessions.
*/ staticconststruct 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,
};
/* Some modems (e.g. Telit LE922A6) do not work properly with altsetting * toggle done in cdc_ncm_bind_common. CDC_MBIM_FLAG_AVOID_ALTSETTING_TOGGLE * flag is used to avoid this procedure.
*/ staticconststruct driver_info cdc_mbim_info_avoid_altsetting_toggle = {
.description = "CDC MBIM",
.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN | FLAG_SEND_ZLP,
.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_MBIM_FLAG_AVOID_ALTSETTING_TOGGLE,
};
staticconststruct 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 * allow us to bind the correct driver_info to such devices. * * bind() will sort out this for us, selecting the correct * entry and reject the other
*/
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE),
.driver_info = (unsignedlong)&cdc_mbim_info,
}, /* 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 = (unsignedlong)&cdc_mbim_info,
},
/* 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 = (unsignedlong)&cdc_mbim_info_ndp_to_end,
},
/* The HP lt4132 (03f0:a31d) is a rebranded Huawei ME906s-158, * therefore it too requires the above "NDP to end" quirk.
*/
{ USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
.driver_info = (unsignedlong)&cdc_mbim_info_ndp_to_end,
},
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.