aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2013-04-19 17:51:26 -0400
committerDavid S. Miller <davem@davemloft.net>2013-04-19 17:51:26 -0400
commitb79d4a8dfd1b3a8387788b070ac2f856fb3a0877 (patch)
treec9a0a487312539688bcac21db127bcbbef842764
parent0cb670eef554652d0aa40bf09bd850e62598fc6c (diff)
parentcc6ba5fdaabea7a7b28de3ba1e0fe54d92232fe5 (diff)
Merge branch 'qmi_wwan'
Bjørn Mork says: ==================== This series adds workarounds for 3 different firmware bugs, each preventing the affected devices from working at all. I therefore humbly request that these fixes go to stable-3.8 (if still maintained) and 3.9 (either via net if still possible, or via stable if not). All 3 workarounds are applied to all devices supported by the driver. Adding quirks for specific devices was considered as an alternative, but was rejected because we have too little information about the exact distribution of the buggy firmwares. All we know is that the same bug shows up in devices from at least 3 different, and presumably independent, vendors. The workarounds have instead been designed to automatically apply when necessary, and to have as little impact as possible on unaffected devices. The series has been tested on a number of devices both with and without these bugs. The series should apply cleanly to net/master, net-next/master and stable/linux-3.8.y ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--drivers/net/usb/qmi_wwan.c104
1 files changed, 104 insertions, 0 deletions
diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
index 968d5d50751..2a3579f6791 100644
--- a/drivers/net/usb/qmi_wwan.c
+++ b/drivers/net/usb/qmi_wwan.c
@@ -13,6 +13,7 @@
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
+#include <linux/etherdevice.h>
#include <linux/mii.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
@@ -52,6 +53,96 @@ struct qmi_wwan_state {
struct usb_interface *data;
};
+/* default ethernet address used by the modem */
+static const u8 default_modem_addr[ETH_ALEN] = {0x02, 0x50, 0xf3};
+
+/* 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.
+ */
+static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+ __be16 proto;
+
+ /* usbnet rx_complete guarantees that skb->len is at least
+ * hard_header_len, so we can inspect the dest address without
+ * checking skb->len
+ */
+ switch (skb->data[0] & 0xf0) {
+ case 0x40:
+ proto = htons(ETH_P_IP);
+ break;
+ case 0x60:
+ proto = htons(ETH_P_IPV6);
+ break;
+ case 0x00:
+ 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:
+ /* pass along other packets without modifications */
+ return 1;
+ }
+ if (skb_headroom(skb) < ETH_HLEN)
+ return 0;
+ skb_push(skb, ETH_HLEN);
+ skb_reset_mac_header(skb);
+ eth_hdr(skb)->h_proto = proto;
+ memset(eth_hdr(skb)->h_source, 0, ETH_ALEN);
+fix_dest:
+ memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
+ return 1;
+}
+
+/* very simplistic detection of IPv4 or IPv6 headers */
+static bool possibly_iphdr(const char *data)
+{
+ return (data[0] & 0xd0) == 0x40;
+}
+
+/* disallow addresses which may be confused with IP headers */
+static int 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;
+}
+
+static const struct net_device_ops qmi_wwan_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 = usbnet_change_mtu,
+ .ndo_set_mac_address = qmi_wwan_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+};
+
/* using a counter to merge subdriver requests with our own into a combined state */
static int qmi_wwan_manage_power(struct usbnet *dev, int on)
{
@@ -229,6 +320,18 @@ next_desc:
usb_driver_release_interface(driver, info->data);
}
+ /* Never use the same address on both ends of the link, even
+ * if the buggy firmware told us to.
+ */
+ if (!compare_ether_addr(dev->net->dev_addr, default_modem_addr))
+ eth_hw_addr_random(dev->net);
+
+ /* make MAC addr easily distinguishable from an IP header */
+ if (possibly_iphdr(dev->net->dev_addr)) {
+ dev->net->dev_addr[0] |= 0x02; /* set local assignment bit */
+ dev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */
+ }
+ dev->net->netdev_ops = &qmi_wwan_netdev_ops;
err:
return status;
}
@@ -307,6 +410,7 @@ static const struct driver_info qmi_wwan_info = {
.bind = qmi_wwan_bind,
.unbind = qmi_wwan_unbind,
.manage_power = qmi_wwan_manage_power,
+ .rx_fixup = qmi_wwan_rx_fixup,
};
#define HUAWEI_VENDOR_ID 0x12D1