// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no> * * The probing code is heavily inspired by cdc_ether, which is: * Copyright (C) 2003-2005 by David Brownell * Copyright (C) 2006 by Ole Andre Vadla Ravnas (ActiveSync)
*/
/* This driver supports wwan (3G/LTE/?) devices using a vendor * specific management protocol called Qualcomm MSM Interface (QMI) - * in addition to the more common AT commands over serial interface * management * * QMI is wrapped in CDC, using CDC encapsulated commands on the * control ("master") interface of a two-interface CDC Union * resembling standard CDC ECM. The devices do not use the control * interface for any other CDC messages. Most likely because the * management protocol is used in place of the standard CDC * notifications NOTIFY_NETWORK_CONNECTION and NOTIFY_SPEED_CHANGE * * Alternatively, control and data functions can be combined in a * single USB interface. * * Handling a protocol like QMI is out of the scope for any driver. * It is exported as a character device using the cdc-wdm driver as * a subdriver, enabling userspace applications ("modem managers") to * handle it. * * These devices may alternatively/additionally be configured using AT * commands on a serial interface
*/
while (offset + qmimux_hdr_sz < skb->len) {
hdr = (struct qmimux_hdr *)(skb->data + offset);
len = be16_to_cpu(hdr->pkt_len);
/* drop the packet, bogus length */ if (offset + len + qmimux_hdr_sz > skb->len) return 0;
/* control packet, we do not know what to do */ if (hdr->pad & 0x80) goto skip;
/* extract padding length and check for valid length info */
pad_len = hdr->pad & 0x3f; if (len == 0 || pad_len >= len) goto skip;
pkt_len = len - pad_len;
net = qmimux_find_dev(dev, hdr->mux_id); if (!net) goto skip;
skbn = netdev_alloc_skb(net, pkt_len + LL_MAX_HEADER); if (!skbn) return 0;
/* Raw IP packets don't have a MAC header, but other subsystems * (like xfrm) may still access MAC header offsets, so they must * be initialized.
*/
skb_reset_mac_header(skbn);
switch (skb->data[offset + qmimux_hdr_sz] & 0xf0) { case 0x40:
skbn->protocol = htons(ETH_P_IP); break; case 0x60:
skbn->protocol = htons(ETH_P_IPV6); break; default: /* not ip - do not know what to do */
kfree_skb(skbn); goto skip;
}
/* no change? */ if (enable == (info->flags & QMI_WWAN_FLAG_RAWIP)) return len;
/* ip mode cannot be cleared when pass through mode is set */ if (!enable && (info->flags & QMI_WWAN_FLAG_PASS_THROUGH)) {
netdev_err(dev->net, "Cannot clear ip mode on pass through device\n"); return -EINVAL;
}
if (!rtnl_trylock()) return restart_syscall();
/* we don't want to modify a running netdev */ if (netif_running(dev->net)) {
netdev_err(dev->net, "Cannot change a running device\n");
ret = -EBUSY; goto err;
}
/* let other drivers deny the change */
ret = call_netdevice_notifiers(NETDEV_PRE_TYPE_CHANGE, dev->net);
ret = notifier_to_errno(ret); if (ret) {
netdev_err(dev->net, "Type change was refused\n"); goto err;
}
if (enable)
info->flags |= QMI_WWAN_FLAG_RAWIP; else
info->flags &= ~QMI_WWAN_FLAG_RAWIP;
qmi_wwan_netdev_setup(dev->net);
call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev->net);
ret = len;
err:
rtnl_unlock(); return ret;
}
/* no change? */ if (enable == (info->flags & QMI_WWAN_FLAG_PASS_THROUGH)) return len;
/* pass through mode can be set for raw ip devices only */ if (!(info->flags & QMI_WWAN_FLAG_RAWIP)) {
netdev_err(dev->net, "Cannot set pass through mode on non ip device\n"); return -EINVAL;
}
if (enable)
info->flags |= QMI_WWAN_FLAG_PASS_THROUGH; else
info->flags &= ~QMI_WWAN_FLAG_PASS_THROUGH;
/* Make up an ethernet header if the packet doesn't have one. * * A firmware bug common among several devices cause them to send raw * IP packets under some circumstances. There is no way for the * driver/host to know when this will happen. And even when the bug * hits, some packets will still arrive with an intact header. * * The supported devices are only capably of sending IPv4, IPv6 and * ARP packets on a point-to-point link. Any packet with an ethernet * header will have either our address or a broadcast/multicast * address as destination. ARP packets will always have a header. * * This means that this function will reliably add the appropriate * header iff necessary, provided our hardware address does not start * with 4 or 6. * * Another common firmware bug results in all packets being addressed * to 00:a0:c6:00:00:00 despite the host address being different. * This function will also fixup such packets.
*/ staticint qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{ struct qmi_wwan_state *info = (void *)&dev->data; bool rawip = info->flags & QMI_WWAN_FLAG_RAWIP;
__be16 proto;
/* This check is no longer done by usbnet */ if (skb->len < dev->net->hard_header_len) return 0;
if (info->flags & QMI_WWAN_FLAG_MUX) return qmimux_rx_fixup(dev, skb);
switch (skb->data[0] & 0xf0) { case 0x40:
proto = htons(ETH_P_IP); break; case 0x60:
proto = htons(ETH_P_IPV6); break; case 0x00: if (rawip) return 0; if (is_multicast_ether_addr(skb->data)) return 1; /* possibly bogus destination - rewrite just in case */
skb_reset_mac_header(skb); goto fix_dest; default: if (rawip) return 0; /* pass along other packets without modifications */ return 1;
} if (rawip) {
skb_reset_mac_header(skb);
skb->dev = dev->net; /* normally set by eth_type_trans */
skb->protocol = proto; return 1;
}
/* very simplistic detection of IPv4 or IPv6 headers */ staticbool possibly_iphdr(constchar *data)
{ return (data[0] & 0xd0) == 0x40;
}
/* disallow addresses which may be confused with IP headers */ staticint qmi_wwan_mac_addr(struct net_device *dev, void *p)
{ int ret; struct sockaddr *addr = p;
ret = eth_prepare_mac_addr_change(dev, p); if (ret < 0) return ret; if (possibly_iphdr(addr->sa_data)) return -EADDRNOTAVAIL;
eth_commit_mac_addr_change(dev, p); return 0;
}
/* using a counter to merge subdriver requests with our own into a * combined state
*/ staticint qmi_wwan_manage_power(struct usbnet *dev, int on)
{ struct qmi_wwan_state *info = (void *)&dev->data; int rv;
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;
}
/* prevent usbnet from using status endpoint */
dev->status = NULL;
/* save subdriver struct for suspend/resume wrappers */
info->subdriver = subdriver;
err: return rv;
}
/* Send CDC SetControlLineState request, setting or clearing the DTR. * "Required for Autoconnect and 9x30 to wake up" according to the * GobiNet driver. The requirement has been verified on an MDM9230 * based Sierra Wireless MC7455
*/ staticint qmi_wwan_change_dtr(struct usbnet *dev, bool on)
{
u8 intf = dev->intf->cur_altsetting->desc.bInterfaceNumber;
/* set up initial state */
info->control = intf;
info->data = intf;
/* and a number of CDC descriptors */
cdc_parse_cdc_header(&hdr, intf, buf, len);
cdc_union = hdr.usb_cdc_union_desc;
cdc_ether = hdr.usb_cdc_ether_desc;
/* Use separate control and data interfaces if we found a CDC Union */ if (cdc_union) {
info->data = usb_ifnum_to_if(dev->udev,
cdc_union->bSlaveInterface0); if (desc->bInterfaceNumber != cdc_union->bMasterInterface0 ||
!info->data) {
dev_err(&intf->dev, "bogus CDC Union: master=%u, slave=%u\n",
cdc_union->bMasterInterface0,
cdc_union->bSlaveInterface0);
/* errors aren't fatal - we can live with the dynamic address */ if (cdc_ether && cdc_ether->wMaxSegmentSize) {
dev->hard_mtu = le16_to_cpu(cdc_ether->wMaxSegmentSize);
usbnet_get_ethernet_addr(dev, cdc_ether->iMACAddress);
}
/* claim data interface and set it up */ if (info->control != info->data) {
status = usb_driver_claim_interface(driver, info->data, dev); if (status < 0) goto err;
}
status = qmi_wwan_register_subdriver(dev); if (status < 0 && info->control != info->data) {
usb_set_intfdata(info->data, NULL);
usb_driver_release_interface(driver, info->data);
}
/* disabling remote wakeup on MDM9x30 devices has the same * effect as clearing DTR. The device will not respond to QMI * requests until we set DTR again. This is similar to a * QMI_CTL SYNC request, clearing a lot of firmware state * including the client ID allocations. * * Our usage model allows a session to span multiple * open/close events, so we must prevent the firmware from * clearing out state the clients might need. * * MDM9x30 is the first QMI chipset with USB3 support. Abuse * this fact to enable the quirk for all USB3 devices. * * There are also chipsets with the same "set DTR" requirement * but without USB3 support. Devices based on these chips * need a quirk flag in the device ID table.
*/ if (dev->driver_info->data & QMI_WWAN_QUIRK_DTR ||
le16_to_cpu(dev->udev->descriptor.bcdUSB) >= 0x0201) {
qmi_wwan_manage_power(dev, 1);
qmi_wwan_change_dtr(dev, true);
}
/* Never use the same address on both ends of the link, even if the * buggy firmware told us to. Or, if device is assigned the well-known * buggy firmware MAC address, replace it with a random address,
*/ if (ether_addr_equal(dev->net->dev_addr, default_modem_addr) ||
ether_addr_equal(dev->net->dev_addr, buggy_fw_addr))
eth_hw_addr_random(dev->net);
/* make MAC addr easily distinguishable from an IP header */ if (possibly_iphdr(dev->net->dev_addr)) {
u8 addr = dev->net->dev_addr[0];
addr |= 0x02; /* set local assignment bit */
addr &= 0xbf; /* clear "IP" bit */
dev_addr_mod(dev->net, 0, &addr, 1);
}
dev->net->netdev_ops = &qmi_wwan_netdev_ops;
dev->net->sysfs_groups[0] = &qmi_wwan_sysfs_attr_group;
err: return status;
}
/* suspend/resume wrappers calling both usbnet and the cdc-wdm * subdriver if present. * * NOTE: cdc-wdm also supports pre/post_reset, but we cannot provide * wrappers for those without adding usbnet reset support first.
*/ staticint qmi_wwan_suspend(struct usb_interface *intf, pm_message_t message)
{ struct usbnet *dev = usb_get_intfdata(intf); struct qmi_wwan_state *info = (void *)&dev->data; int ret;
/* 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 err;
if (intf == info->control && info->subdriver &&
info->subdriver->suspend)
ret = info->subdriver->suspend(intf, message); if (ret < 0)
usbnet_resume(intf);
err: return ret;
}
/* Gobi 1000 QMI/wwan interface number is 3 according to qcserial */ #define QMI_GOBI1K_DEVICE(vend, prod) \
QMI_FIXED_INTF(vend, prod, 3)
/* Gobi 2000/3000 QMI/wwan interface number is 0 according to qcserial */ #define QMI_GOBI_DEVICE(vend, prod) \
QMI_FIXED_INTF(vend, prod, 0)
/* Many devices have QMI and DIAG functions which are distinguishable * from other vendor specific functions by class, subclass and * protocol all being 0xff. The DIAG function has exactly 2 endpoints * and is silently rejected when probed. * * This makes it possible to match dynamically numbered QMI functions * as seen on e.g. many Quectel modems.
*/ #define QMI_MATCH_FF_FF_FF(vend, prod) \
USB_DEVICE_AND_INTERFACE_INFO(vend, prod, USB_CLASS_VENDOR_SPEC, \
USB_SUBCLASS_VENDOR_SPEC, 0xff), \
.driver_info = (unsignedlong)&qmi_wwan_info_quirk_dtr
staticconststruct usb_device_id products[] = { /* 1. CDC ECM like devices match on the control interface */
{ /* Huawei E392, E398 and possibly others sharing both device id and more... */
USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 1, 9),
.driver_info = (unsignedlong)&qmi_wwan_info,
},
{ /* Vodafone/Huawei K5005 (12d1:14c8) and similar modems */
USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 1, 57),
.driver_info = (unsignedlong)&qmi_wwan_info,
},
{ /* HUAWEI_INTERFACE_NDIS_CONTROL_QUALCOMM */
USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 0x01, 0x69),
.driver_info = (unsignedlong)&qmi_wwan_info,
},
{ /* Motorola Mapphone devices with MDM6600 */
USB_VENDOR_AND_INTERFACE_INFO(0x22b8, USB_CLASS_VENDOR_SPEC, 0xfb, 0xff),
.driver_info = (unsignedlong)&qmi_wwan_info,
},
/* Workaround to enable dynamic IDs. This disables usbnet * blacklisting functionality. Which, if required, can be * reimplemented here by using a magic "blacklist" value * instead of 0 in the static device id table
*/ if (!id->driver_info) {
dev_dbg(&intf->dev, "setting defaults for dynamic device id\n");
id->driver_info = (unsignedlong)&qmi_wwan_info;
}
/* There are devices where the same interface number can be * configured as different functions. We should only bind to * vendor specific functions when matching on interface number
*/ if (id->match_flags & USB_DEVICE_ID_MATCH_INT_NUMBER &&
desc->bInterfaceClass != USB_CLASS_VENDOR_SPEC) {
dev_dbg(&intf->dev, "Rejecting interface number match for class %02x\n",
desc->bInterfaceClass); return -ENODEV;
}
/* Quectel EC20 quirk where we've QMI on interface 4 instead of 0 */ if (quectel_ec20_detected(intf) && desc->bInterfaceNumber == 0) {
dev_dbg(&intf->dev, "Quectel EC20 quirk, skipping interface 0\n"); return -ENODEV;
}
/* Several Quectel modems supports dynamic interface configuration, so * we need to match on class/subclass/protocol. These values are * identical for the diagnostic- and QMI-interface, but bNumEndpoints is * different. Ignore the current interface if the number of endpoints * equals the number for the diag interface (two).
*/ if (desc->bNumEndpoints == 2) return -ENODEV;
/* called twice if separate control and data intf */ if (!dev) return;
info = (void *)&dev->data; if (info->flags & QMI_WWAN_FLAG_MUX) { if (!rtnl_trylock()) {
restart_syscall(); return;
}
rcu_read_lock();
netdev_for_each_upper_dev_rcu(dev->net, ldev, iter)
qmimux_unregister_device(ldev, &list);
rcu_read_unlock();
unregister_netdevice_many(&list);
rtnl_unlock();
info->flags &= ~QMI_WWAN_FLAG_MUX;
}
usbnet_disconnect(intf);
}
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.