[RFC experiment 2/2] qmi_wwan: support "raw IP" mode

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



QMI wwan devices will emulate ethernet devices by default. But
they can also operate without any L2 header at all, transmitting
and receiving "raw" IP packets.  This mode can be selected by
sending QMI_CTL command 0x0026 (SET_DATA_FORMAT) with TLV 0x10
(le16) set to 0x0001 for ethernet or 0x0002 for raw IP.

This driver must be informed about the mode change by setting
the raw_ip_mode private flag:

  ethtool --set-priv-flags wwan0 raw_ip_mode on

IP configuration must be done manually, using the IP address from
either QMI_WDS command 0x002d or AT command AT^DHCP, as normal
DHCP clients won't work with this type of headerless interfaces.

QMI wwan devices also support prepending a QoS header to TX
packets. This mode is selected by including TLV 0x01 (u8) in the
SET_DATA_FORMAT command with the value 0x00 for no QoS header
or 0x01 for signalling that an QoS header will be prepended.

This driver must be informed about the mode change by setting
the qmi_qos_header private flag:

  ethtool --set-priv-flags wwan0 qmi_qos_header on

The QMI QoS header "flow_id" field will be set based on the
netfilter "mark".  Note that the meaning of "flow_id" is
undocumented and that some values may crash the device.

Signed-off-by: Bjørn Mork <bjorn@xxxxxxx>
---
 drivers/net/usb/qmi_wwan.c |  154 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 154 insertions(+), 0 deletions(-)

diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
index 546dca5..50eff4e 100644
--- a/drivers/net/usb/qmi_wwan.c
+++ b/drivers/net/usb/qmi_wwan.c
@@ -8,6 +8,7 @@
 
 #include <linux/module.h>
 #include <linux/netdevice.h>
+#include <linux/if_arp.h>
 #include <linux/ethtool.h>
 #include <linux/mii.h>
 #include <linux/usb.h>
@@ -18,6 +19,109 @@
 /* The name of the CDC Device Management driver */
 #define DM_DRIVER "cdc_wdm"
 
+enum qmi_wwan_flags {
+	QMI_WWAN_FLAG_RAWIP = 1<<0,
+	QMI_WWAN_FLAG_QOS   = 1<<1,
+};
+
+struct qmi_qos_hdr {
+	u8 version;	/* always 1? */
+	u8 flags;	/* and possible values are? */
+	u32 flow_id;	/* presumably LE, but why care? */
+} __packed;
+
+
+/* ethtool */
+#define QMI_WWAN_ETHTOOL_PFLAGS ( QMI_WWAN_FLAG_RAWIP | QMI_WWAN_FLAG_QOS )
+
+static const char qmi_wwan_gstrings_pflags[][ETH_GSTRING_LEN] = {
+	"raw_ip_mode",
+	"qmi_qos_header",
+};
+
+static int qmi_wwan_get_sset_count(struct net_device *net, int sset)
+{
+	switch (sset) {
+	case ETH_SS_PRIV_FLAGS:
+		return ARRAY_SIZE(qmi_wwan_gstrings_pflags);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static void qmi_wwan_get_strings(struct net_device *net, u32 stringset, u8 *data)
+{
+	switch (stringset) {
+	case  ETH_SS_PRIV_FLAGS:
+		memcpy(data, qmi_wwan_gstrings_pflags, sizeof(qmi_wwan_gstrings_pflags));
+		break;
+	}
+}
+
+static u32 qmi_wwan_get_pflags(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+	unsigned long flags = dev->data[2];
+
+	return flags;
+}
+
+static void qmi_wwan_netdev_setup(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+	unsigned long flags = dev->data[2];
+
+	/* ether_setup() has already set sane ethernet settings for us */
+	if (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: IP\n");
+	} else if (!net->header_ops) { /* don't bother if already set */
+		ether_setup(net);
+		netdev_dbg(net, "mode: Ethernet\n");
+	}
+}
+
+static int qmi_wwan_set_pflags(struct net_device *net, u32 data)
+{
+	struct usbnet *dev = netdev_priv(net);
+	unsigned long *flags = &dev->data[2];
+
+	if (data & ~QMI_WWAN_ETHTOOL_PFLAGS) {
+		netdev_info(net, "unspported private flags: 0x%08x\n", data);
+		return -EINVAL;
+	}
+
+	if (netif_running(net)) {
+		netdev_info(net, "cannot change modes while device is open\n");
+		return -EBUSY;
+	}
+	*flags = data;
+
+	/* possibly reconfigure netdev according to new flags */
+	qmi_wwan_netdev_setup(net);
+
+	return 0;
+}
+
+/* add private flags set/get to default usbnet ops */
+static const struct ethtool_ops qmi_wwan_ethtool_ops = {
+	.get_settings	= usbnet_get_settings,
+	.set_settings	= usbnet_set_settings,
+	.get_link	= usbnet_get_link,
+	.nway_reset	= usbnet_nway_reset,
+	.get_drvinfo	= usbnet_get_drvinfo,
+	.get_msglevel	= usbnet_get_msglevel,
+	.set_msglevel	= usbnet_set_msglevel,
+	.get_strings	= qmi_wwan_get_strings,
+	.get_sset_count	= qmi_wwan_get_sset_count,
+	.get_priv_flags	= qmi_wwan_get_pflags,
+	.set_priv_flags	= qmi_wwan_set_pflags,
+};
+
 /*
  * This driver supports wwan (3G/LTE/?) devices using a vendor
  * specific management protocol called Qualcomm MSM Interface (QMI) -
@@ -167,6 +271,9 @@ next_desc:
 	/* collect bulk endpoints now that we know intf == "data" interface */
 	status = usbnet_get_endpoints(dev, intf);
 
+	/* support some additional ethtool operations */
+	dev->net->ethtool_ops = &qmi_wwan_ethtool_ops;
+
 err:
 	return status;
 }
@@ -233,6 +340,9 @@ static int qmi_wwan_bind_shared(struct usbnet *dev, struct usb_interface *intf)
 	/* save subdriver struct for suspend/resume wrappers */
 	dev->data[0] = (unsigned long)subdriver;
 
+	/* support some additional ethtool operations */
+	dev->net->ethtool_ops = &qmi_wwan_ethtool_ops;
+
 err:
 	return rv;
 }
@@ -270,6 +380,44 @@ static void qmi_wwan_unbind_shared(struct usbnet *dev, struct usb_interface *int
 	dev->data[0] = (unsigned long)NULL;
 }
 
+/* fixup if we receive raw IP packets */
+static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	unsigned long flags = dev->data[2];
+
+	if (flags & QMI_WWAN_FLAG_RAWIP) {
+		skb->dev = dev->net; /* normally set by eth_type_trans */
+		switch (skb->data[0] & 0xf0) {
+		case 0x40:
+			skb->protocol = htons(ETH_P_IP);
+			break;
+		case 0x60:
+			skb->protocol = htons(ETH_P_IPV6);
+			break;
+		default:
+			/* drop non IP packets */
+			return 0;
+		}
+	}
+	return 1;
+}
+
+/* add QMI QoS header if requested */
+static struct sk_buff *qmi_wwan_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t alloc_flags)
+{
+	unsigned long flags = dev->data[2];
+
+	/* QoS header - shamelessly stolen from msm_rmnet */
+	if (flags & QMI_WWAN_FLAG_QOS) {
+		struct qmi_qos_hdr *qmih = (void *)skb_push(skb, sizeof(struct qmi_qos_hdr));
+		qmih->version = 1;
+		qmih->flags = 0;
+		qmih->flow_id = skb->mark;
+		netif_dbg(dev, tx_queued, dev->net, "adding QoS header with flow_id 0x%08x", qmih->flow_id);
+	}
+ 	return skb;
+}
+
 /* suspend/resume wrappers calling both usbnet and the cdc-wdm
  * subdriver if present.
  *
@@ -317,6 +465,8 @@ static const struct driver_info	qmi_wwan_info = {
 	.flags		= FLAG_WWAN,
 	.bind		= qmi_wwan_bind,
 	.manage_power	= qmi_wwan_manage_power,
+	.rx_fixup	= qmi_wwan_rx_fixup,
+	.tx_fixup	= qmi_wwan_tx_fixup,
 };
 
 static const struct driver_info	qmi_wwan_shared = {
@@ -325,6 +475,8 @@ static const struct driver_info	qmi_wwan_shared = {
 	.bind		= qmi_wwan_bind_shared,
 	.unbind		= qmi_wwan_unbind_shared,
 	.manage_power	= qmi_wwan_manage_power,
+	.rx_fixup	= qmi_wwan_rx_fixup,
+	.tx_fixup	= qmi_wwan_tx_fixup,
 };
 
 static const struct driver_info	qmi_wwan_gobi = {
@@ -333,6 +485,8 @@ static const struct driver_info	qmi_wwan_gobi = {
 	.bind		= qmi_wwan_bind_gobi,
 	.unbind		= qmi_wwan_unbind_shared,
 	.manage_power	= qmi_wwan_manage_power,
+	.rx_fixup	= qmi_wwan_rx_fixup,
+	.tx_fixup	= qmi_wwan_tx_fixup,
 };
 
 #define HUAWEI_VENDOR_ID	0x12D1
-- 
1.7.9

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux