Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 32f7adf

Browse files
bmorkdavem330
authored andcommitted
net: qmi_wwan: support "raw IP" mode
QMI wwan devices have traditionally emulated ethernet devices by default. But they have always had the capability of operating without any L2 header at all, transmitting and receiving "raw" IP packets over the USB link. This firmware feature used to be configurable through the QMI management protocol. Traditionally there was no way to verify the firmware mode without attempting to change it. And the firmware would often disallow changes anyway, i.e. due to a session already being established. In some cases, this could be a hidden firmware internal session, completely outside host control. For these reasons, sticking with the "well known" default mode was safest. But newer generations of QMI hardware and firmware have moved towards defaulting to "raw IP" mode instead, followed by an increasing number of bugs in the already buggy "802.3" firmware implementation. At the same time, the QMI management protocol gained the ability to detect the current mode. This has enabled the userspace QMI management application to verify the current firmware mode without trying to modify it. Following this development, the latest QMI hardware and firmware (the MDM9x30 generation) has dropped support for "802.3" mode entirely. Support for "raw IP" framing in the driver is therefore necessary for these devices, and to a certain degree to work around problems with the previous generation, This patch adds support for "raw IP" framing for QMI devices, changing the netdev from an ethernet device to an ARPHRD_NONE p-t-p device when "raw IP" framing is enabled. The firmware setup is fully delegated to the QMI userspace management application, through simple tunneling of the QMI protocol. The driver will therefore not know which mode has been "negotiated" between firmware and userspace. Allowing userspace to inform the driver of the result through a sysfs switch is considered a better alternative than to change the well established clean delegation of firmware management to userspace. Signed-off-by: Bjørn Mork <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 81e0ce7 commit 32f7adf

File tree

1 file changed

+97
-1
lines changed

1 file changed

+97
-1
lines changed

drivers/net/usb/qmi_wwan.c

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <linux/netdevice.h>
1515
#include <linux/ethtool.h>
1616
#include <linux/etherdevice.h>
17+
#include <linux/if_arp.h>
1718
#include <linux/mii.h>
1819
#include <linux/usb.h>
1920
#include <linux/usb/cdc.h>
@@ -48,11 +49,93 @@
4849
struct qmi_wwan_state {
4950
struct usb_driver *subdriver;
5051
atomic_t pmcount;
51-
unsigned long unused;
52+
unsigned long flags;
5253
struct usb_interface *control;
5354
struct usb_interface *data;
5455
};
5556

57+
enum qmi_wwan_flags {
58+
QMI_WWAN_FLAG_RAWIP = 1 << 0,
59+
};
60+
61+
static void qmi_wwan_netdev_setup(struct net_device *net)
62+
{
63+
struct usbnet *dev = netdev_priv(net);
64+
struct qmi_wwan_state *info = (void *)&dev->data;
65+
66+
if (info->flags & QMI_WWAN_FLAG_RAWIP) {
67+
net->header_ops = NULL; /* No header */
68+
net->type = ARPHRD_NONE;
69+
net->hard_header_len = 0;
70+
net->addr_len = 0;
71+
net->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
72+
netdev_dbg(net, "mode: raw IP\n");
73+
} else if (!net->header_ops) { /* don't bother if already set */
74+
ether_setup(net);
75+
netdev_dbg(net, "mode: Ethernet\n");
76+
}
77+
78+
/* recalculate buffers after changing hard_header_len */
79+
usbnet_change_mtu(net, net->mtu);
80+
}
81+
82+
static ssize_t raw_ip_show(struct device *d, struct device_attribute *attr, char *buf)
83+
{
84+
struct usbnet *dev = netdev_priv(to_net_dev(d));
85+
struct qmi_wwan_state *info = (void *)&dev->data;
86+
87+
return sprintf(buf, "%c\n", info->flags & QMI_WWAN_FLAG_RAWIP ? 'Y' : 'N');
88+
}
89+
90+
static ssize_t raw_ip_store(struct device *d, struct device_attribute *attr, const char *buf, size_t len)
91+
{
92+
struct usbnet *dev = netdev_priv(to_net_dev(d));
93+
struct qmi_wwan_state *info = (void *)&dev->data;
94+
bool enable;
95+
int err;
96+
97+
if (strtobool(buf, &enable))
98+
return -EINVAL;
99+
100+
/* no change? */
101+
if (enable == (info->flags & QMI_WWAN_FLAG_RAWIP))
102+
return len;
103+
104+
/* we don't want to modify a running netdev */
105+
if (netif_running(dev->net)) {
106+
netdev_err(dev->net, "Cannot change a running device\n");
107+
return -EBUSY;
108+
}
109+
110+
/* let other drivers deny the change */
111+
err = call_netdevice_notifiers(NETDEV_PRE_TYPE_CHANGE, dev->net);
112+
err = notifier_to_errno(err);
113+
if (err) {
114+
netdev_err(dev->net, "Type change was refused\n");
115+
return err;
116+
}
117+
118+
if (enable)
119+
info->flags |= QMI_WWAN_FLAG_RAWIP;
120+
else
121+
info->flags &= ~QMI_WWAN_FLAG_RAWIP;
122+
qmi_wwan_netdev_setup(dev->net);
123+
call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev->net);
124+
return len;
125+
}
126+
127+
static DEVICE_ATTR_RW(raw_ip);
128+
129+
static struct attribute *qmi_wwan_sysfs_attrs[] = {
130+
&dev_attr_raw_ip.attr,
131+
NULL,
132+
};
133+
134+
static struct attribute_group qmi_wwan_sysfs_attr_group = {
135+
.name = "qmi",
136+
.attrs = qmi_wwan_sysfs_attrs,
137+
};
138+
56139
/* default ethernet address used by the modem */
57140
static const u8 default_modem_addr[ETH_ALEN] = {0x02, 0x50, 0xf3};
58141

@@ -80,6 +163,8 @@ static const u8 buggy_fw_addr[ETH_ALEN] = {0x00, 0xa0, 0xc6, 0x00, 0x00, 0x00};
80163
*/
81164
static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
82165
{
166+
struct qmi_wwan_state *info = (void *)&dev->data;
167+
bool rawip = info->flags & QMI_WWAN_FLAG_RAWIP;
83168
__be16 proto;
84169

85170
/* This check is no longer done by usbnet */
@@ -94,15 +179,25 @@ static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
94179
proto = htons(ETH_P_IPV6);
95180
break;
96181
case 0x00:
182+
if (rawip)
183+
return 0;
97184
if (is_multicast_ether_addr(skb->data))
98185
return 1;
99186
/* possibly bogus destination - rewrite just in case */
100187
skb_reset_mac_header(skb);
101188
goto fix_dest;
102189
default:
190+
if (rawip)
191+
return 0;
103192
/* pass along other packets without modifications */
104193
return 1;
105194
}
195+
if (rawip) {
196+
skb->dev = dev->net; /* normally set by eth_type_trans */
197+
skb->protocol = proto;
198+
return 1;
199+
}
200+
106201
if (skb_headroom(skb) < ETH_HLEN)
107202
return 0;
108203
skb_push(skb, ETH_HLEN);
@@ -326,6 +421,7 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
326421
dev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */
327422
}
328423
dev->net->netdev_ops = &qmi_wwan_netdev_ops;
424+
dev->net->sysfs_groups[0] = &qmi_wwan_sysfs_attr_group;
329425
err:
330426
return status;
331427
}

0 commit comments

Comments
 (0)