Subject: [RFC] tag_daddr: add tagging scheme used by Marvel 88e6065 switch

From: Pavel Machek <[email-masked]>
Date: Thu, 6 Dec 2018 14:06:23 +0100

Tagging scheme used by 88e6065 is "interesting" as it moves around
ethernet addreses and forces us to use PROMISC mode on the ethernets.

I'm not sure how to call it, so I selected tag_daddr ("tag where
destination address should be").

Signed-off-by: Pavel Machek <[email-masked]>

diff --git a/include/net/dsa.h b/include/net/dsa.h
index 461e8a7661b7..187da100d468 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -40,6 +40,7 @@ enum dsa_tag_protocol {
 	DSA_TAG_PROTO_MTK,
 	DSA_TAG_PROTO_QCA,
 	DSA_TAG_PROTO_TRAILER,
+	DSA_TAG_PROTO_DADDR,
 	DSA_TAG_LAST,		/* MUST BE LAST */
 };
 
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 4183e4ba27a5..1c177b6c5127 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -32,6 +32,9 @@ config NET_DSA_TAG_BRCM
 config NET_DSA_TAG_BRCM_PREPEND
 	bool
 
+config NET_DSA_TAG_DADDR
+	bool "Support for DADDR tagging, found on Marvell 88e6065" 
+
 config NET_DSA_TAG_DSA
 	bool
 
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index 9e4d3536f977..4466c1b860cc 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -7,6 +7,7 @@ dsa_core-$(CONFIG_NET_DSA_LEGACY) += legacy.o
 # tagging formats
 dsa_core-$(CONFIG_NET_DSA_TAG_BRCM) += tag_brcm.o
 dsa_core-$(CONFIG_NET_DSA_TAG_BRCM_PREPEND) += tag_brcm.o
+dsa_core-$(CONFIG_NET_DSA_TAG_DADDR) += tag_daddr.o
 dsa_core-$(CONFIG_NET_DSA_TAG_DSA) += tag_dsa.o
 dsa_core-$(CONFIG_NET_DSA_TAG_EDSA) += tag_edsa.o
 dsa_core-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 9f3209ff7ffd..97d289b0ef9d 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -46,6 +46,9 @@ const struct dsa_device_ops *dsa_device_ops[DSA_TAG_LAST] = {
 #ifdef CONFIG_NET_DSA_TAG_BRCM_PREPEND
 	[DSA_TAG_PROTO_BRCM_PREPEND] = &brcm_prepend_netdev_ops,
 #endif
+#ifdef CONFIG_NET_DSA_TAG_DADDR
+	[DSA_TAG_PROTO_DADDR] = &daddr_netdev_ops,
+#endif
 #ifdef CONFIG_NET_DSA_TAG_DSA
 	[DSA_TAG_PROTO_DSA] = &dsa_netdev_ops,
 #endif
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index 3964c6f7a7c0..0530b125ef72 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -199,6 +199,9 @@ void dsa_switch_unregister_notifier(struct dsa_switch *ds);
 extern const struct dsa_device_ops brcm_netdev_ops;
 extern const struct dsa_device_ops brcm_prepend_netdev_ops;
 
+/* tag_daddr.c */
+extern const struct dsa_device_ops daddr_netdev_ops;
+
 /* tag_dsa.c */
 extern const struct dsa_device_ops dsa_netdev_ops;
 
diff --git a/net/dsa/tag_daddr.c b/net/dsa/tag_daddr.c
new file mode 100644
index 000000000000..8e1e4de4df81
--- /dev/null
+++ b/net/dsa/tag_daddr.c
@@ -0,0 +1,100 @@
+/*
+ * net/dsa/tag_daddr.c - Daddr tag format handling
+ * Copyright (c) 2018 Pavel Machek <[email-masked]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "dsa_priv.h"
+
+static struct sk_buff *daddr_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct dsa_port *dp = dsa_slave_to_port(dev);
+	struct sk_buff *nskb;
+	int padlen;
+	u8 *daddr;
+
+	padlen = 0;
+	if (skb->len < 60)
+		padlen = 60 - skb->len;
+
+	nskb = alloc_skb(NET_IP_ALIGN + skb->len + padlen + 2, GFP_ATOMIC);
+	if (!nskb)
+		return NULL;
+	skb_reserve(nskb, NET_IP_ALIGN);
+
+	skb_reset_mac_header(nskb);
+	skb_set_network_header(nskb, skb_network_header(skb) - skb->head);
+	skb_set_transport_header(nskb, skb_transport_header(skb) - skb->head);
+	skb_copy_and_csum_dev(skb, skb_put(nskb, skb->len));
+	consume_skb(skb);
+
+	if (padlen) {
+		skb_put_zero(nskb, padlen);
+	}
+
+	daddr = skb_push(nskb, 2);
+
+	nskb->data[0] = 0xff; /* dbnum << 4, 0x0e -- priority, 0x01 forcemap */
+	nskb->data[1] = 0xff; /* 0xc0 dbnum, 0x3f vlantable */
+
+	/* Hm. Seems same as PORT_VLAN_MAP value, with funny endian */
+	{
+#define PORT_VLAN_MAP_DBNUM_SHIFT	12
+	  
+		struct dsa_port *dp = dsa_slave_to_port(dev);
+		struct dsa_switch *ds = dp->ds;
+		int p = dp->index;
+
+		u16 vlan_map = ((p & 0xf) << PORT_VLAN_MAP_DBNUM_SHIFT) |
+		  		(dsa_is_cpu_port(ds, p) ? dsa_user_ports(ds) :
+			       BIT(p));
+
+		nskb->data[0] = vlan_map >> 8;
+		nskb->data[1] = vlan_map & 0xff;
+	}
+
+	return nskb;
+}
+
+#define DADDR_HDR_LEN 2
+
+static struct sk_buff *daddr_rcv(struct sk_buff *skb, struct net_device *dev,
+				   struct packet_type *pt)
+{
+	u8 ver;
+	int port;
+	u8 *phdr;
+
+	if (unlikely(!pskb_may_pull(skb, DADDR_HDR_LEN)))
+		return NULL;
+
+	/*
+	 * At this point, skb->data points to ethertype.
+	 */
+	phdr = (skb->data - 2 - 2 * ETH_ALEN);
+
+	/* Remove DADDR tag and recalculate checksum */
+	skb_pull_rcsum(skb, DADDR_HDR_LEN);
+
+	/* Get source port information */
+	port = phdr[1] & 0x07;
+
+	skb->dev = dsa_master_find_slave(dev, 0, port);
+	if (!skb->dev)
+		return NULL;
+
+	return skb;
+}
+
+const struct dsa_device_ops daddr_netdev_ops = {
+	.xmit	= daddr_xmit,
+	.rcv	= daddr_rcv,
+};

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html