// 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)
*/
#include <linux/module.h>
#include <linux/sched/signal.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/kstrtox.h>
#include <linux/mii.h>
#include <linux/rtnetlink.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
#include <linux/usb/usbnet.h>
#include <linux/usb/cdc-wdm.h>
#include <linux/u64_stats_sync.h>
/* 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
*/
/* driver specific data */
struct qmi_wwan_state {
struct usb_driver *subdriver;
atomic_t pmcount;
unsigned long flags;
struct usb_interface *control;
struct usb_interface *data;
};
enum qmi_wwan_flags {
QMI_WWAN_FLAG_RAWIP = 1 << 0,
QMI_WWAN_FLAG_MUX = 1 << 1,
QMI_WWAN_FLAG_PASS_THROUGH = 1 << 2,
};
enum qmi_wwan_quirks {
QMI_WWAN_QUIRK_DTR = 1 << 0, /* needs "set DTR" request */
};
struct qmimux_hdr {
u8 pad;
u8 mux_id;
__be16 pkt_len;
};
struct qmimux_priv {
struct net_device *real_dev;
u8 mux_id;
};
static int qmimux_open(struct net_device *dev)
{
struct qmimux_priv *priv = netdev_priv(dev);
struct net_device *real_dev = priv->real_dev;
if (!(priv->real_dev->flags & IFF_UP))
return -ENETDOWN;
if (netif_carrier_ok(real_dev))
netif_carrier_on(dev);
return 0;
}
static int qmimux_stop(struct net_device *dev)
{
netif_carrier_off(dev);
return 0;
}
static netdev_tx_t qmimux_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct qmimux_priv *priv = netdev_priv(dev);
unsigned int len = skb->len;
struct qmimux_hdr *hdr;
netdev_tx_t ret;
hdr = skb_push(skb, sizeof(struct qmimux_hdr));
hdr->pad = 0;
hdr->mux_id = priv->mux_id;
hdr->pkt_len = cpu_to_be16(len);
skb->dev = priv->real_dev;
ret = dev_queue_xmit(skb);
if (likely(ret == NET_XMIT_SUCCESS || ret == NET_XMIT_CN))
dev_sw_netstats_tx_add(dev, 1, len);
else
dev->stats.tx_dropped++;
return ret;
}
static const struct net_device_ops qmimux_netdev_ops = {
.ndo_open = qmimux_open,
.ndo_stop = qmimux_stop,
.ndo_start_xmit = qmimux_start_xmit,
};
static void qmimux_setup(struct net_device *dev)
{
dev->header_ops = NULL; /* No header */
dev->type = ARPHRD_NONE;
dev->hard_header_len = 0;
dev->addr_len = 0;
dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
dev->netdev_ops = &qmimux_netdev_ops;
dev->mtu = 1500;
dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;
dev->needs_free_netdev = true;
}
static struct net_device *qmimux_find_dev(struct usbnet *dev, u8 mux_id)
{
struct qmimux_priv *priv;
struct list_head *iter;
struct net_device *ldev;
rcu_read_lock();
netdev_for_each_upper_dev_rcu(dev->net, ldev, iter) {
priv = netdev_priv(ldev);
if (priv->mux_id == mux_id) {
rcu_read_unlock();
return ldev;
}
}
rcu_read_unlock();
return NULL;
}
static bool qmimux_has_slaves(struct usbnet *dev)
{
return !list_empty(&dev->net->adj_list.upper);
}
static int qmimux_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
unsigned int len, offset = 0, pad_len, pkt_len;
struct qmimux_hdr *hdr;
struct net_device *net;
struct sk_buff *skbn;
u8 qmimux_hdr_sz = sizeof(*hdr);
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
|