Commit 2141eaf0 authored by David S. Miller's avatar David S. Miller

Merge branch 'qmi_wwan_MDM9x30'

Bjørn Mork says:

====================
net: qmi_wwan: MDM9x30 support

We add new device IDs all the time, often without any testing on
actual hardware. This is usually OK as long as the device is similar
to already supported devices, using the same chipset and firmware
basis.  But the Sierra Wireless MC7455 is an example of a new chipset
generation. Adding it based on assumed similarity with its ancestors
proved too optimistic.

This series adds the missing bits and pieces necessary to support LTE
Advanced modems based on the Qualcomm MDM9x30 chipset. A big thanks to
Sierra Wireless for providing MC7455 samples for testing

The most important change is the "raw-ip" support. The series also
adds a necessary control request, removes an unsupported device ID,
and adds a driver specific entry in MAINTAINERS.

A few random notes about "raw-ip":

"I rather have these all running in raw IP mode. The 802.3 framing is
utterly stupid." - Marcel Holtmann in Jan 2012 [1]

Marcel was right.  I should have listened to him. What more can I say?

The 802.3 framing has provided a steady supply of firmware bugs for
many years. We've added driver workarounds for many of these, but
there are still known bugs where the workaround is so yucky that we
have refused to apply it. But all that is over now.  The latest
generation Qualcomm chips no longer supports 802.3 framing at all.

I had two open questions regarding the "raw-ip" userspace API:

1) Should we continue faking an ethernet device, even if we don't use
   the L2 headers on the USB link anymore?

   There was a vote in favour of the "headerless" device. This is the
   honest representation of the hardware/firmware interface.

2) What input should the driver base its framing on?

   Snooping or directly manipulating QMI is considered out of the
   question. We delegated all QMI handling to userspace from the
   beginning.

   We have so far required userspace to configure the firmware for
   "802.3" framing, or fail if that proved impossible.  This
   requirement is now changed.  Userspace must now inform the driver
   if it negotiates "raw-ip" framing.  Two alternative interfaces were
   proposed:
    - ethtool private driver flag, or
    - sysfs file

   The NetworkManager/ModemManager developers were in favour of the
   sysfs alternative.

These questions (or any other you migh have :) are of course still
open.  This patch set presents the solutions I currently prefer,
considering the above.

All comments are appreciated, even simple '+1' ones.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 43dd7a8b 4521b477
What: /sys/class/net/<iface>/qmi/raw_ip
Date: Dec 2015
KernelVersion: 4.4
Contact: Bjørn Mork <bjorn@mork.no>
Description:
Boolean. Default: 'N'
Set this to 'Y' to change the network device link
framing from '802.3' to 'raw-ip'.
The netdev will change to reflect the link framing
mode. The netdev is an ordinary ethernet device in
'802.3' mode, and the driver expects to exchange
frames with an ethernet header over the USB link. The
netdev is a headerless p-t-p device in 'raw-ip' mode,
and the driver expects to echange IPv4 or IPv6 packets
without any L2 header over the USB link.
Userspace is in full control of firmware configuration
through the delegation of the QMI protocol. Userspace
is responsible for coordination of driver and firmware
link framing mode, changing this setting to 'Y' if the
firmware is configured for 'raw-ip' mode.
...@@ -11181,6 +11181,13 @@ L: linux-usb@vger.kernel.org ...@@ -11181,6 +11181,13 @@ L: linux-usb@vger.kernel.org
S: Supported S: Supported
F: drivers/usb/class/usblp.c F: drivers/usb/class/usblp.c
USB QMI WWAN NETWORK DRIVER
M: Bjørn Mork <bjorn@mork.no>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-class-net-qmi
F: drivers/net/usb/qmi_wwan.c
USB RTL8150 DRIVER USB RTL8150 DRIVER
M: Petko Manolov <petkan@nucleusys.com> M: Petko Manolov <petkan@nucleusys.com>
L: linux-usb@vger.kernel.org L: linux-usb@vger.kernel.org
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/ethtool.h> #include <linux/ethtool.h>
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/mii.h> #include <linux/mii.h>
#include <linux/usb.h> #include <linux/usb.h>
#include <linux/usb/cdc.h> #include <linux/usb/cdc.h>
...@@ -48,11 +49,93 @@ ...@@ -48,11 +49,93 @@
struct qmi_wwan_state { struct qmi_wwan_state {
struct usb_driver *subdriver; struct usb_driver *subdriver;
atomic_t pmcount; atomic_t pmcount;
unsigned long unused; unsigned long flags;
struct usb_interface *control; struct usb_interface *control;
struct usb_interface *data; struct usb_interface *data;
}; };
enum qmi_wwan_flags {
QMI_WWAN_FLAG_RAWIP = 1 << 0,
};
static void qmi_wwan_netdev_setup(struct net_device *net)
{
struct usbnet *dev = netdev_priv(net);
struct qmi_wwan_state *info = (void *)&dev->data;
if (info->flags & QMI_WWAN_FLAG_RAWIP) {
net->header_ops = NULL; /* No header */
net->type = ARPHRD_NONE;
net->hard_header_len = 0;
net->addr_len = 0;
net->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
netdev_dbg(net, "mode: raw IP\n");
} else if (!net->header_ops) { /* don't bother if already set */
ether_setup(net);
netdev_dbg(net, "mode: Ethernet\n");
}
/* recalculate buffers after changing hard_header_len */
usbnet_change_mtu(net, net->mtu);
}
static ssize_t raw_ip_show(struct device *d, struct device_attribute *attr, char *buf)
{
struct usbnet *dev = netdev_priv(to_net_dev(d));
struct qmi_wwan_state *info = (void *)&dev->data;
return sprintf(buf, "%c\n", info->flags & QMI_WWAN_FLAG_RAWIP ? 'Y' : 'N');
}
static ssize_t raw_ip_store(struct device *d, struct device_attribute *attr, const char *buf, size_t len)
{
struct usbnet *dev = netdev_priv(to_net_dev(d));
struct qmi_wwan_state *info = (void *)&dev->data;
bool enable;
int err;
if (strtobool(buf, &enable))
return -EINVAL;
/* no change? */
if (enable == (info->flags & QMI_WWAN_FLAG_RAWIP))
return len;
/* we don't want to modify a running netdev */
if (netif_running(dev->net)) {
netdev_err(dev->net, "Cannot change a running device\n");
return -EBUSY;
}
/* let other drivers deny the change */
err = call_netdevice_notifiers(NETDEV_PRE_TYPE_CHANGE, dev->net);
err = notifier_to_errno(err);
if (err) {
netdev_err(dev->net, "Type change was refused\n");
return 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);
return len;
}
static DEVICE_ATTR_RW(raw_ip);
static struct attribute *qmi_wwan_sysfs_attrs[] = {
&dev_attr_raw_ip.attr,
NULL,
};
static struct attribute_group qmi_wwan_sysfs_attr_group = {
.name = "qmi",
.attrs = qmi_wwan_sysfs_attrs,
};
/* default ethernet address used by the modem */ /* default ethernet address used by the modem */
static const u8 default_modem_addr[ETH_ALEN] = {0x02, 0x50, 0xf3}; static const u8 default_modem_addr[ETH_ALEN] = {0x02, 0x50, 0xf3};
...@@ -80,6 +163,8 @@ static const u8 buggy_fw_addr[ETH_ALEN] = {0x00, 0xa0, 0xc6, 0x00, 0x00, 0x00}; ...@@ -80,6 +163,8 @@ static const u8 buggy_fw_addr[ETH_ALEN] = {0x00, 0xa0, 0xc6, 0x00, 0x00, 0x00};
*/ */
static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb) static int 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; __be16 proto;
/* This check is no longer done by usbnet */ /* 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) ...@@ -94,15 +179,25 @@ static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
proto = htons(ETH_P_IPV6); proto = htons(ETH_P_IPV6);
break; break;
case 0x00: case 0x00:
if (rawip)
return 0;
if (is_multicast_ether_addr(skb->data)) if (is_multicast_ether_addr(skb->data))
return 1; return 1;
/* possibly bogus destination - rewrite just in case */ /* possibly bogus destination - rewrite just in case */
skb_reset_mac_header(skb); skb_reset_mac_header(skb);
goto fix_dest; goto fix_dest;
default: default:
if (rawip)
return 0;
/* pass along other packets without modifications */ /* pass along other packets without modifications */
return 1; return 1;
} }
if (rawip) {
skb->dev = dev->net; /* normally set by eth_type_trans */
skb->protocol = proto;
return 1;
}
if (skb_headroom(skb) < ETH_HLEN) if (skb_headroom(skb) < ETH_HLEN)
return 0; return 0;
skb_push(skb, ETH_HLEN); skb_push(skb, ETH_HLEN);
...@@ -223,6 +318,20 @@ static int qmi_wwan_register_subdriver(struct usbnet *dev) ...@@ -223,6 +318,20 @@ static int qmi_wwan_register_subdriver(struct usbnet *dev)
return rv; 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
*/
static int qmi_wwan_change_dtr(struct usbnet *dev, bool on)
{
u8 intf = dev->intf->cur_altsetting->desc.bInterfaceNumber;
return usbnet_write_cmd(dev, USB_CDC_REQ_SET_CONTROL_LINE_STATE,
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
on ? 0x01 : 0x00, intf, NULL, 0);
}
static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf) static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
{ {
int status = -1; int status = -1;
...@@ -280,6 +389,24 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf) ...@@ -280,6 +389,24 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
usb_driver_release_interface(driver, info->data); 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.
*/
if (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 /* 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 told us to. Or, if device is assigned the well-known
* buggy firmware MAC address, replace it with a random address, * buggy firmware MAC address, replace it with a random address,
...@@ -294,6 +421,7 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf) ...@@ -294,6 +421,7 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
dev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */ dev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */
} }
dev->net->netdev_ops = &qmi_wwan_netdev_ops; dev->net->netdev_ops = &qmi_wwan_netdev_ops;
dev->net->sysfs_groups[0] = &qmi_wwan_sysfs_attr_group;
err: err:
return status; return status;
} }
...@@ -307,6 +435,12 @@ static void qmi_wwan_unbind(struct usbnet *dev, struct usb_interface *intf) ...@@ -307,6 +435,12 @@ static void qmi_wwan_unbind(struct usbnet *dev, struct usb_interface *intf)
if (info->subdriver && info->subdriver->disconnect) if (info->subdriver && info->subdriver->disconnect)
info->subdriver->disconnect(info->control); info->subdriver->disconnect(info->control);
/* disable MDM9x30 quirk */
if (le16_to_cpu(dev->udev->descriptor.bcdUSB) >= 0x0201) {
qmi_wwan_change_dtr(dev, false);
qmi_wwan_manage_power(dev, 0);
}
/* allow user to unbind using either control or data */ /* allow user to unbind using either control or data */
if (intf == info->control) if (intf == info->control)
other = info->data; other = info->data;
...@@ -715,8 +849,6 @@ static const struct usb_device_id products[] = { ...@@ -715,8 +849,6 @@ static const struct usb_device_id products[] = {
{QMI_FIXED_INTF(0x1199, 0x9056, 8)}, /* Sierra Wireless Modem */ {QMI_FIXED_INTF(0x1199, 0x9056, 8)}, /* Sierra Wireless Modem */
{QMI_FIXED_INTF(0x1199, 0x9057, 8)}, {QMI_FIXED_INTF(0x1199, 0x9057, 8)},
{QMI_FIXED_INTF(0x1199, 0x9061, 8)}, /* Sierra Wireless Modem */ {QMI_FIXED_INTF(0x1199, 0x9061, 8)}, /* Sierra Wireless Modem */
{QMI_FIXED_INTF(0x1199, 0x9070, 8)}, /* Sierra Wireless MC74xx/EM74xx */
{QMI_FIXED_INTF(0x1199, 0x9070, 10)}, /* Sierra Wireless MC74xx/EM74xx */
{QMI_FIXED_INTF(0x1199, 0x9071, 8)}, /* Sierra Wireless MC74xx/EM74xx */ {QMI_FIXED_INTF(0x1199, 0x9071, 8)}, /* Sierra Wireless MC74xx/EM74xx */
{QMI_FIXED_INTF(0x1199, 0x9071, 10)}, /* Sierra Wireless MC74xx/EM74xx */ {QMI_FIXED_INTF(0x1199, 0x9071, 10)}, /* Sierra Wireless MC74xx/EM74xx */
{QMI_FIXED_INTF(0x1bbb, 0x011e, 4)}, /* Telekom Speedstick LTE II (Alcatel One Touch L100V LTE) */ {QMI_FIXED_INTF(0x1bbb, 0x011e, 4)}, /* Telekom Speedstick LTE II (Alcatel One Touch L100V LTE) */
......
...@@ -324,7 +324,10 @@ void usbnet_skb_return (struct usbnet *dev, struct sk_buff *skb) ...@@ -324,7 +324,10 @@ void usbnet_skb_return (struct usbnet *dev, struct sk_buff *skb)
return; return;
} }
skb->protocol = eth_type_trans (skb, dev->net); /* only update if unset to allow minidriver rx_fixup override */
if (skb->protocol == 0)
skb->protocol = eth_type_trans (skb, dev->net);
dev->net->stats.rx_packets++; dev->net->stats.rx_packets++;
dev->net->stats.rx_bytes += skb->len; dev->net->stats.rx_bytes += skb->len;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment