--- /dev/null
+From b07ffec3cb03542e2169ab95fe74c4b6261777c8 Mon Sep 17 00:00:00 2001
+From: John Crispin <john@phrozen.org>
+Date: Tue, 1 Nov 2016 02:05:56 +0100
+Subject: [PATCH 21/22] net: dsa: qca8k: add offloading layer
+
+This patch adds support for various offloading features provided by the
+qca8k family of switches. This includes offloading for
+
+- ipv4 NAT
+- ipv4 routing (std gateway)
+- ipv6 routing
+
+Signed-off-by: John Crispin <john@phrozen.org>
+---
+ drivers/net/dsa/Kconfig | 9 +
+ drivers/net/dsa/Makefile | 2 +-
+ drivers/net/dsa/qca8k.c | 2 +-
+ drivers/net/dsa/qca8k.h | 114 +++-
+ drivers/net/dsa/qca8k_offload/Makefile | 20 +
+ drivers/net/dsa/qca8k_offload/compat.h | 76 +++
+ drivers/net/dsa/qca8k_offload/qca8k.h | 307 +++++++++++
+ drivers/net/dsa/qca8k_offload/qca8k_acl.c | 380 +++++++++++++
+ drivers/net/dsa/qca8k_offload/qca8k_arl.c | 207 +++++++
+ drivers/net/dsa/qca8k_offload/qca8k_arp.c | 341 ++++++++++++
+ drivers/net/dsa/qca8k_offload/qca8k_debugfs.c | 119 ++++
+ drivers/net/dsa/qca8k_offload/qca8k_fib.c | 140 +++++
+ drivers/net/dsa/qca8k_offload/qca8k_hook_ct.c | 412 ++++++++++++++
+ drivers/net/dsa/qca8k_offload/qca8k_hook_iface.c | 226 ++++++++
+ drivers/net/dsa/qca8k_offload/qca8k_iface.c | 212 ++++++++
+ drivers/net/dsa/qca8k_offload/qca8k_init.c | 103 ++++
+ drivers/net/dsa/qca8k_offload/qca8k_l3.c | 106 ++++
+ drivers/net/dsa/qca8k_offload/qca8k_napt.c | 334 ++++++++++++
+ drivers/net/dsa/qca8k_offload/qca8k_nat.c | 35 ++
+ drivers/net/dsa/qca8k_offload/qca8k_normalize.c | 173 ++++++
+ drivers/net/dsa/qca8k_offload/qca8k_private_ip.c | 112 ++++
+ drivers/net/dsa/qca8k_offload/qca8k_public_ip.c | 165 ++++++
+ drivers/net/dsa/qca8k_offload/qca8k_qos.c | 634 ++++++++++++++++++++++
+ drivers/net/dsa/qca8k_offload/qca8k_route.c | 108 ++++
+ drivers/net/dsa/qca8k_offload/qca8k_thread.c | 70 +++
+ 25 files changed, 4400 insertions(+), 7 deletions(-)
+ create mode 100644 drivers/net/dsa/qca8k_offload/Makefile
+ create mode 100644 drivers/net/dsa/qca8k_offload/compat.h
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k.h
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_acl.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_arl.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_arp.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_debugfs.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_fib.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_hook_ct.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_hook_iface.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_iface.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_init.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_l3.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_napt.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_nat.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_normalize.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_private_ip.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_public_ip.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_qos.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_route.c
+ create mode 100644 drivers/net/dsa/qca8k_offload/qca8k_thread.c
+
+Index: linux-4.9.34/drivers/net/dsa/Kconfig
+===================================================================
+--- linux-4.9.34.orig/drivers/net/dsa/Kconfig
++++ linux-4.9.34/drivers/net/dsa/Kconfig
+@@ -34,4 +34,13 @@ config NET_DSA_QCA8K
+ This enables support for the Qualcomm Atheros QCA8K Ethernet
+ switch chips.
+
++config NET_DSA_QCA8K_OFFLOAD
++ tristate "Qualcomm Atheros AR8K Ethernet switch family support"
++ depends on NET_DSA_QCA8K && NF_CONNTRACK && IPV6
++ select NF_CONNTRACK_MARK
++ select NF_CONNTRACK_QCA8K
++ ---help---
++ This enables support for the Qualcomm Atheros AR8XXX Ethernet
++ switch chips HW offloading feature.
++
+ endmenu
+Index: linux-4.9.34/drivers/net/dsa/Makefile
+===================================================================
+--- linux-4.9.34.orig/drivers/net/dsa/Makefile
++++ linux-4.9.34/drivers/net/dsa/Makefile
+@@ -1,6 +1,6 @@
+ obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o
+ obj-$(CONFIG_NET_DSA_BCM_SF2) += bcm_sf2.o
+-obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o
++obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o qca8k_offload/
+
+ obj-y += b53/
+ obj-y += mv88e6xxx/
+Index: linux-4.9.34/drivers/net/dsa/qca8k.c
+===================================================================
+--- linux-4.9.34.orig/drivers/net/dsa/qca8k.c
++++ linux-4.9.34/drivers/net/dsa/qca8k.c
+@@ -157,12 +157,12 @@ qca8k_read(struct qca8k_priv *priv, u32
+
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ qca8k_mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+ qca8k_set_page(priv->bus, page);
+ val = qca8k_mii_read32(priv->bus, 0x10 | r2, r1);
+
+- mutex_unlock(&priv->bus->mdio_lock);
++ qca8k_mutex_unlock(&priv->bus->mdio_lock);
+
+ return val;
+ }
+@@ -175,12 +175,12 @@ qca8k_write(struct qca8k_priv *priv, u32
+
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ qca8k_mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+ qca8k_set_page(priv->bus, page);
+ qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val);
+
+- mutex_unlock(&priv->bus->mdio_lock);
++ qca8k_mutex_unlock(&priv->bus->mdio_lock);
+ }
+ EXPORT_SYMBOL_GPL(qca8k_write);
+
+@@ -192,7 +192,7 @@ qca8k_rmw(struct qca8k_priv *priv, u32 r
+
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ qca8k_mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+ qca8k_set_page(priv->bus, page);
+ ret = qca8k_mii_read32(priv->bus, 0x10 | r2, r1);
+@@ -200,7 +200,7 @@ qca8k_rmw(struct qca8k_priv *priv, u32 r
+ ret |= val;
+ qca8k_mii_write32(priv->bus, 0x10 | r2, r1, ret);
+
+- mutex_unlock(&priv->bus->mdio_lock);
++ qca8k_mutex_unlock(&priv->bus->mdio_lock);
+
+ return ret;
+ }
+@@ -642,7 +642,7 @@ qca8k_setup(struct dsa_switch *ds)
+ /* For port based vlans to work we need to set the
+ * default egress vid
+ */
+- qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
++ qca8k_rmw(priv, QCA8K_EGRESS_VID(i),
+ 0xffff << shift, 1 << shift);
+ qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
+ QCA8K_PORT_VLAN_CVID(1) |
+Index: linux-4.9.34/drivers/net/dsa/qca8k.h
+===================================================================
+--- linux-4.9.34.orig/drivers/net/dsa/qca8k.h
++++ linux-4.9.34/drivers/net/dsa/qca8k.h
+@@ -45,6 +45,8 @@
+ #define QCA8K_PORT_PAD_RGMII_RX_DELAY_EN BIT(24)
+ #define QCA8K_PORT_PAD_SGMII_EN BIT(7)
+ #define QCA8K_REG_MODULE_EN 0x030
++#define QCA8K_MODULE_EN_L3 BIT(2)
++#define QCA8K_MODULE_EN_ACL BIT(1)
+ #define QCA8K_MODULE_EN_MIB BIT(0)
+ #define QCA8K_REG_MIB 0x034
+ #define QCA8K_MIB_FLUSH BIT(24)
+@@ -76,7 +78,15 @@
+ #define QCA8K_REG_EEE_CTRL 0x100
+ #define QCA8K_REG_EEE_CTRL_LPI_EN(_i) ((_i + 1) * 2)
+
++/* Parser control registers */
++#define QCA8K_REG_FRAME_ACK_CTRL0 0x0210
++#define QCA8K_FRAME_ACK_CTRL0_ARP_ACK BIT(5)
++
++#define QCA8K_REG_FRAME_ACK_CTRL1 0x0214
++#define QCA8K_FRAME_ACK_CTRL1_IMGP_V3_EN BIT(24)
++
+ /* ACL registers */
++#define QCA8K_REG_VLAN_TRANS_TEST 0x0418
+ #define QCA8K_REG_PORT_VLAN_CTRL0(_i) (0x420 + (_i * 8))
+ #define QCA8K_PORT_VLAN_CVID(x) (x << 16)
+ #define QCA8K_PORT_VLAN_SVID(x) x
+@@ -100,6 +110,8 @@
+ #define QCA8K_ATU_STATUS_STATIC 0xf
+ #define QCA8K_REG_ATU_FUNC 0x60c
+ #define QCA8K_ATU_FUNC_BUSY BIT(31)
++#define QCA8K_ATU_FUNC_IDX_M 0x1f
++#define QCA8K_ATU_FUNC_IDX_S 16
+ #define QCA8K_ATU_FUNC_PORT_EN BIT(14)
+ #define QCA8K_ATU_FUNC_MULTI_EN BIT(13)
+ #define QCA8K_ATU_FUNC_FULL BIT(12)
+@@ -114,6 +126,7 @@
+ #define QCA8K_GLOBAL_FW_CTRL1_BC_DP_S 16
+ #define QCA8K_GLOBAL_FW_CTRL1_MC_DP_S 8
+ #define QCA8K_GLOBAL_FW_CTRL1_UC_DP_S 0
++#define QCA8K_REG_TOS_PRI_MAP(p) (0x630 + ((p) * 0x4))
+ #define QCA8K_PORT_LOOKUP_CTRL(_i) (0x660 + (_i) * 0xc)
+ #define QCA8K_PORT_LOOKUP_MEMBER GENMASK(6, 0)
+ #define QCA8K_PORT_LOOKUP_STATE_MASK GENMASK(18, 16)
+@@ -124,30 +137,121 @@
+ #define QCA8K_PORT_LOOKUP_STATE_FORWARD (4 << 16)
+ #define QCA8K_PORT_LOOKUP_STATE GENMASK(18, 16)
+ #define QCA8K_PORT_LOOKUP_LEARN BIT(20)
++#define QCA8K_REG_QOS_PORT_PRI_CTRL(p) (0x664 + ((p) * 0xc))
++#define QCA8K_QOS_PORT_PRI_CTRL_M (0x7 << 16)
++#define QCA8K_QOS_PORT_PRI_CTRL_DA BIT(18)
++#define QCA8K_QOS_PORT_PRI_CTRL_VLAN BIT(17)
++#define QCA8K_QOS_PORT_PRI_CTRL_TOS BIT(16)
+ #define QCA8K_REG_PORT_LEARN_LIMIT(p) (0x668 + (p * 0xc))
+ #define QCA8K_PORT_LEARN_LIMIT_EN BIT(11)
+ #define QCA8K_PORT_LEARN_LIMIT_CNT_M 0x7ff
+ #define QCA8K_PORT_LEARN_LIMIT_STATUS (7 << 12)
+
++#define QCA8K_REG_QOS_GLOBAL_FLOW_THD 0x800
++#define QCA8K_REG_QOS_QM_CTRL 0x808
++#define QCA8K_REG_QOS_WAN_QUEUE_MAP 0x810
++#define QCA8K_REG_QOS_LAN_QUEUE_MAP 0x814
++#define QCA8K_REG_QOS_PORT_WRR_CTRL(p) (0x830 + ((p) * 4))
++#define QCA8K_QOS_PORT_WRR_CTRL_M 0x3
++#define QCA8K_QOS_PORT_WRR_CTRL_S 30
++#define QCA8K_QOS_PORT_WRR_PRIO_M 0x1f
++#define QCA8K_QOS_PORT_WRR_PRIO_S 5
++#define QCA8K_REG_QOS_ECTRL(p, r) (0x890 + ((r) * 4) + ((p) * 0x20))
++#define QCA8K_QOS_ECTRL_TYPE_M 0x3f
++#define QCA8K_QOS_ECTRL_TYPE_S 8
++#define QCA8K_QOS_ECTRL_RATE_EN 8
++#define QCA8K_QOS_ECTRL_BURST_M 0x7
++#define QCA8K_QOS_ECTRL_BURST_S 4
++#define QCA8K_QOS_ECTRL_IR_M 0x7fff
++#define QCA8K_QOS_ECTRL_IR_S 16
++#define QCA8K_QOS_ECTRL_TIME_M 0x7
++#define QCA8K_QOS_ECTRL7_Q_UNIT_S 8
++#define QCA8K_QOS_ECTRL7_RATE_EN BIT(3)
++#define QCA8K_REG_QOS_PORT_HOL_CTRL0(p) (0x970 + ((p) * 8))
++#define QCA8K_QOS_PORT_HOL0_EGRESS_M 0xffffff
++#define QCA8K_QOS_PORT_HOL0_PORT_M 0x3f
++#define QCA8K_QOS_PORT_HOL0_PORT_S 24
++#define QCA8K_QOS_PORT_HOL0_QUEUE_M 0xf
++#define QCA8K_QOS_PORT_HOL0_QUEUE_S 4
++#define QCA8K_REG_QOS_PORT_HOL_CTRL1(p) (0x974 + ((p) * 8))
++#define QCA8K_QOS_PORT_HOL1_QUEUE_ENABLE (BIT(6) | BIT(7))
++#define QCA8K_QOS_PORT_HOL1_QUEUE_WRED BIT(8)
++#define QCA8K_QOS_PORT_HOL1_INGRESS_M 0xf
++#define QCA8K_REG_QOS_PORT_FLOW_THD(p) (0x9b0 + ((p) * 4))
++#define QCA8K_QOS_PORT_FLOW_THD_XON_S 16
++#define QCA8K_REG_ACL_POLICY_MODE 0x9f0
++#define QCA8K_REG_ACL_COUNTER_MODE 0x9f4
++#define QCA8K_REG_ACL_COUNTER_RST 0x9f8
++#define QCA8K_REG_ACL_RATE_CTRL(idx, r) (0xa00 + ((idx) * 8) + ((r) * 4))
+
+ /* Pkt edit registers */
+-#define QCA8K_EGRESS_VLAN(x) (0x0c70 + (4 * (x / 2)))
++#define QCA8K_REG_PORT_QUEUE_REMAP 0xc40
++#define QCA8K_REG_DEF_VID0 0xc70
++#define QCA8K_REG_DEF_VID1 0xc74
++#define QCA8K_REG_DEF_VID2 0xc78
++
++#define QCA8K_EGRESS_VID(x) (0xc70 + (4 * (x / 2)))
++#define QCA8K_EGRESS_VLAN 0xc80
+
+ /* L3 registers */
+ #define QCA8K_HROUTER_CONTROL 0xe00
+ #define QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_M GENMASK(17, 16)
+ #define QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_S 16
+ #define QCA8K_HROUTER_CONTROL_ARP_AGE_MODE 1
++#define QCA8K_HROUTER_CONTROL_ROUTER_EN BIT(0)
++#define QCA8K_REG_HROUTER_PCONTROL0 0xe04
++#define QCA8K_HROUTER_PCONTROL0_M(x) (GENMASK(2, 0) << ((x) * 3))
++#define QCA8K_HROUTER_PCONTROL0_GUARD(x) (BIT(1) << ((x) * 3))
+ #define QCA8K_HROUTER_PBASED_CONTROL1 0xe08
+ #define QCA8K_HROUTER_PBASED_CONTROL2 0xe0c
+ #define QCA8K_HNAT_CONTROL 0xe38
++#define QCA8K_REG_NAPT_USED_COUNT 0xe44
++#define QCA8K_REG_L3_ENTRY_CTRL 0xe58
++#define QCA8K_L3_ENTRY_BUSY BIT(31)
++#define QCA8K_L3_ENTRY_STATUS BIT(7)
++#define QCA8K_REG_L3_ENTRY0 0xe80
++#define QCA8K_REG_L3_ENTRY2 0xe88
++#define QCA8K_REG_L3_ENTRY3 0xe8c
++#define QCA8K_REG_L3_ENTRY4 0xe90
++#define QCA8K_REG_L3_ENTRY6 0xe98
++
+
+ /* MIB registers */
+ #define QCA8K_PORT_MIB_COUNTER(_i) (0x1000 + (_i) * 0x100)
+
+-/* QCA specific MII registers */
+-#define MII_ATH_MMD_ADDR 0x0d
+-#define MII_ATH_MMD_DATA 0x0e
++#define QCA8K_REG_MAC_EDIT0(x) (0x2000 + ((x) << 4))
++#define QCA8K_REG_MAC_EDIT1(x) (0x2004 + ((x) << 4))
++
++#define QCA8K_REG_PUB_IP_EDIT0 0x2100
++#define QCA8K_REG_PUB_IP_EDIT1 0x2104
++
++#define QCA8K_REG_ACL_COUNTER(i, r) (0x1c000 + ((i) * 8) + ((r) * 4))
++
++#define QCA8K_REG_PUB_IP_OFFLOAD 0x2f000
++#define QCA8K_REG_PUB_IP_VALID 0x2f040
++
++#define QCA8K_REG_ACL_VLU(x) (0x58000 + ((x) << 5))
++/* ipv4 pattern */
++
++#define QCA8K_REG_ACL_MSK(x) (0x59000 + ((x) << 5))
++#define QCA8K_REG_ACL_ACT(x) (0x5a000 + ((x) << 4))
++#define QCA8K_ACL_ACT_CTAG_PRIORITY_S 29
++#define QCA8K_ACL_ACT_PRIORITY_EN BIT(28)
++#define QCA8K_ACL_ACT_PRIORITY_M 0xf
++#define QCA8K_ACL_ACT_PRIORITY_S 25
++#define QCA8K_ACL_ACT_ARP_IDX_S 17
++#define QCA8K_ACL_ACT_ARP_IDX_EN BIT(16)
++#define QCA8K_ACL_ACT_COUNTER_EN BIT(14)
++#define QCA8K_ACL_ACT_STAG_PRIORITY_S 13
++#define QCA8K_ACL_ACT_COUNTER_M 0x1f
++#define QCA8K_ACL_ACT_COUNTER_S 9
++
++#define QCA8K_REG_MAC_TBL0(x) (0x5a900 + ((x) << 4))
++#define QCA8K_REG_MAC_TBL1(x) (0x5a904 + ((x) << 4))
++#define QCA8K_REG_MAC_TBL2(x) (0x5a908 + ((x) << 4))
++
++#define QCA8K_REG_PUB_IP_TBL0 0x5aa00
++#define QCA8K_REG_PUB_IP_TBL1 0x5aa04
+
+ /* the maximum number of SA addresses a user port may learn */
+ #define QCA8K_SA_LEARN_LIMIT 512
+@@ -178,7 +282,7 @@ struct ar8xxx_port_status {
+ int enabled;
+ };
+
+-struct qca8k_offload *offload;
++struct qca8k_offload;
+
+ struct qca8k_priv {
+ struct regmap *regmap;
+@@ -225,4 +329,22 @@ qca8k_reg_clear(struct qca8k_priv *priv,
+ qca8k_rmw(priv, reg, val, 0);
+ }
+
++static inline void qca8k_mutex_lock(struct mutex *mutex)
++{
++ if (likely(!in_atomic()))
++ mutex_lock(mutex);
++}
++
++static inline void qca8k_mutex_lock_nested(struct mutex *mutex, u32 subclass)
++{
++ if (likely(!in_atomic()))
++ mutex_lock_nested(mutex, subclass);
++}
++
++static inline void qca8k_mutex_unlock(struct mutex *mutex)
++{
++ if (likely(!in_atomic()))
++ mutex_unlock(mutex);
++}
++
+ #endif /* __QCA8K_H */
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/Makefile
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/Makefile
+@@ -0,0 +1,20 @@
++qca8k_offload-objs := \
++ qca8k_public_ip.o \
++ qca8k_private_ip.o \
++ qca8k_iface.o \
++ qca8k_l3.o \
++ qca8k_arp.o \
++ qca8k_acl.o \
++ qca8k_nat.o \
++ qca8k_napt.o \
++ qca8k_arl.o \
++ qca8k_route.o \
++ qca8k_hook_iface.o \
++ qca8k_hook_ct.o \
++ qca8k_thread.o \
++ qca8k_fib.o \
++ qca8k_qos.o \
++ qca8k_normalize.o \
++ qca8k_debugfs.o \
++ qca8k_init.o
++obj-$(CONFIG_NET_DSA_QCA8K_OFFLOAD) += qca8k_offload.o
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/compat.h
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/compat.h
+@@ -0,0 +1,76 @@
++#include <linux/types.h>
++#include <linux/workqueue.h>
++#include <net/switchdev.h>
++
++#include "../ar8xxx.h"
++
++#define SSDK_HACK
++//#define HNAT_DEBUG 1
++#ifdef SSDK_HACK
++#define S17_WAN_PORT 5
++#define S17_LAN_PORT0 1
++#define S17_LAN_PORT1 2
++#define S17_LAN_PORT2 3
++#define S17_LAN_PORT3 4
++#else
++#define S17_WAN_PORT 5
++#define S17_LAN_PORT0 1
++#define S17_LAN_PORT1 2
++#define S17_LAN_PORT2 3
++#define S17_LAN_PORT3 4
++#endif
++
++#define KVER32
++#define ISISC
++#define KERNEL_MODULE
++#define HSL_STANDALONG
++#define KERNEL_MODE
++
++#define IN_ACL 1
++#define IN_FDB 1
++#define IN_IGMP 1
++#define IN_LEAKY 1
++#define IN_LED 1
++#define IN_MIB 1
++#define IN_MIRROR 1
++#define IN_MISC 1
++#define IN_PORTCONTROL 1
++#define IN_PORTVLAN 1
++#define IN_QOS 1
++#define IN_RATE 1
++#define IN_STP 1
++#define IN_VLAN 1
++#define IN_COSMAP 1
++#define IN_IP 1
++#define IN_TRUNK 1
++#define IN_SEC 1
++#define IN_INTERFACECONTROL 1
++
++#define ssdk_init_cfg void
++#define ssdk_cfg_t void
++#define hsl_init_mode int
++#define hsl_access_mode int
++
++#define qca8k_write ar8xxx_write
++#define qca8k_read ar8xxx_read
++#define qca8k_rmw ar8xxx_rmw
++#define qca8k_reg_set ar8xxx_reg_set
++#define qca8k_reg_clear ar8xxx_reg_clear
++
++
++extern int
++qca8k_ssdk_reg_get(u32 dev_id, u32 reg_addr, u8 value[],
++ u32 value_len);
++extern int
++qca8k_ssdk_reg_set(u32 dev_id, u32 reg_addr, u8 value[],
++ u32 value_len);
++extern int
++qca8k_ssdk_reg_field_get(u32 dev_id, u32 reg_addr,
++ u32 bit_offset, u32 field_len,
++ u8 value[], u32 value_len);
++extern int
++qca8k_ssdk_reg_field_set(u32 dev_id, u32 reg_addr,
++ u32 bit_offset, u32 field_len,
++ const u8 value[], u32 value_len);
++
++
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k.h
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k.h
+@@ -0,0 +1,312 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/types.h>
++#include <linux/workqueue.h>
++#include <net/switchdev.h>
++#include <linux/debugfs.h>
++#include <linux/seq_file.h>
++
++#include "../qca8k.h"
++
++#define LAN_MASK 0x2e
++
++#define S17_WAN_PORT 5
++
++#define QCA8K_CT_SCAN_TIMEOUT 5
++#define QCA8K_CT_AGING_TIMEOUT 20
++#define QCA8K_ARP_EXPIRE_TIMEOUT 40
++#define QCA8K_ROUTE_TIMEOUT 5
++#define QCA8K_CT_OFFLOAD_THRESHOLD 50
++#define QCA8K_CT_FAIL_MAX 5
++#define QCA8K_CT_IGNORE_MARK 1000
++
++#define QCA8K_ACL_IPV6_MAX 16
++#define QCA8K_ACL_IPV6_FIRST 3
++#define QCA8K_ACL_IPV6_MULTICAST QCA8K_ACL_IPV6_MAX
++#define QCA8K_ACL_IPV6_GATEWAY (QCA8K_ACL_IPV6_MAX + 1)
++#define QCA8K_ACL_IPV4_GATEWAY 2
++#define QCA8K_ACL_IPV4_PUBLIC 1
++#define QCA8K_ACL_IPV4_MULTICAST 0
++
++#define QCA8K_DEFAULT_WAN_INTERFACE "wan"
++#define QCA8K_DEFAULT_LAN_INTERFACE "br-lan"
++
++#define QCA8K_GLOBAL_FW_CTRL0_ARP_FWD_M GENMASK(27, 26)
++#define QCA8K_GLOBAL_FW_CTRL0_ARP_FWD_TO_CPU BIT(27)
++#define QCA8K_GLOBAL_FW_CTRL0_ARP_NOT_FOUND_M GENMASK(23, 22)
++#define QCA8K_GLOBAL_FW_CTRL0_ARP_NOT_FOUND_TO_CPU BIT(23)
++#define QCA8K_GLOBAL_FW_CTRL0_ARP_FWD_ACT_M GENMASK(27, 26)
++#define QCA8K_GLOBAL_FW_CTRL0_ARP_FWD_ACT_S 26
++#define QCA8K_GLOBAL_FW_CTRL0_ARP_NOT_FOUND_M GENMASK(23, 22)
++#define QCA8K_GLOBAL_FW_CTRL0_ARP_NOT_FOUND_S 22
++#define QCA8K_GLOBAL_FW_CTRL1_MC_FLOOD_S 8
++#define QCA8K_GLOBAL_FW_CTRL1_IGMP_S 24
++
++#define QCA8K_IFACE_MAX 8
++#define QCA8K_PUBLIC_IP_MAX 16
++#define QCA8K_ACL_MAX 96
++#define QCA8K_ARP_MAX 128
++#define QCA8K_NAPT_MAX 1024
++
++#define QCA8K_L3_EXPIRED 1
++
++#define qca8k_info(p, t, ...) pr_info("qca8k: " t, ##__VA_ARGS__)
++#define qca8k_error(p, t, ...) pr_err("qca8k: " t, ##__VA_ARGS__)
++#define qca8k_debug(p, t, ...) if (p->offload->debug) pr_err("qca8k: " t, ##__VA_ARGS__)
++
++
++#define QCA8K_ACL_DP_ACT_S 6
++#define QCA8K_ACL_DP_ACT_M 7
++
++enum qca8k_napt_table {
++ QCA8K_L3_NAPT = 0,
++ QCA8K_L3_NAT = 2,
++ QCA8K_L3_ARP = 3,
++};
++
++enum qca8k_action {
++ QCA8K_MIRROR = 0,
++ QCA8K_REDIRECT,
++ QCA8K_COPY,
++ QCA8K_FORWARD,
++ QCA8K_DROP,
++};
++
++enum qca8k_qos_dp_act {
++ QCA8K_QOS_FORWARD = 0,
++ QCA8K_QOS_COPY = 1,
++ QCA8K_QOS_REDIRECT = 3,
++ QCA8K_QOS_DROP = 7,
++};
++
++enum qca8k_napt_proto {
++ QCA8K_NAPT_TCP = 0,
++ QCA8K_NAPT_UDP = 1,
++ QCA8K_NAPT_GRE = 3,
++};
++
++enum qca8k_napt_cmd {
++ QCA8K_L3_FLUSH = 1,
++ QCA8K_L3_ADD = 2,
++ QCA8K_L3_DEL = 3,
++ QCA8K_L3_NEXT = 4,
++ QCA8K_L3_SEARCH = 5,
++};
++
++enum qca8k_acl_rule_type {
++ QCA8K_RULE_IPV4 = 2,
++ QCA8K_RULE_IPV6_DIP = 3,
++};
++
++enum qca8k_l3_select {
++ QCA8K_L3_AGE = 1,
++ QCA8K_L3_SIP = 2,
++ QCA8K_L3_PIP = 4,
++ QCA8K_L3_VID = 8,
++ QCA8K_L3_SP = 16,
++};
++
++enum qca8k_acl_priority {
++ QCA8K_PRIORITY_IPV6 = 0,
++ QCA8K_PRIORITY_IPV4,
++};
++
++struct qca8k_napt {
++ int idx;
++ u16 aging;
++ enum qca8k_napt_proto proto;
++ enum qca8k_action action;
++ __be32 src_ip;
++ __be16 src_port;
++ u8 trans_ip_idx;
++ __be16 trans_ip_port;
++ __be32 dst_ip;
++ __be16 dst_port;
++};
++
++struct qca8k_arp {
++ int idx;
++ int ipv6;
++ u16 aging;
++ enum qca8k_action action;
++ u8 cpu_addr;
++ u8 sport;
++ u16 vid_offset;
++ u8 mac_idx;
++ u8 mac[8];
++ __be32 ip[4];
++};
++
++struct qca8k_iface {
++ u16 vid_l;
++ u16 vid_h;
++ u8 mac[6];
++ int ipv4;
++ int ipv6;
++};
++
++struct qca8k_public_ip
++{
++ u32 ip;
++ u32 refcount;
++};
++
++struct qca8k_v6_lanip
++{
++ int valid;
++ int prefix_len;
++ struct in6_addr ip;
++};
++
++struct qca8k_offload
++{
++ struct qca8k_priv *priv;
++ int debug;
++
++ char lan_dev[IFNAMSIZ];
++ char wan_dev[IFNAMSIZ];
++
++ struct qca8k_public_ip public_ip[QCA8K_PUBLIC_IP_MAX];
++
++ u32 qca8k_priv_ip;
++ u32 qca8k_priv_netmask;
++
++ u32 ct_ignore_mark;
++
++ int acl_ipv4_prio;
++ int acl_ipv6_prio;
++
++ struct dentry *rootdir;
++ struct dentry *qosdir;
++
++ struct in6_addr lanip6;
++
++ u32 ipv4_gateway;
++ u32 ipv6_gateway[4];
++ int ipv6_gateway_arp;
++ int ipv4_gateway_arp;
++ struct qca8k_v6_lanip ipv6_lanip[QCA8K_ACL_IPV6_MAX];
++
++ struct nf_conn_qca8k *nf_conn[QCA8K_NAPT_MAX];
++ unsigned int ct_bucket;
++
++ struct qca8k_iface iface[QCA8K_IFACE_MAX];
++ int iface_cnt;
++
++ struct task_struct *thread;
++
++ struct workqueue_struct *wq_arp;
++
++ struct notifier_block fib_nb;
++
++ struct notifier_block netdev_notifier;
++ struct notifier_block netevent_notifier;
++ struct notifier_block inetaddr_notifier;
++ struct notifier_block inet6addr_notifier;
++};
++
++extern int qca8k_debugfs_init(struct qca8k_priv *priv);
++extern void qca8k_debugfs_exit(struct qca8k_priv *priv);
++extern int qca8k_debugfs_tokenize(const char *ubuf, int slen, char **table, int tlen);
++
++extern int qca8k_l3_access(struct qca8k_priv *priv,
++ enum qca8k_napt_table table,
++ enum qca8k_napt_cmd cmd,
++ enum qca8k_l3_select select, u32 idx);
++
++extern int qca8k_napt_iterate(struct qca8k_priv *priv, struct qca8k_napt *napt,
++ u32 idx, u16 age);
++extern int qca8k_napt_write(struct qca8k_priv *priv, int proto, __be32 src_ip,
++ u8 trans_ip_idx, __be16 trans_port, __be16 src_port,
++ __be16 dst_port, __be32 dst_ip);
++extern int qca8k_napt_get_idx(struct qca8k_priv *priv, struct qca8k_napt *napt,
++ u32 idx);
++extern int qca8k_napt_del(struct qca8k_priv *priv, struct qca8k_napt *napt);
++extern int qca8k_napt_search(struct qca8k_priv *priv, struct qca8k_napt *napt,
++ enum qca8k_l3_select select, __be32 ip);
++extern int qca8k_napt_flush(struct qca8k_priv *priv);
++extern void qca8k_napt_init(struct qca8k_priv *priv);
++
++extern int qca8k_arp_iterate(struct qca8k_priv *priv, struct qca8k_arp *arp,
++ u32 idx, u16 age);
++extern int qca8k_arp_write(struct qca8k_priv *priv, u8 sport, __be16 vid,
++ __be32 *ip, u8 *mac, int ipv6, int dynamic);
++extern int qca8k_arp_search(struct qca8k_priv *priv, struct qca8k_arp *arp,
++ __be32 *ip, int ipv6);
++extern int qca8k_arp_del(struct qca8k_priv *priv, u32 *ip, int ipv6);
++extern void qca8k_arp_init(struct qca8k_priv *priv);
++extern void qca8k_arp_exit(struct qca8k_priv *priv);
++extern void qca8k_arp_expire(struct qca8k_priv *priv);
++
++extern int qca8k_priv_ip_match(struct qca8k_priv *priv, u32 ip);
++extern int qca8k_priv_ip_set(struct qca8k_priv *priv, u32 ip);
++extern u32 qca8k_priv_ip_get(struct qca8k_priv *priv);
++extern int qca8k_priv_netmask_set(struct qca8k_priv *priv, u32 ipmask);
++extern u32 qca8k_priv_netmask_get(struct qca8k_priv *priv);
++extern void qca8k_priv_ip_init(struct qca8k_priv *priv);
++
++extern int qca8k_pub_ip_add(struct qca8k_priv *priv, __be32 ip);
++extern void qca8k_pub_ip_del(struct qca8k_priv *priv, u32 idx);
++extern void qca8k_pub_ip_init(struct qca8k_priv *priv);
++
++extern void qca8k_acl_init(struct qca8k_priv *priv);
++extern void qca8k_acl_write_route_v4(struct qca8k_priv *priv, __be32 ip,
++ __be32 netmask, u16 port_mask,
++ int arp_idx);
++extern void qca8k_acl_write_public_v4(struct qca8k_priv *priv, __be32 ip,
++ __be32 netmask, u16 port_mask);
++extern void qca8k_acl_write_route_v6(struct qca8k_priv *priv, int idx,
++ __be32 *ip, int prefix, u16 port_mask,
++ int arp_idx, int inverse);
++extern void qca8k_acl_flush_route_v4(struct qca8k_priv *priv);
++extern void qca8k_acl_flush_route_v6(struct qca8k_priv *priv, int idx);
++extern void qca8k_acl_set_priority(struct qca8k_priv *priv, int priority, int type);
++
++extern int qca8k_arl_search(struct qca8k_priv *priv, struct qca8k_arl *arl,
++ u8 *mac, u16 vid);
++extern void qca8k_arl_init(struct qca8k_priv *priv);
++
++extern int qca8k_iface_read(struct qca8k_priv *priv, int idx,
++ struct qca8k_iface *iface);
++extern int qca8k_iface_add(struct qca8k_priv *priv, u8 *mac, u16 vid);
++extern void qca8k_iface_init(struct qca8k_priv *priv);
++
++extern void qca8k_nat_init(struct qca8k_priv *priv);
++
++extern void qca8k_hook_iface_init(struct qca8k_priv *priv);
++extern void qca8k_hook_iface_exit(struct qca8k_priv *priv);
++
++extern void qca8k_hook_ct_init(struct qca8k_priv *priv);
++extern void qca8k_hook_ct_exit(struct qca8k_priv *priv);
++extern void qca8k_ct_flush(struct qca8k_priv *priv);
++extern void qca8k_ct_ager(struct qca8k_priv *priv);
++extern void qca8k_ct_scanner(struct qca8k_priv *priv);
++
++extern void qca8k_route_init(struct qca8k_priv *priv);
++extern void qca8k_route_ip6_addr_add(struct qca8k_priv *priv, struct in6_addr ip, int prefix_len);
++extern void qca8k_route_ip6_addr_del(struct qca8k_priv *priv, struct in6_addr ip, int prefix_len);
++extern void qca8k_route_ip6_worker(struct qca8k_priv *priv);
++
++extern void qca8k_fib_init(struct qca8k_priv *priv);
++extern void qca8k_fib_exit(struct qca8k_priv *priv);
++extern void qca8k_fib_apply_route(struct qca8k_priv *priv);
++
++extern int qca8k_thread_start(struct qca8k_priv *priv);
++extern void qca8k_thread_stop(struct qca8k_priv *priv);
++
++extern void qca8k_qos_init(struct qca8k_priv *priv);
++
++extern void qca8k_norm_init(struct qca8k_priv *priv);
++
++extern void qca8k_abort(struct qca8k_priv *priv, const char *func);
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_acl.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_acl.c
+@@ -0,0 +1,413 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++static void
++qca8k_acl_write(struct qca8k_priv *priv, int rule, u32 *vlu, u32 *msk, u32 *act)
++{
++ int i;
++
++ for (i = 0; i < 5; i++)
++ qca8k_write(priv, QCA8K_REG_ACL_VLU(rule) + (i << 2), vlu[i]);
++
++ for (i = 0; i < 5; i++)
++ qca8k_write(priv, QCA8K_REG_ACL_MSK(rule) + (i << 2), msk[i]);
++
++ for (i = 0; i < 3; i++)
++ qca8k_write(priv, QCA8K_REG_ACL_ACT(rule) + (i << 2), act[i]);
++}
++
++static void
++qca8k_acl_read(struct qca8k_priv *priv, int rule, u32 *vlu, u32 *msk, u32 *act)
++{
++ int i;
++
++ for (i = 0; i < 5; i++)
++ vlu[i] = qca8k_read(priv, QCA8K_REG_ACL_VLU(rule) + (i << 2));
++
++ for (i = 0; i < 5; i++)
++ msk[i] = qca8k_read(priv, QCA8K_REG_ACL_MSK(rule) + (i << 2));
++
++ for (i = 0; i < 3; i++)
++ act[i] = qca8k_read(priv, QCA8K_REG_ACL_ACT(rule) + (i << 2));
++}
++
++static void
++qca8k_acl_flush_rule(struct qca8k_priv *priv, int rule)
++{
++ u32 vlu[5] = { 0 };
++ u32 msk[5] = { 0 };
++ u32 act[3] = { 0 };
++
++ qca8k_acl_write(priv, rule, vlu, msk, act);
++}
++
++void
++qca8k_acl_flush_route_v4(struct qca8k_priv *priv)
++{
++ qca8k_acl_flush_rule(priv, QCA8K_ACL_IPV4_GATEWAY);
++}
++
++void
++qca8k_acl_flush_route_v6(struct qca8k_priv *priv, int rule)
++{
++ qca8k_acl_flush_rule(priv, QCA8K_ACL_IPV6_FIRST + rule);
++}
++
++static void
++qac8k_acl_setup_act(u32 *act, int arp_idx, int priority, int acl_counter)
++{
++ if (priority) {
++ act[1] |= (priority & 0x7) << QCA8K_ACL_ACT_PRIORITY_S;
++ act[1] |= QCA8K_ACL_ACT_PRIORITY_EN;
++ }
++ if (arp_idx >= 0) {
++ /* arp index - 55:49 */
++ act[1] |= arp_idx << QCA8K_ACL_ACT_ARP_IDX_S;
++ /* arp index enable - 48 */
++ act[1] |= QCA8K_ACL_ACT_ARP_IDX_EN;
++ }
++ if (acl_counter >= 0) {
++ act[2] |= QCA8K_ACL_ACT_COUNTER_EN;
++ act[2] |= (acl_counter & QCA8K_ACL_ACT_COUNTER_M) << QCA8K_ACL_ACT_COUNTER_S;
++ }
++}
++
++static void
++qac8k_acl_setup_vlan(u32 *act, int priority)
++{
++ if (priority) {
++ act[0] |= (priority & 0x7) << QCA8K_ACL_ACT_CTAG_PRIORITY_S;
++ act[0] |= (priority & 0x7) << QCA8K_ACL_ACT_STAG_PRIORITY_S;
++ act[1] |= BIT(9);
++ } else {
++ act[1] &= ~BIT(9);
++ }
++}
++
++static void
++qca8k_acl_set_rule_priority(struct qca8k_priv *priv, int priority, int rule)
++{
++ u32 vlu[5];
++ u32 msk[5];
++ u32 act[3];
++
++ qca8k_acl_read(priv, rule, vlu, msk, act);
++ act[1] &= ~(QCA8K_ACL_ACT_PRIORITY_M << QCA8K_ACL_ACT_PRIORITY_S);
++ if (priority) {
++ act[1] |= (priority & 0x7) << QCA8K_ACL_ACT_PRIORITY_S;
++ act[1] |= QCA8K_ACL_ACT_PRIORITY_EN;
++ }
++ qca8k_acl_write(priv, rule, vlu, msk, act);
++}
++
++void
++qca8k_acl_set_priority(struct qca8k_priv *priv, int priority, int type)
++{
++ int i;
++
++ switch(type) {
++ case QCA8K_PRIORITY_IPV6:
++ priv->offload->acl_ipv4_prio = priority;
++ for (i = 0; i < QCA8K_ACL_IPV6_MAX; i++)
++ qca8k_acl_set_rule_priority(priv, priority,
++ QCA8K_ACL_IPV6_FIRST + i);
++ break;
++ case QCA8K_PRIORITY_IPV4:
++ priv->offload->acl_ipv6_prio = priority;
++ qca8k_acl_set_rule_priority(priv, priority, 0);
++ break;
++ }
++}
++
++static void
++qca8k_acl_write_multicast_v4(struct qca8k_priv *priv)
++{
++ u32 vlu[5] = { 0 };
++ u32 msk[5] = { 0 };
++ u32 act[3] = { 0 };
++ u32 ip = 0xe0;
++ u32 netmask = 0xf0;
++
++ /* action */
++ qac8k_acl_setup_act(act, -1, priv->offload->acl_ipv4_prio, QCA8K_ACL_IPV4_MULTICAST);
++
++ /* pattern */
++
++ /* src port - 128:134 */
++ vlu[4] |= LAN_MASK;
++ /* dip - 31:0 */
++ vlu[0] = ntohl(ip);
++
++ /* mask */
++
++ /* rule type 134:135 */
++ msk[4] = BIT(7) | BIT(6);
++ /* rule type 128:130 */
++ msk[4] |= QCA8K_RULE_IPV4;
++ /* src mask - 113 */
++ msk[3] = BIT(17);
++ /* src mask - 112 */
++ msk[3] |= BIT(16);
++ /* ip_mask - 31:0 */
++ msk[0] = ntohl(netmask);
++
++ qca8k_acl_write(priv, QCA8K_ACL_IPV4_MULTICAST, vlu, msk, act);
++}
++
++void
++qca8k_acl_write_public_v4(struct qca8k_priv *priv, __be32 ip, __be32 netmask, u16 port_mask)
++{
++ u32 vlu[5] = { 0 };
++ u32 msk[5] = { 0 };
++ u32 act[3] = { 0 };
++
++ /* action */
++ qac8k_acl_setup_act(act, -1, priv->offload->acl_ipv4_prio, QCA8K_ACL_IPV4_PUBLIC);
++
++ /* pattern */
++
++ /* src port - 128:134 */
++ vlu[4] |= port_mask;
++ /* dip - 31:0 */
++ vlu[0] = ntohl(ip);
++
++ /* mask */
++
++ /* rule type 134:135 */
++ msk[4] = BIT(7) | BIT(6);
++ /* rule type 128:130 */
++ msk[4] |= QCA8K_RULE_IPV4;
++ /* src mask - 113 */
++ msk[3] = BIT(17);
++ /* src mask - 112 */
++ msk[3] |= BIT(16);
++ /* ip_mask - 31:0 */
++ msk[0] = ntohl(netmask);
++
++ qca8k_acl_write(priv, QCA8K_ACL_IPV4_PUBLIC, vlu, msk, act);
++}
++
++void
++qca8k_acl_write_route_v4(struct qca8k_priv *priv, __be32 ip, __be32 netmask,
++ u16 port_mask, int arp_idx)
++{
++ u32 vlu[5] = { 0 };
++ u32 msk[5] = { 0 };
++ u32 act[3] = { 0 };
++
++ /* action */
++ qac8k_acl_setup_act(act, arp_idx, priv->offload->acl_ipv4_prio, QCA8K_ACL_IPV4_GATEWAY);
++ qac8k_acl_setup_vlan(act, 2);
++
++ /* pattern */
++
++ /* rule inverse - 135 */
++ vlu[4] = BIT(7);
++ /* src port - 128:134 */
++ vlu[4] |= port_mask;
++ /* dip - 31:0 */
++ vlu[0] = ntohl(ip);
++
++ /* mask */
++
++ /* rule type 134:135 */
++ msk[4] = BIT(7) | BIT(6);
++ /* rule type 128:130 */
++ msk[4] |= QCA8K_RULE_IPV4;
++ /* src mask - 113 */
++ msk[3] = BIT(17);
++ /* src mask - 112 */
++ msk[3] |= BIT(16);
++ /* ip_mask - 31:0 */
++ msk[0] = ntohl(netmask);
++
++ qca8k_acl_write(priv, QCA8K_ACL_IPV4_GATEWAY, vlu, msk, act);
++}
++
++void
++qca8k_acl_write_route_v6(struct qca8k_priv *priv, int idx, __be32 *ip,
++ int prefix, u16 port_mask, int arp_idx, int inverse)
++{
++ u32 vlu[5] = { 0 };
++ u32 msk[5] = { 0 };
++ u32 act[3] = { 0 };
++ int p = 3;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++
++ /* action */
++
++ qac8k_acl_setup_act(act, arp_idx, priv->offload->acl_ipv6_prio,
++ QCA8K_ACL_IPV6_FIRST + idx);
++ if (inverse)
++ qac8k_acl_setup_vlan(act, 2);
++
++ /* mask */
++
++ /* rule type 134:135 */
++ msk[4] = BIT(7) | BIT(6);
++ /* rule type 128:130 */
++ msk[4] |= QCA8K_RULE_IPV6_DIP;
++ /* ip_mask - 31:0 */
++ while (prefix >= 32) {
++ msk[p] = 0xffffffff;
++ p--;
++ prefix -= 32;
++ }
++ if (prefix)
++ msk[p] = GENMASK(31, 31 - (prefix - 1));
++
++ /* pattern */
++
++ if (inverse)
++ /* rule inverse - 135 */
++ vlu[4] = BIT(7);
++ /* src port - 128:134 */
++ vlu[4] |= port_mask;
++ /* dip - 127:0 */
++ vlu[3] = ntohl(ip[0]) & msk[3];
++ vlu[2] = ntohl(ip[1]) & msk[2];
++ vlu[1] = ntohl(ip[2]) & msk[1];
++ vlu[0] = ntohl(ip[3]) & msk[0];
++
++ qca8k_acl_write(priv, QCA8K_ACL_IPV6_FIRST + idx, vlu, msk, act);
++
++ qca8k_mutex_unlock(&priv->reg_mutex);
++}
++
++static int
++qca8k_fop_acl_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ int i;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ seq_printf(seq, "id arp inv sport qos prio act counter proto ip/mask\n");
++ for (i = 0; i < QCA8K_ACL_MAX; i++) {
++ u32 vlu[5];
++ u32 msk[5];
++ u32 act[3];
++ int j;
++
++ msk[4] = qca8k_read(priv, QCA8K_REG_ACL_MSK(i) + (4 << 2));
++
++ if (!msk[4])
++ continue;
++
++ for (j = 0; j < 5; j++)
++ vlu[j] = qca8k_read(priv, QCA8K_REG_ACL_VLU(i) + (j << 2));
++
++ for (j = 0; j < 5; j++)
++ msk[j] = qca8k_read(priv, QCA8K_REG_ACL_MSK(i) + (j << 2));
++
++ for (j = 0; j < 3; j++)
++ act[j] = qca8k_read(priv, QCA8K_REG_ACL_ACT(i) + (j << 2));
++
++ /* id */
++ seq_printf(seq, "%2d ", i);
++
++ /* arp index - 55:49 */
++ if (act[1] & BIT(16))
++ seq_printf(seq, "%3d ", (act[1] >> 17) & 0x7f);
++ else
++ seq_printf(seq, " * ");
++
++ /* rule inverse - 135 */
++ seq_printf(seq, " %s ", (vlu[4] & BIT(7)) ? ("1") : ("0"));
++ /* src port - 128:134 */
++ seq_printf(seq, "0x%02x ", vlu[4] & 0x7e);
++
++ if (act[1] & BIT(28))
++ seq_printf(seq, "%d ", (act[1] >> 25) & 0x7);
++ else
++ seq_printf(seq, "0 ");
++
++ if (act[1] & BIT(9))
++ seq_printf(seq, "%d ", (act[0] >> 13) & 0x7);
++ else
++ seq_printf(seq, "0 ");
++
++
++ switch ((act[2] >> QCA8K_ACL_DP_ACT_S) & QCA8K_ACL_DP_ACT_M) {
++ case QCA8K_QOS_COPY:
++ seq_printf(seq, "copy ");
++ break;
++ case QCA8K_QOS_FORWARD:
++ seq_printf(seq, "fwd ");
++ break;
++ case QCA8K_QOS_REDIRECT:
++ seq_printf(seq, "redir ");
++ break;
++ case QCA8K_QOS_DROP:
++ seq_printf(seq, "drop ");
++ break;
++ default:
++ seq_printf(seq, "??? ");
++ break;
++ }
++
++ if (act[2] & BIT(14)) {
++ int id = (act[2] >> 9) & 0x1f;
++
++ seq_printf(seq, "%08x%08x ",
++ qca8k_read(priv, QCA8K_REG_ACL_COUNTER(id, 1)),
++ qca8k_read(priv, QCA8K_REG_ACL_COUNTER(id, 0)));
++ } else {
++ seq_printf(seq, "0 ");
++ }
++
++ switch (msk[4] & 0x7) {
++ case QCA8K_RULE_IPV6_DIP:
++ seq_printf(seq, "ipv6 %08x:%08x:%08x:%08x/", vlu[3], vlu[2], vlu[1], vlu[0]);
++ seq_printf(seq, "%08x:%08x:%08x:%08x", msk[3], msk[2], msk[1], msk[0]);
++ break;
++ case QCA8K_RULE_IPV4:
++ /* sip mask - 31:0 */
++ seq_printf(seq, "ipv4 %08x/", vlu[0]);
++ /* dip - 31:0 */
++ seq_printf(seq, "%08x", msk[0]);
++ break;
++ }
++ seq_printf(seq, "\n");
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static int
++qca8k_fop_acl_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_acl_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_acl_ops = {
++ .owner = THIS_MODULE,
++ .open = qca8k_fop_acl_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = seq_release,
++};
++
++void
++qca8k_acl_init(struct qca8k_priv *priv)
++{
++ u32 mcastv6[4] = { 0xff, 0, 0, 0 };
++
++ qca8k_acl_write_multicast_v4(priv);
++ qca8k_acl_write_route_v6(priv, QCA8K_ACL_IPV6_MULTICAST, mcastv6, 8, LAN_MASK, -1 , 0);
++
++ debugfs_create_file("acl", 0600, priv->offload->rootdir, priv, &qca8k_acl_ops);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_arl.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_arl.c
+@@ -0,0 +1,207 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++static int
++qca8k_arl_busy_wait(struct qca8k_priv *priv)
++{
++ int retry = 0x1000;
++ u32 reg;
++ int busy;
++
++ /* loop until the busy flag ahs cleared */
++ do {
++ reg = qca8k_read(priv, QCA8K_REG_ATU_FUNC);
++ busy = reg & QCA8K_ATU_FUNC_BUSY;
++ } while (retry-- && busy);
++
++ if (!retry) {
++ qca8k_info(priv, " ARL table access busy\n");
++ return -1;
++ }
++
++ return 0;
++}
++
++static void
++qca8k_arl_read(struct qca8k_priv *priv, struct qca8k_arl *arl)
++{
++ u32 reg[4];
++ int i;
++
++ /* load the ARL table into an array */
++ for (i = 0; i < 4; i++)
++ reg[i] = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4));
++
++ /* vid - 83:72 */
++ arl->vid = (reg[2] >> QCA8K_ATU_VID_S) & QCA8K_ATU_VID_M;
++ /* aging - 67:64 */
++ arl->aging = reg[2] & QCA8K_ATU_STATUS_M;
++ /* portmask - 54:48 */
++ arl->port_mask = (reg[1] >> QCA8K_ATU_PORT_S) & QCA8K_ATU_PORT_M;
++ /* mac - 47:0 */
++ arl->mac[0] = (reg[1] >> 8) & 0xff;
++ arl->mac[1] = reg[1] & 0xff;
++ arl->mac[2] = (reg[0] >> 24) & 0xff;
++ arl->mac[3] = (reg[0] >> 16) & 0xff;
++ arl->mac[4] = (reg[0] >> 8) & 0xff;
++ arl->mac[5] = reg[0] & 0xff;
++}
++
++static void
++qca8k_arl_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, u8 aging,
++ u8 *mac)
++{
++ u32 reg[3] = { 0 };
++ int i;
++
++ /* vid - 83:72 */
++ reg[2] = (vid & QCA8K_ATU_VID_M) << QCA8K_ATU_VID_S;
++ /* aging - 67:64 */
++ reg[2] |= aging & QCA8K_ATU_STATUS_M;
++ /* portmask - 54:48 */
++ reg[1] = (port_mask & QCA8K_ATU_PORT_M) << QCA8K_ATU_PORT_S;
++ /* mac - 47:0 */
++ reg[1] |= mac[0] << 8;
++ reg[1] |= mac[1];
++ reg[0] |= mac[2] << 24;
++ reg[0] |= mac[3] << 16;
++ reg[0] |= mac[4] << 8;
++ reg[0] |= mac[5];
++
++ /* load the array into the ARL table */
++ for (i = 0; i < 3; i++)
++ qca8k_write(priv, QCA8K_REG_ATU_DATA0 + (i * 4), reg[i]);
++}
++
++static int
++qca8k_arl_access(struct qca8k_priv *priv, struct qca8k_arl *arl,
++ enum qca8k_arl_cmd cmd, u16 idx)
++{
++ u32 reg;
++
++ reg = QCA8K_ATU_FUNC_BUSY;
++ reg |= (idx & QCA8K_ATU_FUNC_IDX_M) << QCA8K_ATU_FUNC_IDX_S;
++ reg |= cmd;
++
++ qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
++
++ if (qca8k_arl_busy_wait(priv))
++ return -1;
++
++ if (arl)
++ qca8k_arl_read(priv, arl);
++
++ reg = qca8k_read(priv, QCA8K_REG_ATU_FUNC);
++
++ return (reg >> QCA8K_ATU_FUNC_IDX_S) & QCA8K_ATU_FUNC_IDX_M;
++}
++
++int
++qca8k_arl_search(struct qca8k_priv *priv, struct qca8k_arl *arl, u8 *mac,
++ u16 vid)
++{
++ int idx;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++
++ qca8k_arl_write(priv, vid, 0, 0, mac);
++ idx = qca8k_arl_access(priv, arl, QCA8K_ARL_SEARCH, 0);
++
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ if (idx < 0 || !arl->aging)
++ return -1;
++
++ return idx;
++}
++static int
++qca8k_fop_arl_learn_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ int i;
++
++ seq_printf(seq, "port limit\n");
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ u32 val = qca8k_read(priv, QCA8K_REG_PORT_LEARN_LIMIT(i));
++
++ if (val & QCA8K_PORT_LEARN_LIMIT_EN)
++ val &= QCA8K_PORT_LEARN_LIMIT_CNT_M;
++ else
++ val = 0;
++ seq_printf(seq, "%d %d\n", i, val);
++ }
++
++ return 0;
++}
++
++static ssize_t
++qca8k_fop_arl_learn_write(struct file *file, const char __user * ubuf,
++ size_t len, loff_t *offp)
++{
++ struct seq_file *m = file->private_data;
++ struct qca8k_priv *priv = m->private;
++ unsigned long port, limit;
++ int ret = -1, cnt;
++ char *tok[2];
++
++ /* port limit */
++ cnt = qca8k_debugfs_tokenize(ubuf, len, tok, ARRAY_SIZE(tok));
++ if (cnt != 2)
++ goto out;
++
++ if (kstrtoul(tok[0], 10, &port))
++ goto out;
++
++ if (kstrtoul(tok[1], 10, &limit))
++ goto out;
++
++ if (port >= QCA8K_NUM_PORTS)
++ goto out;
++
++ limit &= QCA8K_PORT_LEARN_LIMIT_CNT_M;
++ if (limit)
++ limit |= QCA8K_PORT_LEARN_LIMIT_EN;
++ qca8k_rmw(priv, QCA8K_REG_PORT_LEARN_LIMIT(port),
++ QCA8K_PORT_LEARN_LIMIT_CNT_M | QCA8K_PORT_LEARN_LIMIT_EN,
++ limit);
++ ret = len;
++out:
++ if (tok[0])
++ kfree(tok[0]);
++
++ return ret;
++}
++
++static int
++qca8k_fop_arl_learn_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_arl_learn_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_arl_learn_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_arl_learn_open,
++ .write = qca8k_fop_arl_learn_write,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++void
++qca8k_arl_init(struct qca8k_priv *priv)
++{
++ qca8k_arl_access(priv, NULL, QCA8K_ARL_FLUSH, 0);
++ debugfs_create_file("arl_learn", 0660, priv->offload->rootdir, priv, &qca8k_arl_learn_ops);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_arp.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_arp.c
+@@ -0,0 +1,341 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++static void
++qca8k_arp_read(struct qca8k_priv *priv, struct qca8k_arp *arp)
++{
++ u32 reg[7];
++ u32 ctrl;
++ int i;
++
++ for (i = 0; i < 7; i++)
++ reg[i] = qca8k_read(priv, QCA8K_REG_L3_ENTRY0 + (i * 4));
++ ctrl = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++
++ /* index - 241:252 */
++ arp->idx = (ctrl >> 8) & 0x3ff;
++ /* ip_ver - 207 */
++ arp->ipv6 = (reg[6] >> 15) & 0x1;
++ /* aging - 206:204 */
++ arp->aging = (reg[6] >> 12) & 0x7;
++ /* action - 193:192 */
++ arp->action = reg[6] & 0x3;
++ /* cpu_ddr - 191 */
++ arp->cpu_addr = (reg[5] >> 31) & 0x1;
++ /* sport - 190:188 */
++ arp->sport = (reg[5] >> 28) & 0x7;
++ /* vid_offset - 187:179 */
++ arp->vid_offset = (reg[5] >> 19) & 0x1ff;
++ /* mac_idx - 178:176 */
++ arp->mac_idx = (reg[5] >> 16) & 0x7;
++ /* mac_addr - 175:128 */
++ arp->mac[0] = (reg[5] >> 8) & 0xff;
++ arp->mac[1] = reg[5] & 0xff;
++ arp->mac[2] = (reg[4] >> 24) & 0xff;
++ arp->mac[3] = (reg[4] >> 16) & 0xff;
++ arp->mac[4] = (reg[4] >> 8) & 0xff;
++ arp->mac[5] = reg[4] & 0xff;
++ /* ip - 127:0 */
++ if (arp->ipv6) {
++ arp->ip[0] = htonl(reg[3]);
++ arp->ip[1] = htonl(reg[2]);
++ arp->ip[2] = htonl(reg[1]);
++ arp->ip[3] = htonl(reg[0]);
++ } else {
++ arp->ip[0] = htonl(reg[0]);
++ }
++ if (arp->sport == 7)
++ arp->action = QCA8K_DROP;
++}
++
++int
++qca8k_arp_write(struct qca8k_priv *priv, u8 sport, __be16 vid, __be32 *ip, u8 *mac, int ipv6, int dynamic)
++{
++ u32 reg[7] = { 0 }, ctrl, status;
++ int mac_idx = 1;
++ int i;
++
++ /* ip_ver - 207 */
++ if (ipv6)
++ reg[6] |= BIT(15);
++ /* aging - 206:204 */
++ if (dynamic)
++ reg[6] |= 6 << 12;
++ else
++ reg[6] |= 7 << 12;
++ /* action - 193:192 */
++ reg[6] |= 3;
++ /* cpu_ddr - 191 */
++ reg[5] |= 0 << 31;
++ /* sport - 190:188 */
++ reg[5] |= sport << 28;
++ /* vid_offset - 187:179 */
++ reg[5] |= vid << 19;
++ /* mac_idx - 178:176 */
++ reg[5] |= mac_idx << 16;
++ /* mac_addr - 175:128 */
++ reg[5] |= mac[0] << 8;
++ reg[5] |= mac[1];
++ reg[4] |= mac[2] << 24;
++ reg[4] |= mac[3] << 16;
++ reg[4] |= mac[4] << 8;
++ reg[4] |= mac[5];
++ if (ipv6) {
++ reg[0] = ntohl(ip[3]);
++ reg[1] = ntohl(ip[2]);
++ reg[2] = ntohl(ip[1]);
++ reg[3] = ntohl(ip[0]);
++ } else {
++ reg[0] = ntohl(ip[0]);
++ }
++
++ for (i = 0; i < 7; i++)
++ qca8k_write(priv, QCA8K_REG_L3_ENTRY0 + (i * 4), reg[i]);
++
++ qca8k_l3_access(priv, QCA8K_L3_ARP, QCA8K_L3_ADD, 0, 0);
++
++ ctrl = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++ status = (ctrl & QCA8K_L3_ENTRY_STATUS);
++ if (!status && !dynamic)
++ qca8k_info(priv, "failed to add arp entry\n");
++
++ return !status;
++}
++
++static int
++qca8k_arp_next(struct qca8k_priv *priv, struct qca8k_arp *arp,
++ enum qca8k_napt_cmd cmd, enum qca8k_l3_select select, u32 idx)
++{
++ u32 reg;
++ u32 status;
++
++ if (qca8k_l3_access(priv, QCA8K_L3_ARP, cmd, select, idx))
++ return -1;
++
++ reg = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++ status = (reg & QCA8K_L3_ENTRY_STATUS);
++ if (status && arp)
++ qca8k_arp_read(priv, arp);
++
++ return !status;
++}
++
++int
++qca8k_arp_iterate(struct qca8k_priv *priv, struct qca8k_arp *arp, u32 idx,
++ u16 age)
++{
++ enum qca8k_l3_select select = 0;
++
++ if (idx == QCA8K_ARP_MAX)
++ idx--;
++ else if (idx == QCA8K_ARP_MAX - 1)
++ /* last one so stop */
++ return -1;
++
++ if (age) {
++ select = QCA8K_L3_AGE;
++ qca8k_write(priv, QCA8K_REG_L3_ENTRY6, age << 12);
++ }
++
++ return qca8k_arp_next(priv, arp, QCA8K_L3_NEXT, select, idx);
++}
++
++static int
++qca8k_arp_flush(struct qca8k_priv *priv)
++{
++ u32 reg;
++
++ if (qca8k_l3_access(priv, QCA8K_L3_ARP, QCA8K_L3_FLUSH, 0, 0))
++ return -1;
++
++ reg = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++
++ return !(reg & QCA8K_L3_ENTRY_STATUS);
++}
++
++static int
++qca8k_arp_cmd_ip(struct qca8k_priv *priv, struct qca8k_arp *arp, __be32 *ip,
++ int ipv6, enum qca8k_napt_cmd cmd)
++{
++ u32 reg[7] = { 0 };
++ int ret;
++ int i;
++
++ if (ipv6) {
++ reg[0] = ntohl(ip[3]);
++ reg[1] = ntohl(ip[2]);
++ reg[2] = ntohl(ip[1]);
++ reg[3] = ntohl(ip[0]);
++ reg[6] |= BIT(15);
++ } else {
++ reg[0] = ntohl(ip[0]);
++ }
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++
++ for (i = 0; i < 7; i++)
++ qca8k_write(priv, QCA8K_REG_L3_ENTRY0 + (i * 4), reg[i]);
++ ret = qca8k_arp_next(priv, arp, cmd, 0, 0);
++
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return ret;
++}
++
++int
++qca8k_arp_search(struct qca8k_priv *priv, struct qca8k_arp *arp, __be32 *ip,
++ int ipv6)
++{
++ return qca8k_arp_cmd_ip(priv, arp, ip, ipv6, QCA8K_L3_SEARCH);
++}
++
++int
++qca8k_arp_del(struct qca8k_priv *priv, __be32 *ip, int ipv6)
++{
++ return qca8k_arp_cmd_ip(priv, NULL, ip, ipv6, QCA8K_L3_DEL);
++}
++
++void
++qca8k_arp_expire(struct qca8k_priv *priv)
++{
++ struct qca8k_arp arp;
++ unsigned long flags;
++ u32 idx = QCA8K_ARP_MAX;
++
++ local_irq_save(flags);
++ while (!qca8k_arp_iterate(priv, &arp, idx, QCA8K_L3_EXPIRED)) {
++ struct qca8k_napt napt;
++
++ idx = arp.idx;
++ if (arp.ipv6 && (arp.idx != priv->offload->ipv6_gateway_arp)) {
++ qca8k_arp_del(priv, arp.ip, arp.ipv6);
++ } else if (!arp.ipv6 && (arp.idx != priv->offload->ipv4_gateway_arp) &&
++ qca8k_napt_search(priv, &napt, QCA8K_L3_SIP, arp.ip[0]) &&
++ qca8k_napt_search(priv, &napt, QCA8K_L3_PIP, arp.ip[0])) {
++ qca8k_debug(priv, "no napt entry found for arp idx:%d\n", arp.idx);
++ qca8k_arp_del(priv, arp.ip, arp.ipv6);
++ } else {
++ qca8k_arp_write(priv, arp.sport, arp.vid_offset, arp.ip,
++ arp.mac, arp.ipv6, 1);
++ }
++ }
++ local_irq_restore(flags);
++}
++
++static int
++qca8k_fop_arp_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ struct qca8k_arp arp;
++ int idx = QCA8K_ARP_MAX;
++
++ seq_printf(seq, "id\tmac\t\tiface\tvid\tport\tcpu\tage\taction\tip\n");
++ qca8k_mutex_lock(&priv->reg_mutex);
++ while (!qca8k_arp_iterate(priv, &arp, idx, 0)) {
++ idx = arp.idx;
++ seq_printf(seq, "%d\t%02x%02x%02x%02x%02x%02x\t",
++ arp.idx,
++ arp.mac[0], arp.mac[1],
++ arp.mac[2], arp.mac[3],
++ arp.mac[4], arp.mac[5]);
++ seq_printf(seq, "%d\t%d\t%d\t%d\t%d\t", arp.mac_idx, arp.vid_offset,
++ arp.sport, arp.cpu_addr, arp.aging);
++ switch (arp.action) {
++ case QCA8K_DROP:
++ seq_printf(seq, "drop\t");
++ break;
++ case QCA8K_REDIRECT:
++ seq_printf(seq, "redir\t");
++ break;
++ case QCA8K_COPY:
++ seq_printf(seq, "copy\t");
++ break;
++ case QCA8K_FORWARD:
++ seq_printf(seq, "fwd\t");
++ break;
++ case QCA8K_MIRROR:
++ seq_printf(seq, "mirror\t");
++ break;
++ }
++ if (arp.ipv6) {
++ seq_printf(seq, "%08x:%08x:%08x:%08x\n",
++ ntohl(arp.ip[0]), ntohl(arp.ip[1]),
++ ntohl(arp.ip[2]), ntohl(arp.ip[3]));
++ } else {
++ seq_printf(seq, "%08x\n", ntohl(arp.ip[0]));
++ }
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static int
++qca8k_fop_arp_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_arp_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_arp_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_arp_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++void
++qca8k_arp_init(struct qca8k_priv *priv)
++{
++ int i;
++
++ /* arp should be forwarded */
++ qca8k_rmw(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
++ QCA8K_GLOBAL_FW_CTRL0_ARP_FWD_M |
++ QCA8K_GLOBAL_FW_CTRL0_ARP_NOT_FOUND_M,
++ QCA8K_GLOBAL_FW_CTRL0_ARP_FWD_TO_CPU |
++ QCA8K_GLOBAL_FW_CTRL0_ARP_NOT_FOUND_TO_CPU);
++
++ /* disable arp leakage on the wan port */
++ qca8k_rmw(priv, QCA8K_REG_HROUTER_PCONTROL0,
++ QCA8K_HROUTER_PCONTROL0_M(S17_WAN_PORT),
++ QCA8K_HROUTER_PCONTROL0_GUARD(S17_WAN_PORT));
++
++ /* enable frame ack for arp frames */
++ for (i = 1; i < 6; i++) {
++ u32 addr = QCA8K_REG_FRAME_ACK_CTRL0 + (4 * (i / 4));
++
++ qca8k_reg_set(priv, addr, QCA8K_FRAME_ACK_CTRL0_ARP_ACK << (i % 4));
++ }
++
++ /* default ARP action is FWD */
++ qca8k_rmw(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
++ QCA8K_GLOBAL_FW_CTRL0_ARP_FWD_ACT_M,
++ 2 << QCA8K_GLOBAL_FW_CTRL0_ARP_FWD_ACT_S);
++
++ /* redirect ARP to cpu port if sport is unknown */
++ qca8k_rmw(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
++ QCA8K_GLOBAL_FW_CTRL0_ARP_NOT_FOUND_M,
++ 2 << QCA8K_GLOBAL_FW_CTRL0_ARP_NOT_FOUND_S);
++
++ /* arp aging should stop when it gets down to 1 */
++ qca8k_reg_set(priv, QCA8K_HROUTER_CONTROL,
++ QCA8K_HROUTER_CONTROL_ARP_AGE_MODE);
++
++ /* flush the arp table */
++ qca8k_arp_flush(priv);
++
++ debugfs_create_file("arp", 0600, priv->offload->rootdir, priv, &qca8k_arp_ops);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_debugfs.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_debugfs.c
+@@ -0,0 +1,119 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++int
++qca8k_debugfs_tokenize(const char *ubuf, int slen, char **table, int tlen)
++{
++ char *buf, *next;
++ int i, cnt = 0;
++
++ buf = memdup_user(ubuf, slen + 1);
++ if (IS_ERR(buf))
++ return PTR_ERR(buf);
++ buf[slen] = '\0';
++ next = buf;
++ for (i = 0; i < tlen && next; i++) {
++ char *val = strsep(&next, " ");
++
++ if (!val) {
++ table[cnt++] = next;
++ break;
++ }
++ table[cnt++] = val;
++ }
++
++ return cnt;
++}
++
++static int
++qca8k_fop_debug_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++
++ seq_printf(seq, "%d\n", priv->offload->debug);
++
++ return 0;
++}
++
++static ssize_t
++qca8k_fop_debug_write(struct file *file, const char __user * ubuf,
++ size_t len, loff_t *offp)
++{
++ struct seq_file *m = file->private_data;
++ struct qca8k_priv *priv = m->private;
++ unsigned long debug;
++ int ret = -1, cnt;
++ char *tok;
++
++ /* port enable wred ingress e0 e1 e2 e3 e4 e5 emax */
++ cnt = qca8k_debugfs_tokenize(ubuf, len, &tok, 1);
++ if (!cnt)
++ goto out;
++
++ if (kstrtoul(tok, 16, &debug))
++ goto out;
++
++ priv->offload->debug = !!debug;
++ ret = len;
++out:
++ if (tok)
++ kfree(tok);
++
++ return ret;
++}
++
++static int
++qca8k_fop_debug_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_debug_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_debug_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_debug_open,
++ .write = qca8k_fop_debug_write,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++int
++qca8k_debugfs_init(struct qca8k_priv *priv)
++{
++ priv->offload->rootdir = debugfs_create_dir("qca8k", NULL);
++ if (IS_ERR(priv->offload->rootdir)) {
++ int rc = PTR_ERR(priv->offload->rootdir);
++ priv->offload->rootdir = NULL;
++ return rc;
++ }
++
++ priv->offload->qosdir = debugfs_create_dir("qos", priv->offload->rootdir);
++ if (IS_ERR(priv->offload->qosdir)) {
++ int rc = PTR_ERR(priv->offload->qosdir);
++ priv->offload->qosdir = NULL;
++ return rc;
++ }
++
++ debugfs_create_file("debug", 0660, priv->offload->rootdir, priv, &qca8k_debug_ops);
++
++ return 0;
++}
++
++void
++qca8k_debugfs_exit(struct qca8k_priv *priv)
++{
++ if (priv->offload->rootdir)
++ debugfs_remove(priv->offload->rootdir);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_fib.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_fib.c
+@@ -0,0 +1,141 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/notifier.h>
++
++#include "qca8k.h"
++
++void qca8k_fib_apply_route(struct qca8k_priv *priv)
++{
++ struct qca8k_arp arp = { 0 };
++
++ if (qca8k_arp_search(priv, &arp, &priv->offload->ipv4_gateway, 0))
++ return;
++
++ priv->offload->ipv4_gateway_arp = arp.idx;
++
++ qca8k_acl_write_route_v4(priv, qca8k_priv_ip_get(priv),
++ qca8k_priv_netmask_get(priv), LAN_MASK, arp.idx);
++
++ qca8k_info(priv, "new default route - gw:%08X\n",
++ priv->offload->ipv4_gateway);
++}
++
++static int
++qca8k_fib4_add(struct qca8k_priv *priv,
++ struct fib_entry_notifier_info *fen_info)
++{
++ struct fib_info *fi = fen_info->fi;
++
++ switch (fen_info->type) {
++ case RTN_UNICAST:
++ /* ignore new routes if we already have one */
++ if (priv->offload->ipv4_gateway)
++ break;
++
++ /* filter out the default gateway */
++ if (!fi->fib_nh->nh_gw)
++ break;
++ if (fen_info->dst || fen_info->dst_len)
++ break;
++ if (!fi->fib_dev || strcmp(fi->fib_dev->name, priv->offload->wan_dev))
++ break;
++
++ priv->offload->ipv4_gateway = fi->fib_nh->nh_gw;
++ qca8k_fib_apply_route(priv);
++ break;
++ }
++
++ return 0;
++}
++
++static void
++qca8k_fib4_del(struct qca8k_priv *priv,
++ struct fib_entry_notifier_info *fen_info)
++{
++ struct fib_info *fi = fen_info->fi;
++
++ switch (fen_info->type) {
++ case RTN_UNICAST:
++ /* filter out the default gateway */
++ if (!fi->fib_nh->nh_gw)
++ break;
++ if (fen_info->dst || fen_info->dst_len)
++ break;
++ if (!fi->fib_dev || strcmp(fi->fib_dev->name, priv->offload->wan_dev))
++ break;
++ if (priv->offload->ipv4_gateway != fi->fib_nh->nh_gw)
++ break;
++
++ qca8k_info(priv, "del default route - dev:%s gw:%08X\n",
++ fi->fib_dev ? fi->fib_dev->name : "*",
++ fi->fib_nh->nh_gw);
++
++ priv->offload->ipv4_gateway = 0;
++ priv->offload->ipv4_gateway_arp = -1;
++ qca8k_acl_flush_route_v4(priv);
++ break;
++
++ default:
++ break;
++ }
++}
++
++static void
++qca8k_fib4_abort(struct qca8k_priv *priv)
++{
++ /* some thing went wrong */
++ qca8k_abort(priv, __func__);
++}
++
++static int
++qca8k_fib_event(struct notifier_block *nb,
++ unsigned long event, void *ptr)
++{
++ struct qca8k_offload *offload = container_of(nb, struct qca8k_offload, fib_nb);
++ struct qca8k_priv *priv = offload->priv;
++ int err;
++
++ switch (event) {
++ case FIB_EVENT_ENTRY_ADD:
++ err = qca8k_fib4_add(priv, ptr);
++ if (err)
++ qca8k_fib4_abort(priv);
++ else
++ break;
++ case FIB_EVENT_ENTRY_DEL:
++ qca8k_fib4_del(priv, ptr);
++ break;
++ case FIB_EVENT_RULE_ADD: /* fall through */
++ case FIB_EVENT_RULE_DEL:
++ /* policy based routing - we cant offload this so drop all
++ * offloaded routes
++ */
++ printk("qca8k: %s failed\n", __func__);
++ //qca8k_fib4_abort(priv);
++ break;
++ }
++ return NOTIFY_DONE;
++}
++
++void qca8k_fib_init(struct qca8k_priv *priv)
++{
++ priv->offload->ipv4_gateway_arp = -1;
++ priv->offload->fib_nb.notifier_call = qca8k_fib_event;
++ register_fib_notifier(&priv->offload->fib_nb);
++}
++
++void qca8k_fib_exit(struct qca8k_priv *priv)
++{
++ unregister_fib_notifier(&priv->offload->fib_nb);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_hook_ct.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_hook_ct.c
+@@ -0,0 +1,414 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <net/netfilter/nf_conntrack.h>
++#include <net/netfilter/nf_conntrack_core.h>
++#include <net/netfilter/nf_conntrack_l3proto.h>
++#include <net/netfilter/nf_conntrack_l4proto.h>
++#include <net/netfilter/nf_conntrack_acct.h>
++#include <net/netfilter/nf_conntrack_qca8k.h>
++#include "qca8k.h"
++
++static int
++qca8k_ct_add_napt(struct qca8k_priv *priv, struct nf_conn *ct,
++ struct nf_conn_qca8k *conn)
++{
++ struct nf_conntrack_tuple *original, *reply;
++ u8 trans_ip_idx = 0;
++ struct qca8k_arp arp = { 0 };
++
++ if (!(ct->status & IPS_NAT_MASK)) {
++ qca8k_debug(priv, "napt can only offload nat connections\n");
++ return -1;
++ }
++
++ if ((ct->status & IPS_NAT_MASK) == IPS_SRC_NAT) {
++ original = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
++ reply = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
++ } else {
++ reply = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
++ original = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
++ }
++ trans_ip_idx = qca8k_pub_ip_add(priv, reply->dst.u3.ip);
++
++ if (trans_ip_idx < 0)
++ return -1;
++
++ if (qca8k_arp_search(priv, &arp, &original->src.u3.ip, 0)) {
++ qca8k_debug(priv, "not adding ct %p, waiting for valid arp\n", ct);
++ return -1;
++ }
++
++ conn->idx = qca8k_napt_write(priv,
++ original->dst.protonum,
++ original->src.u3.ip,
++ trans_ip_idx,
++ reply->dst.u.all,
++ original->src.u.all,
++ original->dst.u.all,
++ original->dst.u3.ip);
++ conn->priv = priv;
++
++ if (conn->idx < 0) {
++ qca8k_pub_ip_del(priv, trans_ip_idx);
++ qca8k_debug(priv, "failed to add napt entry\n");
++ conn->fail = QCA8K_CT_FAIL_MAX;
++ return -1;
++ }
++
++ qca8k_debug(priv, "added napt entry idx:%d\n", conn->idx);
++
++ //if (timer_pending(&ct->timeout))
++ // del_timer(&ct->timeout);
++ ct->timeout = 0xffffffff;
++ priv->offload->nf_conn[conn->idx] = conn;
++
++ return 0;
++}
++
++static u64
++qca8k_ct_get_count(struct nf_conn *ct)
++{
++ struct nf_conn_acct *acct;
++
++ acct = nf_conn_acct_find(ct);
++ if (!acct)
++ return 0;
++
++ return (atomic64_read(&acct->counter[IP_CT_DIR_ORIGINAL].packets) +
++ atomic64_read(&acct->counter[IP_CT_DIR_REPLY].packets));
++}
++
++static int
++qca8k_ct_threshold(struct nf_conn *ct, struct nf_conn_qca8k *conn, u8 pkts_sum)
++{
++ u64 old = conn->counter;
++ u64 new = qca8k_ct_get_count(ct);
++
++ conn->counter = new;
++ if (new - old > QCA8K_CT_OFFLOAD_THRESHOLD)
++ return 1;
++
++ return 0;
++}
++
++static int
++qca8k_ct_del_napt(struct qca8k_priv *priv, struct nf_conn_qca8k *conn,
++ struct qca8k_napt *napt, int handover)
++{
++ u16 l3num;
++ u8 protonum;
++
++ if (qca8k_napt_del(priv, napt) < 0) {
++ qca8k_debug(priv, " failed to delete %d from napt table\n", conn->idx);
++ return -1;
++ }
++ qca8k_debug(priv, "deleted %d from napt table\n", conn->idx);
++ qca8k_pub_ip_del(priv, napt->trans_ip_idx);
++
++ spin_lock_bh(&conn->ct->lock);
++ l3num = conn->ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
++ protonum = conn->ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum;
++ if (handover) {
++ conn->ct->timeout = jiffies + 10 * HZ;
++ if ((l3num == AF_INET) && (protonum == IPPROTO_TCP))
++ if (conn->ct->proto.tcp.state == TCP_CONNTRACK_ESTABLISHED)
++ conn->ct->timeout = jiffies + 120 * HZ;
++ } else {
++ conn->ct->timeout = jiffies + HZ;
++ }
++ //add_timer(&conn->ct->timeout);
++
++ priv->offload->nf_conn[conn->idx] = NULL;
++ conn->idx = -1;
++ spin_unlock_bh(&conn->ct->lock);
++
++ return 0;
++}
++
++void
++qca8k_ct_flush(struct qca8k_priv *priv)
++{
++ struct qca8k_napt napt = { 0 };
++ u32 idx = QCA8K_NAPT_MAX;
++
++ /* iterate over all entries and delete them */
++ while (!qca8k_napt_iterate(priv, &napt, idx, 0)) {
++ idx = napt.idx;
++ if (priv->offload->nf_conn[idx])
++ qca8k_ct_del_napt(priv, priv->offload->nf_conn[idx], &napt, 0);
++ else
++ qca8k_napt_del(priv, &napt);
++ }
++
++ /* better safe than sorry */
++ qca8k_pub_ip_init(priv);
++ qca8k_napt_flush(priv);
++}
++
++void
++qca8k_ct_del_shutdown(struct qca8k_priv *priv, struct nf_conn_qca8k *conn)
++{
++ struct qca8k_napt napt = { 0 };
++
++ if (qca8k_napt_get_idx(priv, &napt, conn->idx) < 0)
++ return;
++
++ qca8k_ct_del_napt(priv, conn, &napt, 1);
++}
++
++void
++qca8k_ct_free(struct nf_conn *ct,
++ struct nf_conn_qca8k *conn)
++{
++ if (conn->idx < 0)
++ return;
++
++ qca8k_ct_del_shutdown(conn->priv, conn);
++}
++
++static void
++qca8k_ct_validate(struct qca8k_priv *priv, struct nf_conn_qca8k *conn)
++{
++ struct qca8k_napt napt = { 0 };
++
++ if (qca8k_napt_get_idx(priv, &napt, conn->idx) < 0)
++ return;
++
++ /* check if it is offloaded but aging */
++ if (napt.aging == 0xe)
++ conn->fail++;
++ if (conn->fail >= QCA8K_CT_FAIL_MAX) {
++ qca8k_debug(priv, " excess error, removing from table %p\n", conn->ct);
++ qca8k_ct_del_napt(priv, conn, &napt, 1);
++ }
++}
++
++static int
++qca8k_ct_add(struct qca8k_priv *priv, struct nf_conn *ct, struct nf_conn_qca8k *conn)
++{
++ if (ct->mark && (ct->mark == priv->offload->ct_ignore_mark)) {
++ conn->fail = QCA8K_CT_FAIL_MAX;
++ qca8k_debug(priv, "ignoring %p due to connmark\n", ct);
++ return 0;
++ }
++
++ if (conn->fail >= QCA8K_CT_FAIL_MAX) {
++ qca8k_debug(priv, "permanent fail %p\n", ct);
++ return -1;
++ }
++
++ if (qca8k_ct_threshold(ct, conn, 1)) {
++ if (conn->idx >= 0)
++ qca8k_ct_validate(priv, conn);
++ else
++ qca8k_ct_add_napt(priv, ct, conn);
++ }
++
++ return 0;
++}
++
++static struct hlist_nulls_node*
++ct_get_first(struct qca8k_priv *priv)
++{
++ struct hlist_nulls_node *n;
++
++ for (priv->offload->ct_bucket = 0;
++ priv->offload->ct_bucket < nf_conntrack_htable_size;
++ //priv->offload->ct_bucket < net->ct.htable_size;
++ priv->offload->ct_bucket++) {
++ n = rcu_dereference(hlist_nulls_first_rcu(&nf_conntrack_hash[priv->offload->ct_bucket]));
++ if (!is_a_nulls(n))
++ return n;
++ }
++ return NULL;
++}
++
++static struct hlist_nulls_node*
++ct_get_next(struct qca8k_priv *priv, struct hlist_nulls_node *head)
++{
++ head = rcu_dereference(hlist_nulls_next_rcu(head));
++ while (is_a_nulls(head)) {
++ if (likely(get_nulls_value(head) == priv->offload->ct_bucket)) {
++ if (++priv->offload->ct_bucket >= nf_conntrack_htable_size)
++ return NULL;
++ }
++ head = rcu_dereference(
++ hlist_nulls_first_rcu(
++ &nf_conntrack_hash[priv->offload->ct_bucket]));
++ }
++ return head;
++}
++
++void
++qca8k_ct_scanner(struct qca8k_priv *priv)
++{
++ struct hlist_nulls_node *node;
++
++ rcu_read_lock();
++ node = ct_get_first(priv);
++
++ while (node) {
++ struct nf_conntrack_tuple_hash *hash = (struct nf_conntrack_tuple_hash *) node;
++ const struct nf_conntrack_l3proto *l3proto;
++ const struct nf_conntrack_l4proto *l4proto;
++ struct nf_conn_qca8k *conn;
++ uint32_t private_ip;
++ struct nf_conn *ct;
++ uint8_t proto;
++
++ node = ct_get_next(priv, node);
++ ct = nf_ct_tuplehash_to_ctrack(hash);
++ conn = nf_ct_qca8k_find(ct);
++
++ if (conn && (conn->fail >= QCA8K_CT_FAIL_MAX))
++ continue;
++
++ /* is this a nat'ed connection ? */
++ if (!(ct->status & IPS_NAT_MASK))
++ continue;
++
++ /* only honour IP connections */
++ if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num != AF_INET)
++ continue;
++
++ /* we only handle TCP and UDP */
++ proto = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum;
++ if ((proto != IPPROTO_TCP) && (proto != IPPROTO_UDP))
++ continue;
++
++ /* if TCP then check if it is established */
++ if ((proto == IPPROTO_TCP) && (ct->proto.tcp.state != TCP_CONNTRACK_ESTABLISHED)) {
++ /* if the connection is offloaded, then expire it immediatley */
++ if (conn->idx >= 0)
++ qca8k_ct_del_shutdown(priv, conn);
++ continue;
++ }
++
++ /* get the private ip */
++ if ((ct->status & IPS_NAT_MASK) == IPS_SRC_NAT) {
++ if (!NF_CT_DIRECTION(hash))
++ continue;
++
++ private_ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip;
++ } else {
++ if (NF_CT_DIRECTION(hash))
++ continue;
++
++ private_ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip;
++ }
++ private_ip = ntohl(private_ip);
++
++ /* check if the private ip matches our lan interface */
++ if (!qca8k_priv_ip_match(priv, private_ip))
++ continue;
++
++ l3proto = __nf_ct_l3proto_find(nf_ct_l3num(ct));
++ NF_CT_ASSERT(l3proto);
++ l4proto = __nf_ct_l4proto_find(nf_ct_l3num(ct), nf_ct_protonum(ct));
++ NF_CT_ASSERT(l4proto);
++
++#if 0
++ qca8k_debug(priv, "found CT entry %-8s %u %-8s %u %ld \n",
++ l3proto->name, nf_ct_l3num(ct),
++ l4proto->name, nf_ct_protonum(ct),
++ timer_pending(&ct->timeout) ? (long)(ct->timeout - jiffies) / HZ : 0);
++#endif
++ qca8k_ct_add(priv, ct, conn);
++ }
++
++ rcu_read_unlock();
++}
++
++void
++qca8k_ct_ager(struct qca8k_priv *priv)
++{
++ struct qca8k_napt napt = { 0 };
++ struct nf_conn_qca8k *conn;
++ u32 idx = QCA8K_NAPT_MAX;
++
++ while (!qca8k_napt_iterate(priv, &napt, idx, QCA8K_L3_EXPIRED)) {
++ idx = napt.idx;
++ conn = priv->offload->nf_conn[napt.idx];
++ if (!conn)
++ continue;
++ qca8k_ct_del_napt(priv, conn, &napt, 0);
++ }
++}
++
++static int
++qca8k_fop_ct_mark_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++
++ seq_printf(seq, "%08x\n", priv->offload->ct_ignore_mark);
++
++ return 0;
++}
++
++static ssize_t
++qca8k_fop_ct_mark_write(struct file *file, const char __user * ubuf,
++ size_t len, loff_t *offp)
++{
++ struct seq_file *m = file->private_data;
++ struct qca8k_priv *priv = m->private;
++ unsigned long mark;
++ int ret = -1, cnt;
++ char *tok;
++
++ /* port enable wred ingress e0 e1 e2 e3 e4 e5 emax */
++ cnt = qca8k_debugfs_tokenize(ubuf, len, &tok, 1);
++ if (!cnt)
++ goto out;
++
++ if (kstrtoul(tok, 16, &mark))
++ goto out;
++
++ priv->offload->ct_ignore_mark = mark;
++ ret = len;
++out:
++ if (tok)
++ kfree(tok);
++
++ return ret;
++}
++
++static int
++qca8k_fop_ct_mark_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_ct_mark_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_ct_mark_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_ct_mark_open,
++ .write = qca8k_fop_ct_mark_write,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++void
++qca8k_hook_ct_init(struct qca8k_priv *priv)
++{
++ nf_ct_qca8k_destroy = qca8k_ct_free;
++ priv->offload->ct_ignore_mark = QCA8K_CT_IGNORE_MARK;
++ debugfs_create_file("ct_ignore_mark", 0660, priv->offload->rootdir, priv, &qca8k_ct_mark_ops);
++}
++
++void
++qca8k_hook_ct_exit(struct qca8k_priv *priv)
++{
++ nf_ct_qca8k_destroy = NULL;
++ qca8k_ct_flush(priv);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_hook_iface.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_hook_iface.c
+@@ -0,0 +1,260 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/inetdevice.h>
++#include <linux/workqueue.h>
++
++#include <net/netevent.h>
++#include <net/addrconf.h>
++#include <net/arp.h>
++
++#include "qca8k.h"
++
++struct qca8k_work_arp {
++ struct work_struct work;
++
++ u8 smac[6];
++ __be32 sip[4];
++ u16 vid;
++
++ int ipv6;
++ struct qca8k_priv *priv;
++};
++
++static int
++qca8k_netdev_event(struct notifier_block *nb, unsigned long event, void *ptr)
++{
++ struct qca8k_offload *offload = container_of(nb, struct qca8k_offload, netdev_notifier);
++ struct qca8k_priv *priv = offload->priv;
++ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
++
++ /* only track neighbours on wired interfaces */
++ if (strcmp(dev->name, offload->wan_dev) &&
++ strcmp(dev->name, offload->lan_dev))
++ return NOTIFY_DONE;
++
++ switch (event) {
++ case NETDEV_UP:
++ /* add the ip to the interface table */
++ if (qca8k_iface_add(priv, dev->dev_addr, 0))
++ qca8k_info(priv, " failed to setup iface entry for %s\n",
++ dev->name);
++ break;
++
++ case NETDEV_DOWN:
++ /* we need to flush the CT offloading whenever one of the interfaces
++ goes down */
++ qca8k_debug(priv, "netdev %s went down, flush tables\n",
++ dev->name);
++ qca8k_ct_flush(priv);
++ break;
++ }
++
++ return NOTIFY_DONE;
++}
++
++static void
++qca8k_worker_arp(struct work_struct *work)
++{
++ struct qca8k_work_arp *a = container_of(work,
++ struct qca8k_work_arp, work);
++ struct qca8k_arl arl = { 0 };
++ int idx;
++ unsigned long port;
++ int sport;
++
++ /* look up the MAC in the switches fdb */
++ idx = qca8k_arl_search(a->priv, &arl, a->smac, a->vid);
++
++ /* work out the source port */
++ port = arl.port_mask;
++ sport = find_first_bit(&port, 7);
++
++ /* write the mac to the ARP table */
++ if (idx >= 0)
++ qca8k_arp_write(a->priv, sport, a->vid, a->sip, a->smac, a->ipv6, 1);
++ else
++ qca8k_debug(a->priv, "qca8k: failed to look up sport for mac. can't add arp entry (%02x:%02x:%02x:%02x:%02x:%02x)\n",
++ a->smac[0], a->smac[1], a->smac[2], a->smac[3], a->smac[4], a->smac[5]);
++ kfree(a);
++}
++
++static int
++qca8k_netevent_event(struct notifier_block *nb, unsigned long event, void *ptr)
++{
++ struct qca8k_offload *offload = container_of(nb, struct qca8k_offload, netevent_notifier);
++ struct qca8k_priv *priv = offload->priv;
++ struct neighbour *n = ptr;
++ struct net_device *dev = n->dev;
++ struct qca8k_work_arp *a;
++ int ipv6 = 0;
++
++
++ /* we rely on the arp aging code to boot the entry */
++ if (!n->nud_state || (n->nud_state & NUD_FAILED))
++ goto out;
++
++ /* only track neighbours on wired interfaces */
++ if (strcmp(dev->name, offload->wan_dev) &&
++ strcmp(dev->name, offload->lan_dev))
++ goto out;
++
++ switch (event) {
++ case NETEVENT_NEIGH_UPDATE:
++ /* wor out if this is an ipv4 or ipv6 neighbour */
++ if (n->tbl == &arp_tbl)
++ ipv6 = 0;
++ else if (n->tbl == &nd_tbl)
++ ipv6 = 1;
++ else
++ goto out;
++ break;
++
++ default:
++ goto out;
++ }
++
++ /* allocate the worker data */
++ a = kzalloc(sizeof(*a), GFP_KERNEL);
++ if (!a)
++ goto out;
++ INIT_WORK(&a->work, qca8k_worker_arp);
++
++ /* setup the private data and schedule the worker */
++ a->vid = 1;
++ a->ipv6 = ipv6;
++ a->priv = priv;
++ memcpy(a->smac, n->ha, ETH_ALEN);
++ memcpy(a->sip, n->primary_key, (ipv6) ? (16) : (4));
++ queue_work(priv->offload->wq_arp, &a->work);
++
++out:
++ return NOTIFY_DONE;
++}
++
++static int
++qca8k_inetaddr_event_lan(struct notifier_block *nb,
++ unsigned long event, void *ptr)
++{
++ struct qca8k_offload *offload = container_of(nb, struct qca8k_offload, inetaddr_notifier);
++ struct qca8k_priv *priv = offload->priv;
++ struct in_ifaddr *ifa = ptr;
++ struct net_device *dev = ifa->ifa_dev->dev;
++ struct in_device *in_dev = dev->ip_ptr;
++ int flush = 0;
++
++ /* we are only interested in the ips of the lan interface */
++ if (strcmp(dev->name, offload->lan_dev))
++ return NOTIFY_DONE;
++
++ switch (event) {
++ case NETDEV_UP:
++ /* set ip and mask */
++ flush = qca8k_priv_netmask_set(priv, (u32) in_dev->ifa_list->ifa_mask);
++ flush |= qca8k_priv_ip_set(priv, (u32) in_dev->ifa_list->ifa_address);
++
++ /* if they changed we need to flush all conntrack entries that we are tracking */
++ if (flush) {
++ qca8k_debug(priv, "private ip changed, a table flush is required\n");
++ qca8k_ct_flush(priv);
++ }
++ break;
++ }
++
++ return NOTIFY_DONE;
++}
++
++static int
++qca8k_inetaddr_event_wan(struct notifier_block *nb,
++ unsigned long event, void *ptr)
++{
++ struct qca8k_offload *offload = container_of(nb, struct qca8k_offload, inetaddr_notifier);
++ struct qca8k_priv *priv = offload->priv;
++ struct in_ifaddr *ifa = ptr;
++ struct net_device *dev = ifa->ifa_dev->dev;
++ struct in_device *in_dev = dev->ip_ptr;
++
++ /* we are only interested in the ips of the lan interface */
++ if (strcmp(dev->name, offload->wan_dev))
++ return NOTIFY_DONE;
++
++ switch (event) {
++ case NETDEV_UP:
++ qca8k_acl_write_public_v4(priv, in_dev->ifa_list->ifa_address, in_dev->ifa_list->ifa_mask, LAN_MASK);
++ qca8k_debug(priv, "public ip changed, a table flush is required\n");
++ qca8k_ct_flush(priv);
++ break;
++ }
++
++ return NOTIFY_DONE;
++}
++
++static int
++qca8k_inetaddr_event(struct notifier_block *nb,
++ unsigned long event, void *ptr)
++{
++
++ qca8k_inetaddr_event_wan(nb, event, ptr);
++ return qca8k_inetaddr_event_lan(nb, event, ptr);
++}
++
++static int
++qca8k_inet6addr_event(struct notifier_block *nb,
++ unsigned long event, void *ptr)
++{
++ struct qca8k_offload *offload = container_of(nb, struct qca8k_offload, inet6addr_notifier);
++ struct qca8k_priv *priv = offload->priv;
++ struct inet6_ifaddr *ifa = (struct inet6_ifaddr *)ptr;
++ struct net_device *dev = ifa->idev->dev;
++
++ if (strcmp(dev->name, offload->lan_dev))
++ return NOTIFY_DONE;
++
++ switch (event) {
++ case NETDEV_UP:
++ qca8k_route_ip6_addr_add(priv, ifa->addr, ifa->prefix_len);
++ break;
++
++ case NETDEV_DOWN:
++ qca8k_route_ip6_addr_del(priv, ifa->addr, ifa->prefix_len);
++ break;
++ }
++
++ return NOTIFY_DONE;
++}
++
++void
++qca8k_hook_iface_init(struct qca8k_priv *priv)
++{
++ priv->offload->wq_arp = create_workqueue("qca8karp");
++
++ priv->offload->netdev_notifier.notifier_call = qca8k_netdev_event;
++ priv->offload->netevent_notifier.notifier_call = qca8k_netevent_event;
++ priv->offload->inetaddr_notifier.notifier_call = qca8k_inetaddr_event;
++ priv->offload->inet6addr_notifier.notifier_call = qca8k_inet6addr_event;
++
++ register_netdevice_notifier(&priv->offload->netdev_notifier);
++ register_netevent_notifier(&priv->offload->netevent_notifier);
++ register_inetaddr_notifier(&priv->offload->inetaddr_notifier);
++ register_inet6addr_notifier(&priv->offload->inet6addr_notifier);
++}
++
++void
++qca8k_hook_iface_exit(struct qca8k_priv *priv)
++{
++ unregister_netdevice_notifier(&priv->offload->netdev_notifier);
++ unregister_netevent_notifier(&priv->offload->netevent_notifier);
++ unregister_inetaddr_notifier(&priv->offload->inetaddr_notifier);
++ unregister_inet6addr_notifier(&priv->offload->inet6addr_notifier);
++ destroy_workqueue(priv->offload->wq_arp);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_iface.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_iface.c
+@@ -0,0 +1,212 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++int
++qca8k_iface_read(struct qca8k_priv *priv, int idx, struct qca8k_iface *iface)
++{
++ u32 reg[3];
++
++ /* start by reading the 3rd register */
++ reg[2] = qca8k_read(priv, QCA8K_REG_MAC_TBL2(idx));
++
++ /* check if this entry is currently in use */
++ iface->ipv4 = (reg[2] >> 8) & 0x1;
++ iface->ipv6 = (reg[2] >> 9) & 0x1;
++ if (!iface->ipv4 && !iface->ipv6)
++ return -1;
++
++ /* read the other 2 registers */
++ reg[0] = qca8k_read(priv, QCA8K_REG_MAC_TBL0(idx));
++ reg[1] = qca8k_read(priv, QCA8K_REG_MAC_TBL1(idx));
++
++ /* store the entry inside the iface struct */
++ iface->mac[0] = (reg[1] >> 8) & 0xff;
++ iface->mac[1] = reg[1] & 0xff;
++ iface->mac[2] = (reg[0] >> 24) & 0xff;
++ iface->mac[3] = (reg[0] >> 16) & 0xff;
++ iface->mac[4] = (reg[0] >> 8) & 0xff;
++ iface->mac[5] = reg[0] & 0xff;
++ iface->vid_l = (reg[1] >> 16) & 0xfff;
++ iface->vid_h = (reg[2] & 0xff) << 4;
++ iface->vid_h |= reg[1] >> 28;
++
++ return 0;
++}
++
++static int
++qca8k_iface_write(struct qca8k_priv *priv, struct qca8k_iface *iface)
++{
++ u32 reg[3], idx = priv->offload->iface_cnt;
++ int i = 0;
++
++ /* check if the interface is already in the table */
++ for (i = 0; i < priv->offload->iface_cnt; i++) {
++ struct qca8k_iface *iter = &priv->offload->iface[i];
++
++ /* if this identical entry exists, then return without error */
++ if (!memcmp(iter, iface, sizeof(struct qca8k_iface)))
++ return 0;
++
++ /* if this entry has a different mac then skip it */
++ if (memcmp(iter->mac, iface->mac, 6))
++ continue;
++
++ /* same mac, make sure the vlans do not overlap */
++ if (
++ ((iface->vid_l >= iter->vid_l) && (iface->vid_l <= iter->vid_h)) ||
++ ((iface->vid_h >= iter->vid_l) && (iface->vid_h <= iter->vid_h))
++ ) {
++ qca8k_info(priv, " overlapping interface vlans are not supported\n");
++ return -1;
++ }
++ }
++
++ /* convert from struct to register table */
++ reg[0] = iface->mac[5];
++ reg[0] |= iface->mac[4] << 8;
++ reg[0] |= iface->mac[3] << 16;
++ reg[0] |= iface->mac[2] << 24;
++
++ reg[1] = iface->mac[1];
++ reg[1] |= iface->mac[0] << 8;
++ reg[1] |= (iface->vid_l & 0xfff) << 16;
++ reg[1] |= (iface->vid_h & 0xf) << 28;
++
++ reg[2] = (iface->vid_h & 0xfff) >> 4;
++ if (iface->ipv4)
++ reg[2] |= BIT(8);
++ if (iface->ipv6)
++ reg[2] |= BIT(9);
++
++ /* write the array to the two tables holding the iface information */
++ qca8k_mutex_lock(&priv->reg_mutex);
++
++ qca8k_write(priv, QCA8K_REG_MAC_EDIT0(idx), reg[0]);
++ qca8k_write(priv, QCA8K_REG_MAC_EDIT1(idx), reg[1] & 0xffff);
++ qca8k_write(priv, QCA8K_REG_MAC_TBL0(idx), reg[0]);
++ qca8k_write(priv, QCA8K_REG_MAC_TBL1(idx), reg[1]);
++ qca8k_write(priv, QCA8K_REG_MAC_TBL2(idx), reg[2]);
++
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ /* update the iface counter */
++ priv->offload->iface_cnt = priv->offload->iface_cnt + 1;
++
++ qca8k_debug(priv, "adding %02x:%02x:%02x:%02x:%02x:%02x to iface table\n",
++ iface->mac[0], iface->mac[1], iface->mac[2],
++ iface->mac[3], iface->mac[4], iface->mac[5]);
++
++ return 0;
++}
++
++int
++qca8k_iface_add(struct qca8k_priv *priv, u8 *mac, u16 vid)
++{
++ struct qca8k_iface *iface;
++
++ /* 511 is the largest vid supported */
++ if (vid >= 512)
++ return -1;
++
++ /* we only support 8 iface entries */
++ if (priv->offload->iface_cnt == QCA8K_IFACE_MAX) {
++ qca8k_info(priv, "iface table is full\n");
++ return -1;
++ }
++
++ /* copy the entry to the shadow table */
++ iface = &priv->offload->iface[priv->offload->iface_cnt];
++ memcpy(iface->mac, mac, 6);
++ iface->ipv4 = 1;
++ iface->ipv6 = 1;
++ if (!vid) {
++ iface->vid_l = 0;
++ iface->vid_h = 511;
++ } else {
++ iface->vid_l = vid;
++ iface->vid_h = vid;
++ }
++
++ /* tell the HW about the iface entry */
++ return qca8k_iface_write(priv, iface);
++}
++
++static int
++qca8k_fop_iface_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ u32 addr, val1, val2;
++ int i;
++
++ /* dump the table at 0x5a900 */
++ seq_printf(seq, "id\tmac\t\t\tvid low\tvid high\tipv4/6\n");
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ for (i = 0; i < QCA8K_IFACE_MAX; i++) {
++ struct qca8k_iface iface;
++
++ if (qca8k_iface_read(priv, i, &iface))
++ continue;
++
++ seq_printf(seq, "%d\t%02x:%02x:%02x:%02x:%02x:%02x\t%d\t%d\t\t%d %d\n",
++ i, iface.mac[0], iface.mac[1], iface.mac[2],
++ iface.mac[3], iface.mac[4], iface.mac[5],
++ iface.vid_l, iface.vid_h, iface.ipv4, iface.ipv6);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ /* dump the table at 0x5a900 */
++ qca8k_mutex_lock(&priv->reg_mutex);
++ seq_printf(seq, "id\tmac\n");
++ for (i = 0; i < QCA8K_IFACE_MAX; i++) {
++ addr = QCA8K_REG_MAC_EDIT0(i);
++ val1 = qca8k_read(priv, addr);
++ val2 = qca8k_read(priv, addr + 4);
++ seq_printf(seq, "%d\t%04x%08x\n",
++ i, val2 & 0xffff, val1);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static int
++qca8k_fop_iface_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_iface_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_iface_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_iface_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++void
++qca8k_iface_init(struct qca8k_priv *priv)
++{
++ int i;
++
++ /* flush the iface entry tables inside the HW */
++ for (i = 0; i < QCA8K_IFACE_MAX; i++) {
++ qca8k_write(priv, QCA8K_REG_MAC_TBL2(i), 0);
++ qca8k_write(priv, QCA8K_REG_MAC_EDIT0(i), 0);
++ qca8k_write(priv, QCA8K_REG_MAC_EDIT1(i), 0);
++ }
++
++ debugfs_create_file("iface", 0600, priv->offload->rootdir, priv, &qca8k_iface_ops);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_init.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_init.c
+@@ -0,0 +1,113 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/debugfs.h>
++
++#include "qca8k.h"
++
++static int __init
++qca8k_offload_init(void)
++{
++ struct qca8k_priv *priv = qca8k_priv;
++
++ priv->offload = kzalloc(sizeof(struct qca8k_offload), GFP_KERNEL);
++ if (!priv->offload) {
++ qca8k_error(priv, "failed to allocate offloading context\n");
++ return -1;
++ }
++ priv->offload->priv = priv;
++
++ strncpy(priv->offload->wan_dev, QCA8K_DEFAULT_WAN_INTERFACE, sizeof(priv->offload->wan_dev));
++ strncpy(priv->offload->lan_dev, QCA8K_DEFAULT_LAN_INTERFACE, sizeof(priv->offload->lan_dev));
++
++ /* add some debug files to dbugfs */
++ qca8k_debugfs_init(priv);
++
++ /* call the various init functions that setup the switch registers to a
++ default state */
++ qca8k_pub_ip_init(priv);
++ qca8k_priv_ip_init(priv);
++ qca8k_iface_init(priv);
++ qca8k_arp_init(priv);
++ qca8k_napt_init(priv);
++ qca8k_nat_init(priv);
++ qca8k_acl_init(priv);
++ qca8k_arl_init(priv);
++
++ /* init default route handling */
++ qca8k_route_init(priv);
++
++ /* add the hooks used to add acquire MACs and IPs of our
++ lan/wan interface */
++ qca8k_hook_iface_init(priv);
++
++ /* start monitoring the CT table */
++ qca8k_hook_ct_init(priv);
++
++ /* start listening for fib events */
++ qca8k_fib_init(priv);
++
++ /* enable L3 routing feature */
++ qca8k_rmw(priv, QCA8K_HROUTER_CONTROL, 0,
++ QCA8K_HROUTER_CONTROL_ROUTER_EN);
++
++ /* enable L3 NAT and ACL feature */
++ qca8k_rmw(priv, QCA8K_REG_MODULE_EN, 0,
++ QCA8K_MODULE_EN_ACL | QCA8K_MODULE_EN_L3);
++
++ /* start the QoS */
++ qca8k_qos_init(priv);
++
++ /* low level security filters */
++ qca8k_norm_init(priv);
++
++ /* start our background worker thread */
++ qca8k_thread_start(priv);
++
++ return 0;
++}
++
++void
++qca8k_abort(struct qca8k_priv *priv, const char *func)
++{
++ if (func)
++ qca8k_info(priv, "%s: something went wrong, disabling offloading\n", func);
++
++ /* disable L3 routing feature */
++ qca8k_reg_clear(priv, QCA8K_HROUTER_CONTROL,
++ QCA8K_HROUTER_CONTROL_ROUTER_EN);
++
++ /* disable L3 NAT and ACL feature */
++ qca8k_reg_clear(priv, QCA8K_REG_MODULE_EN,
++ QCA8K_MODULE_EN_ACL | QCA8K_MODULE_EN_L3);
++ qca8k_hook_ct_exit(priv);
++ qca8k_hook_iface_exit(priv);
++ qca8k_fib_exit(priv);
++}
++
++static void __exit
++qca8k_offload_exit(void)
++{
++ struct qca8k_priv *priv = qca8k_priv;
++
++ qca8k_abort(priv, NULL);
++ qca8k_debugfs_exit(priv);
++}
++
++
++module_init(qca8k_offload_init);
++module_exit(qca8k_offload_exit);
++
++MODULE_DESCRIPTION("Qualcomm switch offloading driver");
++MODULE_AUTHOR("John Crispin <john@phrozen.org");
++MODULE_LICENSE("GPL v2");
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_l3.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_l3.c
+@@ -0,0 +1,106 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++static int
++qca8k_l3_busy_wait(struct qca8k_priv *priv)
++{
++ int retry = 0x1000;
++ u32 reg;
++ int busy;
++
++ /* loop until the busy flag ahs cleared */
++ do {
++ reg = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++ busy = reg & QCA8K_L3_ENTRY_BUSY;
++ } while (retry-- && busy);
++
++ if (!retry) {
++ qca8k_info(priv, "L3 table access busy\n");
++ return -1;
++ }
++
++ return 0;
++}
++
++static u32
++qca8k_arp_learn_set(struct qca8k_priv *priv, u32 val)
++{
++ u32 reg;
++
++ /* read, mask, write the current ARP learning state */
++ reg = qca8k_read(priv, QCA8K_HROUTER_PBASED_CONTROL2);
++ val &= 0x7f7f;
++ val |= reg & 0xffff8080;
++ qca8k_write(priv, QCA8K_HROUTER_PBASED_CONTROL2, val);
++
++ return reg & 0x7f7f;
++}
++
++int
++qca8k_l3_access(struct qca8k_priv *priv,
++ enum qca8k_napt_table table,
++ enum qca8k_napt_cmd cmd,
++ enum qca8k_l3_select select,
++ u32 idx)
++{
++ int retry = 1, learn = 0, ret = -1, i;
++ u32 reg;
++
++ /* make sure the L# table has finished whatever it was doing before
++ this access */
++ if (qca8k_l3_busy_wait(priv))
++ return -1;
++
++ /* busy - 31*/
++ reg = QCA8K_L3_ENTRY_BUSY;
++ /* search - 22:18 */
++ reg |= select << 18;
++ /* index - 17:8 */
++ reg |= (idx & 0x3fff) << 8;
++ /* table - 5:4 */
++ reg |= (table & 0x3) << 4;
++ /* cmd - 2:0 */
++ reg |= cmd & 0x7;
++
++ /* disable ARP learning prior to any ARP table access */
++ if (table == QCA8K_L3_ARP)
++ learn = qca8k_arp_learn_set(priv, 0);
++
++ if (learn)
++ retry = 10;
++
++ if (table == QCA8K_L3_NAPT && cmd == QCA8K_L3_SEARCH)
++ retry = 0;
++
++ /* trigger the actual i/o operation on the L3 table */
++ qca8k_write(priv, QCA8K_REG_L3_ENTRY_CTRL, reg);
++
++ /* wait for the switch to complete the operation */
++ for (i = 0; i < retry; i++)
++ if (!qca8k_l3_busy_wait(priv)) {
++ ret = 0;
++ break;
++ }
++
++ /* hw flush operations require a 10ms wait */
++ if (cmd == QCA8K_L3_FLUSH)
++ mdelay(10);
++
++ /* enable ARP learning if we disabled it */
++ if (learn)
++ qca8k_arp_learn_set(priv, learn);
++
++ return ret;
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_napt.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_napt.c
+@@ -0,0 +1,337 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++static void
++qca8k_napt_read(struct qca8k_priv *priv, struct qca8k_napt *napt)
++{
++ u32 reg[7];
++ u32 ctrl;
++ int i;
++
++ /* load the L3 table content into an array */
++ for (i = 0; i < 7; i++)
++ reg[i] = qca8k_read(priv, QCA8K_REG_L3_ENTRY0 + (i * 4));
++ ctrl = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++
++ /* index - 241:252 */
++ napt->idx = (ctrl >> 8) & 0x3ff;
++ /* aging status - 131:128 */
++ napt->aging = reg[4] & 0xf;
++ /* protocol - 119:118 */
++ napt->proto = (reg[3] >> 22) & 0x3;
++ /* action - 117:116 */
++ napt->action = (reg[3] >> 20) & 0x3;
++ /* src ip - 115:84 */
++ napt->src_ip = (reg[3] << 12) & 0xfffff000;
++ napt->src_ip |= (reg[2] >> 20) & 0x00000fff;
++ napt->src_ip = htonl(napt->src_ip);
++ /* trans ip idx - 83:80 */
++ napt->trans_ip_idx = (reg[2] >> 16) & 0xf;
++ /* trans ip port - 79:64 */
++ napt->trans_ip_port = htons(reg[2] & 0xffff);
++ /* src port - 63:48 */
++ napt->src_port = htons(reg[1] >> 16);
++ /* dst port - 47:32 */
++ napt->dst_port = htons(reg[1] & 0xffff);
++ /* dst ip - 31:0 */
++ napt->dst_ip = htonl(reg[0]);
++}
++
++static int
++_qca8k_napt_write(struct qca8k_priv *priv, enum qca8k_napt_cmd cmd, int proto,
++ __be32 src_ip, u8 trans_ip_idx, __be16 trans_port,
++ __be16 src_port, __be16 dst_port, __be32 dst_ip)
++{
++ u32 reg[7] = { 0 }, src, ctrl, status;
++ int i;
++
++ src = ntohl(src_ip);
++
++ /* aging status - 131:128 */
++ reg[4] |= 14;
++ /* protocol - 119:118 */
++ switch (proto) {
++ case IPPROTO_UDP:
++ reg[3] |= 1 << 22;
++ break;
++ case IPPROTO_GRE:
++ reg[3] |= 3 << 22;
++ break;
++ default:
++ /* TCP is 0 */
++ break;
++ }
++ /* action - 117:116 */
++ reg[3] |= QCA8K_FORWARD << 20;
++ /* src ip - 115:84 */
++ reg[3] |= src >> 12;
++ reg[2] |= (src & 0xfff) << 20;
++ /* trans ip idx - 83:80 */
++ reg[2] |= trans_ip_idx << 16;
++ /* trans ip port - 79:64 */
++ reg[2] |= ntohs(trans_port);
++ /* src port - 63:48 */
++ reg[1] |= ntohs(src_port) << 16;
++ /* dst port - 47:32 */
++ reg[1] |= ntohs(dst_port);
++ /* dst ip - 31:0 */
++ reg[0] = ntohl(dst_ip);
++
++ /* write the entry into the L3 table */
++ for (i = 0; i < 7; i++)
++ qca8k_write(priv, QCA8K_REG_L3_ENTRY0 + (i * 4), reg[i]);
++
++ /* trigger the L3 table i/o operations */
++ qca8k_l3_access(priv, QCA8K_L3_NAPT, cmd, 0, 0);
++
++ /* read the control register and figure out the entries tbale index */
++ ctrl = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++
++ status = (ctrl & QCA8K_L3_ENTRY_STATUS);
++ if (!status) {
++ qca8k_debug(priv, " failed to %s napt entry\n",
++ (cmd == QCA8K_L3_DEL) ? ("del") : ("add"));
++ return -1;
++ }
++
++ return (ctrl >> 8) & 0x3ff;
++}
++
++int
++qca8k_napt_write(struct qca8k_priv *priv, int proto, __be32 src_ip,
++ u8 trans_ip_idx, __be16 trans_port, __be16 src_port,
++ __be16 dst_port, __be32 dst_ip)
++{
++ int ret;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ ret = _qca8k_napt_write(priv, QCA8K_L3_ADD, proto, src_ip,
++ trans_ip_idx, trans_port, src_port, dst_port,
++ dst_ip);
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return ret;
++}
++
++int
++qca8k_napt_del(struct qca8k_priv *priv, struct qca8k_napt *napt)
++{
++ int proto;
++ int ret;
++
++ switch (napt->proto) {
++ case QCA8K_NAPT_UDP:
++ proto = IPPROTO_UDP;
++ break;
++ case QCA8K_NAPT_GRE:
++ proto = IPPROTO_GRE;
++ break;
++ case QCA8K_NAPT_TCP:
++ default:
++ proto = IPPROTO_TCP;
++ break;
++ }
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ ret = _qca8k_napt_write(priv, QCA8K_L3_DEL, proto, napt->src_ip,
++ napt->trans_ip_idx, napt->trans_ip_port,
++ napt->src_port, napt->dst_port,
++ napt->dst_ip);
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return ret;
++}
++
++static int
++qca8k_napt_access(struct qca8k_priv *priv, struct qca8k_napt *napt,
++ enum qca8k_napt_cmd cmd, enum qca8k_l3_select select, u32 idx)
++{
++ u32 reg;
++ u32 status;
++
++ if (qca8k_l3_access(priv, QCA8K_L3_NAPT, cmd, select, idx))
++ return -1;
++
++ reg = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++ status = (reg & QCA8K_L3_ENTRY_STATUS);
++ if (status && napt)
++ qca8k_napt_read(priv, napt);
++
++ return !status;
++}
++
++int
++qca8k_napt_iterate(struct qca8k_priv *priv, struct qca8k_napt *napt, u32 idx,
++ u16 age)
++{
++ enum qca8k_l3_select select = 0;
++
++ if (idx == QCA8K_NAPT_MAX)
++ idx--;
++ else if (idx == QCA8K_NAPT_MAX - 1)
++ /* last one so stop */
++ return -1;
++
++ if (age) {
++ select = QCA8K_L3_AGE;
++ qca8k_write(priv, QCA8K_REG_L3_ENTRY4, age);
++ }
++
++ return qca8k_napt_access(priv, napt, QCA8K_L3_NEXT, select, idx);
++}
++
++int
++qca8k_napt_get_idx(struct qca8k_priv *priv, struct qca8k_napt *napt, u32 idx)
++{
++ int ret;
++ int _idx;
++
++ if (!idx)
++ _idx = QCA8K_NAPT_MAX;
++ else
++ _idx = idx - 1;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ ret = qca8k_napt_access(priv, napt, QCA8K_L3_NEXT, 0, _idx);
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ if (!ret && napt->idx != idx)
++ return -1;
++
++ return ret;
++}
++
++int
++qca8k_napt_search(struct qca8k_priv *priv, struct qca8k_napt *napt,
++ enum qca8k_l3_select select, __be32 ip)
++{
++ int ret;
++
++ ip = ntohl(ip);
++ qca8k_mutex_lock(&priv->reg_mutex);
++ if (select == QCA8K_L3_SIP) {
++ qca8k_write(priv, QCA8K_REG_L3_ENTRY3, ip >> 12);
++ qca8k_write(priv, QCA8K_REG_L3_ENTRY2, (ip & 0xfff) << 20);
++ } else if (select == QCA8K_L3_PIP) {
++ qca8k_write(priv, QCA8K_REG_L3_ENTRY0, ip);
++ }
++ ret = qca8k_napt_access(priv, napt, QCA8K_L3_NEXT, select, 0);
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return ret;
++}
++
++int
++qca8k_napt_flush(struct qca8k_priv *priv)
++{
++ u32 reg;
++ int ret = -1;
++
++ /* trigger flush operation on the L3 NAPT table */
++ qca8k_mutex_lock(&priv->reg_mutex);
++ if (!qca8k_l3_access(priv, QCA8K_L3_NAPT, QCA8K_L3_FLUSH, 0, 0)) {
++ /* make sure that the flush operation worked */
++ reg = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++ ret = !(reg & QCA8K_L3_ENTRY_STATUS);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return ret;
++}
++
++static int
++qca8k_fop_napt_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ struct qca8k_napt entry = { 0 };
++ u32 idx = QCA8K_NAPT_MAX;
++
++ /* dump napt table */
++ seq_printf(seq, "\ncurrently %d entries are used\n",
++ qca8k_read(priv, QCA8K_REG_NAPT_USED_COUNT));
++ seq_printf(seq, "id\tsrc\t\tdst\t\ttransport\taction\tproto\taging\n");
++ qca8k_mutex_lock(&priv->reg_mutex);
++ while (!qca8k_napt_iterate(priv, &entry, idx, 0)) {
++ idx = entry.idx;
++ seq_printf(seq, "%d\t%08x:%d\t%08x:%d\t%d:%05d\t\t",
++ entry.idx, ntohl(entry.src_ip), ntohs(entry.src_port),
++ ntohl(entry.dst_ip), ntohs(entry.dst_port), entry.trans_ip_idx, entry.trans_ip_port);
++ switch (entry.action) {
++ case QCA8K_MIRROR:
++ seq_printf(seq, "mirror\t");
++ break;
++ case QCA8K_COPY:
++ seq_printf(seq, "copy\t");
++ break;
++ case QCA8K_FORWARD:
++ seq_printf(seq, "fwd\t");
++ break;
++ case QCA8K_REDIRECT:
++ seq_printf(seq, "redir\t");
++ break;
++ default:
++ seq_printf(seq, "???\t");
++ break;
++ }
++ switch (entry.proto) {
++ case QCA8K_NAPT_TCP:
++ seq_printf(seq, "tcp\t");
++ break;
++ case QCA8K_NAPT_UDP:
++ seq_printf(seq, "udp\t");
++ break;
++ case QCA8K_NAPT_GRE:
++ seq_printf(seq, "gre\t");
++ break;
++ default:
++ seq_printf(seq, "any\t");
++ break;
++ }
++ seq_printf(seq, "%d\n", entry.aging);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static int
++qca8k_fop_napt_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_napt_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_napt_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_napt_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++void
++qca8k_napt_init(struct qca8k_priv *priv)
++{
++ qca8k_write(priv, QCA8K_HNAT_CONTROL, 0x5f01cb);
++
++ /* global locktime should be 10uS */
++ qca8k_rmw(priv, QCA8K_HROUTER_CONTROL,
++ QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_M,
++ 1 << QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_S);
++
++ /* flush the napt table */
++ qca8k_napt_flush(priv);
++
++ debugfs_create_file("napt", 0600, priv->offload->rootdir, priv, &qca8k_napt_ops);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_nat.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_nat.c
+@@ -0,0 +1,35 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++static int
++qca8k_nat_flush(struct qca8k_priv *priv)
++{
++ u32 reg;
++
++ /* trigger flush operation on the L3 NAT table */
++ if (qca8k_l3_access(priv, QCA8K_L3_NAT, QCA8K_L3_FLUSH, 0, 0))
++ return -1;
++
++ /* make sure that the flush operation worked */
++ reg = qca8k_read(priv, QCA8K_REG_L3_ENTRY_CTRL);
++
++ return !(reg & QCA8K_L3_ENTRY_STATUS);
++}
++
++void
++qca8k_nat_init(struct qca8k_priv *priv)
++{
++ qca8k_nat_flush(priv);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_normalize.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_normalize.c
+@@ -0,0 +1,173 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++static struct qca8k_norm_strings {
++ const char *name;
++ int reg;
++ int shift;
++ int mask;
++ int val;
++} qca8k_norm_strings[] = {
++ { "tcp_push1_ack0", 0, 29 },
++ { "tcp_fin1_ack0", 0, 28 },
++ { "tcp_rst1_with_data", 0, 27 },
++ { "tcp_syn1_with_data", 0, 26 },
++ { "tcp_rst1", 0, 25 },
++ { "tcp_syn0_ack0_rst0", 0, 24 },
++ { "tcp_syn1_fin1", 0, 23 },
++ { "tcp_syn1_rst1", 0, 22 },
++ { "tcp_nullscan", 0, 21 },
++ { "tcp_xmasscan", 0, 20 },
++ { "tcp_syn1_ack1_psh1", 0, 19 },
++ { "tcp_syn1_psh1", 0, 18 },
++ { "tcp_syn1_urg1", 0, 17 },
++ { "tcp_syn_err", 0, 16 },
++ { "tcp_hdr_min", 0, 15 },
++ { "tcp_same_port", 0, 14 },
++ { "ipv4_checksum", 0, 13 },
++ { "ipv4_dip_err", 0, 12 },
++ { "ipv4_sip_err", 0, 11 },
++ { "ipv4_frag_len", 0, 10 },
++ { "ipv4_frag_max", 0, 9 },
++ { "ipv4_frag_min", 0, 8 },
++ { "ipv4_df", 0, 7 },
++ { "ipv4_len", 0, 6 },
++ { "ipv4_hdr_len_check", 0, 5 },
++ { "ipv4_hdr_len", 0, 4 },
++ { "ipv4_hdr_len_min", 0, 3 },
++ { "ip_same_port", 0, 2 },
++ { "ip_ver", 0, 1 },
++ { "ip_sip_eq_dip", 0, 0 },
++
++ { "ipv4_frag_min_len", 1, 24, 0xff },
++ { "invalid_mac_sa", 1, 20 },
++ { "ipv4_min_pkt_len", 1, 19 },
++ { "ipv6_min_pkt_len", 1, 18 },
++ { "ipv6_invalid_sip", 1, 17 },
++ { "ipv6_invalid_dip", 1, 16 },
++ { "tcp_hdr_min_len", 1, 12, 0xf },
++ { "icmp_checksum", 1, 11 },
++ { "icmpv6_frag", 1, 10 },
++ { "icmpv4_frag", 1, 9 },
++ { "icmpv6_max", 1, 8 },
++ { "icmpv4_max", 1, 7 },
++ { "udp_checksum", 1, 6 },
++ { "udp_len", 1, 5 },
++ { "udp_same_port", 1, 4 },
++ { "tcp_option", 1, 3 },
++ { "tcp_urg0_ptr_err", 1, 2 },
++ { "tcp_checksum", 1, 1 },
++ { "tcp_urg1_ack0", 1, 0 },
++
++ { "icmpv6_max_len", 2, 16, 0x3fff },
++ { "icmpv4_max_len", 2, 16, 0x3fff },
++};
++
++#define QCA8K_REG_NORMALIZE_CTRL(r) (0x200 + (r * 4))
++
++int
++qca8k_norm_set(struct qca8k_priv *priv, const char *name, int val)
++{
++ struct qca8k_norm_strings *norm = qca8k_norm_strings;
++ int i, mask = 1;
++
++ for (i = 0; i < ARRAY_SIZE(qca8k_norm_strings); i++, norm++)
++ if (!strcmp(norm->name, name))
++ break;
++
++ if (i == ARRAY_SIZE(qca8k_norm_strings))
++ return -1;
++
++ if (norm->mask) {
++ mask = norm->mask;
++ val &= mask;
++ } else {
++ val = !!val;
++ }
++ norm->val = val;
++
++ qca8k_rmw(priv, QCA8K_REG_NORMALIZE_CTRL(norm->reg),
++ mask << norm->shift, val << norm->shift);
++
++ return 0;
++}
++
++static ssize_t
++qca8k_fop_normalize_write(struct file *file, const char __user * ubuf,
++ size_t len, loff_t *offp)
++{
++ struct seq_file *m = file->private_data;
++ struct qca8k_priv *priv = m->private;
++ char *tok[2] = { 0 };
++ int cnt, ret = -1;
++ unsigned long val;
++
++ cnt = qca8k_debugfs_tokenize(ubuf, len, tok, 2);
++
++ if ((cnt == 2) && !kstrtoul(tok[1], 10, &val))
++ if (!qca8k_norm_set(priv, tok[0], val))
++ ret = len;
++
++ if (tok[0])
++ kfree(tok[0]);
++
++ return len;
++}
++
++static int
++qca8k_fop_normalize_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_norm_strings *norm = qca8k_norm_strings;
++ int i;
++
++ seq_printf(seq, "val key\n");
++ for (i = 0; i < ARRAY_SIZE(qca8k_norm_strings); i++, norm++)
++ seq_printf(seq, "%2d %s\n", norm->val, norm->name);
++
++ return 0;
++}
++
++static int
++qca8k_fop_normalize_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_normalize_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_normalize_ops = {
++ .owner = THIS_MODULE,
++ .open = qca8k_fop_normalize_open,
++ .read = seq_read,
++ .write = qca8k_fop_normalize_write,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++void
++qca8k_norm_init(struct qca8k_priv *priv)
++{
++ struct qca8k_norm_strings *norm = qca8k_norm_strings;
++ u32 val[3];
++ int i;
++
++ for (i = 0; i < 3; i++)
++ val[i] = qca8k_read(priv, QCA8K_REG_NORMALIZE_CTRL(i));
++
++ for (i = 0; i < ARRAY_SIZE(qca8k_norm_strings); i++, norm++) {
++ norm->val = val[norm->reg] >> norm->shift;
++ norm->val &= (norm->mask) ? (norm->mask) : (1);
++ }
++
++ debugfs_create_file("normalize", 0600, priv->offload->rootdir, priv, &qca8k_normalize_ops);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_private_ip.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_private_ip.c
+@@ -0,0 +1,112 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++u32
++qca8k_priv_ip_get(struct qca8k_priv *priv)
++{
++ return htonl(priv->offload->qca8k_priv_ip);
++}
++
++u32
++qca8k_priv_netmask_get(struct qca8k_priv *priv)
++{
++ return htonl(priv->offload->qca8k_priv_netmask);
++}
++
++int
++qca8k_priv_ip_match(struct qca8k_priv *priv, u32 ip)
++{
++ if ((priv->offload->qca8k_priv_ip & priv->offload->qca8k_priv_netmask) == (ip & priv->offload->qca8k_priv_netmask))
++ return 1;
++
++ return 0;
++}
++
++int
++qca8k_priv_ip_set(struct qca8k_priv *priv, u32 ip)
++{
++ /* make sure the ip is in host endianess */
++ ip = ntohl(ip);
++
++ /* ignore function calls if the ip has not chnaged */
++ if (priv->offload->qca8k_priv_ip == (ip & priv->offload->qca8k_priv_netmask))
++ return 0;
++
++ /* store ip in the shadow variable */
++ priv->offload->qca8k_priv_ip = ip & priv->offload->qca8k_priv_netmask;
++
++ /* write the ip to the register */
++ qca8k_write(priv, QCA8K_REG_IPV4_PRI_BASE_ADDR, ip);
++
++ qca8k_debug(priv, "setting private ip: %08x\n", priv->offload->qca8k_priv_ip);
++
++ return 1;
++}
++
++int
++qca8k_priv_netmask_set(struct qca8k_priv *priv, u32 netmask)
++{
++ /* make sure the mask is in host endianess */
++ netmask = ntohl(netmask);
++
++ /* ignore function calls if the netmask has not chnaged */
++ if (priv->offload->qca8k_priv_netmask == netmask)
++ return 0;
++
++ /* store netmask in the shadow variable */
++ priv->offload->qca8k_priv_netmask = netmask;
++
++ /* write the netmask to the register */
++ qca8k_write(priv, QCA8K_REG_IPV4_PRI_ADDR_MASK, netmask);
++
++ qca8k_debug(priv, "setting private netmask: %08x\n", priv->offload->qca8k_priv_netmask);
++
++ return 1;
++}
++
++static int
++qca8k_fop_priv_ip_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ u32 raw;
++
++ /* dump ip at 0x0470 */
++ raw = qca8k_read(priv, QCA8K_REG_IPV4_PRI_BASE_ADDR);
++ seq_printf(seq, "ipaddr\t\t%08x", raw);
++ raw = qca8k_read(priv, QCA8K_REG_IPV4_PRI_ADDR_MASK);
++ seq_printf(seq, "/%08x\n", raw);
++
++ return 0;
++}
++
++static int
++qca8k_fop_priv_ip_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_priv_ip_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_priv_ip_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_priv_ip_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++void
++qca8k_priv_ip_init(struct qca8k_priv *priv)
++{
++ debugfs_create_file("private_ip", 0600, priv->offload->rootdir, priv, &qca8k_priv_ip_ops);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_public_ip.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_public_ip.c
+@@ -0,0 +1,165 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++int
++qca8k_pub_ip_add(struct qca8k_priv *priv, __be32 _ip)
++{
++ int i, idx = -1;
++ u32 ip = ntohl(_ip);
++
++ /* scan over the shadow table to see if we already know this IP */
++ for (i = 0; i < QCA8K_PUBLIC_IP_MAX; i++) {
++ if ((ip == priv->offload->public_ip[i].ip) &&
++ (priv->offload->public_ip[i].refcount)) {
++ priv->offload->public_ip[i].refcount++;
++ return i;
++ } else if ((idx < 0) && !priv->offload->public_ip[i].refcount) {
++ /* remember the first free entry that we find */
++ idx = i;
++ }
++ }
++
++ /* is the table full ? */
++ if ((i >= QCA8K_PUBLIC_IP_MAX) && (idx < 0))
++ return -1;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++
++ /* there ate 3 tables where we need to store the public IP */
++ qca8k_write(priv, QCA8K_REG_PUB_IP_EDIT0 + (idx << 4), ip);
++ qca8k_write(priv, QCA8K_REG_PUB_IP_OFFLOAD + (idx << 2), ip);
++ qca8k_write(priv, QCA8K_REG_PUB_IP_TBL0 + (idx << 4), ip);
++ qca8k_write(priv, QCA8K_REG_PUB_IP_TBL1 + (idx << 4), 1);
++
++ /* enable the public IP by unmasking it */
++ qca8k_reg_set(priv, QCA8K_REG_PUB_IP_VALID, BIT(idx));
++
++ /* store the info inside the shadow table */
++ priv->offload->public_ip[idx].ip = ip;
++ priv->offload->public_ip[idx].refcount++;
++
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ qca8k_debug(priv, "adding public ip: %08x\n", priv->offload->public_ip[idx].ip);
++
++ return idx;
++}
++
++void
++qca8k_pub_ip_del(struct qca8k_priv *priv, u32 idx)
++{
++ /* make sure that the entry actually exists */
++ if (!priv->offload->public_ip[idx].refcount) {
++ qca8k_info(priv, " failed to delete public ip: %08x\n",
++ priv->offload->public_ip[idx].ip);
++ return;
++ }
++
++ /* check if this was the last user of the IP */
++ qca8k_mutex_lock(&priv->reg_mutex);
++ priv->offload->public_ip[idx].refcount--;
++ if (!priv->offload->public_ip[idx].refcount) {
++ /* mask the the table entry */
++ qca8k_reg_clear(priv, QCA8K_REG_PUB_IP_VALID, BIT(idx));
++
++ /* set the valib bit to 0 */
++ qca8k_write(priv, QCA8K_REG_PUB_IP_TBL1 + (idx << 4), 0);
++
++ qca8k_debug(priv, "deleting public ip: %08x\n", priv->offload->public_ip[idx].ip);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++}
++
++static int
++qca8k_fop_public_ip_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ unsigned long int valid;
++ u32 addr, val1, val2;
++ u16 bit;
++ int i;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++
++ /* find out which addresses are currently flagges as valid */
++ valid = qca8k_read(priv, QCA8K_REG_PUB_IP_VALID);
++ seq_printf(seq, "valid_addr - %08x\n", (u32) valid);
++
++ /* dump the table at 0x5aa00 */
++ seq_printf(seq, "\ntable @%08x\n", QCA8K_REG_PUB_IP_TBL0);
++
++ for (i = 0; i < 16; i++) {
++ addr = QCA8K_REG_PUB_IP_TBL0 + (i << 4);
++ val2 = qca8k_read(priv, addr + 4);
++ if (!val2)
++ continue;
++ val1 = qca8k_read(priv, addr);
++ seq_printf(seq, "%d\t%08x %08x\n",
++ i, val1, val2);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ /* dump the table at 0x02100 */
++ seq_printf(seq, "\ntable @%08x\n", QCA8K_REG_PUB_IP_EDIT0);
++ for_each_set_bit(bit, &valid, 16) {
++ addr = QCA8K_REG_PUB_IP_EDIT0 + (bit << 4);
++ val1 = qca8k_read(priv, addr);
++ seq_printf(seq, "%d\t%08x\n",
++ bit, val1);
++ }
++
++ /* dump the table at 0x02f00 */
++ seq_printf(seq, "\ntable @%08x\n", QCA8K_REG_PUB_IP_OFFLOAD);
++ for_each_set_bit(bit, &valid, 16) {
++ addr = QCA8K_REG_PUB_IP_EDIT0 + (bit << 2);
++ val1 = qca8k_read(priv, addr);
++ seq_printf(seq, "%d\t%08x\n",
++ bit, val1);
++ }
++
++ return 0;
++}
++
++static int
++qca8k_fop_public_ip_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_public_ip_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_public_ip_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_public_ip_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++void
++qca8k_pub_ip_init(struct qca8k_priv *priv)
++{
++ int i;
++
++ /* flush the public ip table */
++ for (i = 0; i < QCA8K_PUBLIC_IP_MAX; i++)
++ qca8k_write(priv, QCA8K_REG_PUB_IP_TBL1 + (i << 4), 0);
++
++ /* mask all public ip entries */
++ qca8k_write(priv, QCA8K_REG_PUB_IP_VALID, 0);
++
++ /* Isolate public and private network segments */
++ qca8k_rmw(priv, QCA8K_REG_VLAN_TRANS_TEST, 0, 1);
++
++ debugfs_create_file("public_ip", 0600, priv->offload->rootdir, priv, &qca8k_public_ip_ops);
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_qos.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_qos.c
+@@ -0,0 +1,634 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "qca8k.h"
++
++static int qca8k_burst_rates[] = { 0, 2, 4, 8, 16, 32, 128, 512 };
++
++static int
++qca8k_find_burst_rate(int rate)
++{
++ int i = 0;
++
++ while ((qca8k_burst_rates[i] < rate) && (i < ARRAY_SIZE(qca8k_burst_rates)))
++ i++;
++ return i;
++}
++
++static int
++qca8k_qos_port_shaper(struct qca8k_priv *priv, int port, int queue,
++ u32 cir, u32 eir, u32 cbs, u32 ebs)
++{
++ int shift = QCA8K_QOS_ECTRL_IR_S * (queue % 2);
++ u32 val;
++
++ if ((port > 0) && (port < 5))
++ return -1;
++
++ if (!cir) {
++ cir = eir = 0x7fff << 5;
++ cbs = ebs = 0;
++ }
++
++ /* set cir */
++ qca8k_rmw(priv, QCA8K_REG_QOS_ECTRL(port, queue / 2),
++ QCA8K_QOS_ECTRL_IR_M << shift,
++ (cir >> 5) << shift);
++
++ /* set eir */
++ qca8k_rmw(priv, QCA8K_REG_QOS_ECTRL(port, 3 + (queue / 2)),
++ QCA8K_QOS_ECTRL_IR_M << shift,
++ (eir >> 5) << shift);
++
++ /* set cbs/ebs */
++ val = (qca8k_find_burst_rate(cbs) << 4) | qca8k_find_burst_rate(ebs);
++ shift = 0;
++ if (queue > 3)
++ shift = 16;
++ shift += QCA8K_QOS_ECTRL_BURST_S * (queue % 4) * 2;
++ qca8k_rmw(priv, QCA8K_REG_QOS_ECTRL(port, 6 + (queue / 4)),
++ (0xff << shift),
++ val << shift);
++
++ /* set byte mode */
++ qca8k_reg_clear(priv, QCA8K_REG_QOS_ECTRL(port, 7),
++ BIT(queue) << QCA8K_QOS_ECTRL7_Q_UNIT_S);
++
++ return 0;
++}
++
++static int
++qca8k_fop_egress_shaper_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ int i;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ int ctrl = 7, queues = 6, j;
++ u32 ectrl[8];
++
++ if ((i > 0) && (i < 5)) {
++ ctrl = 5;
++ queues = 4;
++ }
++ for (j = 0; j < ctrl + 1; j++)
++ ectrl[j] = qca8k_read(priv, QCA8K_REG_QOS_ECTRL(i, j));
++
++ seq_printf(seq, "port %d - mode: %x limit: %x timeslots: %x\n", i,
++ (ectrl[ctrl] >> QCA8K_QOS_ECTRL_TYPE_S) & QCA8K_QOS_ECTRL_TYPE_M,
++ !!(ectrl[ctrl] & QCA8K_QOS_ECTRL_RATE_EN),
++ ectrl[ctrl] & QCA8K_QOS_ECTRL_TIME_M);
++
++ seq_printf(seq, "Q cir cbs eir ebs\n");
++ for (j = 0; j < queues; j++) {
++ int qir = j / 2;
++ int qbs = queues + (j / 4);
++ int sir = QCA8K_QOS_ECTRL_IR_S * (j % 2);
++ int sbs = QCA8K_QOS_ECTRL_BURST_S * 2 * (j % 4);
++ u32 cir, eir, cbs, ebs;
++
++ if (j > 3)
++ sbs += 16;
++ /* *cough* - register layouts and bits inside are not linear */
++ cir = (ectrl[qir] >> sir) & QCA8K_QOS_ECTRL_IR_M;
++ cbs = (ectrl[qbs] >> (sbs + 4)) & QCA8K_QOS_ECTRL_BURST_M;
++ eir = (ectrl[(queues / 2) + qir] >> sir) & QCA8K_QOS_ECTRL_IR_M;
++ ebs = (ectrl[qbs] >> sbs) & QCA8K_QOS_ECTRL_BURST_M;
++
++ seq_printf(seq, "%d %04x %3d %04x %3d\n", j,
++ cir, qca8k_burst_rates[cbs],
++ eir, qca8k_burst_rates[ebs]);
++ }
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static ssize_t
++qca8k_fop_egress_shaper_write(struct file *file, const char __user * ubuf,
++ size_t len, loff_t *offp)
++{
++ struct seq_file *m = file->private_data;
++ struct qca8k_priv *priv = m->private;
++ char *tok[7] = { 0 };
++ int cnt, ret = -1;
++ unsigned long id;
++
++ /* queue port queue cir eir cbs ebs
++ * port port limit timeslot
++ */
++ cnt = qca8k_debugfs_tokenize(ubuf, len, tok, ARRAY_SIZE(tok));
++ if (cnt < 2)
++ goto out;
++
++ if (kstrtoul(tok[1], 10, &id))
++ goto out;
++
++ if (id >= QCA8K_NUM_PORTS)
++ goto out;
++
++ if ((id > 0) && (id < 5))
++ goto out;
++
++ if ((cnt == 4) && !strcmp(tok[0], "port")) {
++ unsigned long limit, timeslot;
++ u32 val = 0;
++
++ if (kstrtoul(tok[2], 10, &limit))
++ goto out;
++
++ if (kstrtoul(tok[3], 10, ×lot))
++ goto out;
++
++ val = timeslot & QCA8K_QOS_ECTRL_TIME_M;
++ if (limit)
++ val |= QCA8K_QOS_ECTRL7_RATE_EN;
++
++ qca8k_rmw(priv, QCA8K_REG_QOS_ECTRL(id, 7),
++ QCA8K_QOS_ECTRL_TIME_M | QCA8K_QOS_ECTRL7_RATE_EN,
++ val);
++
++ ret = len;
++ } else if ((cnt == 7) && !strcmp(tok[0], "queue")) {
++ unsigned long cir, eir, cbs, ebs, queue;
++
++ if (kstrtoul(tok[2], 10, &queue))
++ goto out;
++
++ if (kstrtoul(tok[3], 10, &cir))
++ goto out;
++
++ if (kstrtoul(tok[4], 10, &eir))
++ goto out;
++
++ if (kstrtoul(tok[5], 10, &cbs))
++ goto out;
++
++ if (kstrtoul(tok[6], 10, &ebs))
++ goto out;
++
++ qca8k_qos_port_shaper(priv, id, queue, cir, eir, cbs, ebs);
++
++ ret = len;
++ }
++
++out:
++ if (tok[0])
++ kfree(tok[0]);
++
++ return ret;
++}
++
++static int
++qca8k_fop_egress_shaper_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_egress_shaper_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_egress_shaper_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_egress_shaper_open,
++ .write = qca8k_fop_egress_shaper_write,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++static void
++qca8k_qos_port_weight(struct qca8k_priv *priv, int port, int type, u8 *prio)
++{
++ u32 val = 0;
++ int i;
++
++ val = (type & QCA8K_QOS_PORT_WRR_CTRL_M) << QCA8K_QOS_PORT_WRR_CTRL_S;
++ if (prio)
++ for (i = 0; i < 5; i++) {
++ /* port 0 5 6 have 2 extra registers */
++ if ((i > 3) && (port > 0) && (port < 6))
++ continue;
++ val |= (prio[i] & QCA8K_QOS_PORT_WRR_PRIO_M) <<
++ (i * QCA8K_QOS_PORT_WRR_PRIO_S);
++ }
++ qca8k_write(priv, QCA8K_REG_QOS_PORT_WRR_CTRL(port), val);
++}
++
++static void
++qca8k_qos_hol(struct qca8k_priv *priv, int port, int enable, int wred,
++ unsigned long *egress, unsigned long ingress)
++{
++ u32 val = 0;
++ int i;
++
++ /* setup the egress queue depths */
++ if (egress) {
++ for (i = 0; i < 6; i++)
++ val |= (egress[i] & QCA8K_QOS_PORT_HOL0_QUEUE_M) <<
++ (i * QCA8K_QOS_PORT_HOL0_QUEUE_S);
++ val |= (egress[i] & QCA8K_QOS_PORT_HOL0_PORT_M) <<
++ (i * QCA8K_QOS_PORT_HOL0_QUEUE_S);
++ qca8k_write(priv, QCA8K_REG_QOS_PORT_HOL_CTRL0(port), val);
++ }
++
++ /* setup the ingress queue depth and config */
++ val = 0;
++ if (ingress)
++ val |= ingress & QCA8K_QOS_PORT_HOL0_QUEUE_M;
++ if (enable)
++ val |= QCA8K_QOS_PORT_HOL1_QUEUE_ENABLE;
++ if (wred)
++ val |= QCA8K_QOS_PORT_HOL1_QUEUE_WRED;
++ qca8k_write(priv, QCA8K_REG_QOS_PORT_HOL_CTRL1(port), val);
++}
++
++static void
++qca8k_qos_port_threshold(struct qca8k_priv *priv, int port, u8 xon, u8 xoff)
++{
++ u32 val = xon << QCA8K_QOS_PORT_FLOW_THD_XON_S;
++
++ qca8k_write(priv, QCA8K_REG_QOS_PORT_FLOW_THD(port), val);
++}
++
++static int
++qca8k_fop_qos_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++
++ seq_printf(seq, "id pkt limit timeslot\n");
++ seq_printf(seq, "GLOBAL_FWD_THD %08X\n",
++ qca8k_read(priv, QCA8K_REG_QOS_GLOBAL_FLOW_THD));
++ seq_printf(seq, "QM_CTRL %08X\n",
++ qca8k_read(priv, QCA8K_REG_QOS_QM_CTRL));
++ seq_printf(seq, "WAN_QUEUE_MAP %08X\n",
++ qca8k_read(priv, QCA8K_REG_QOS_WAN_QUEUE_MAP));
++ seq_printf(seq, "LAN_QUEUE_MAP %08X\n",
++ qca8k_read(priv, QCA8K_REG_QOS_LAN_QUEUE_MAP));
++
++ return 0;
++}
++
++static int
++qca8k_fop_qos_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_qos_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_qos_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_qos_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++static int
++qca8k_fop_port_weight_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ int i;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ seq_printf(seq, "id type priority\n");
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ u32 wrr = qca8k_read(priv, QCA8K_REG_QOS_PORT_WRR_CTRL(i));
++
++ seq_printf(seq, "%2d %x %08x \n", i,
++ (wrr >> QCA8K_QOS_PORT_WRR_CTRL_S) & QCA8K_QOS_PORT_WRR_CTRL_M,
++ wrr & ~(QCA8K_QOS_PORT_WRR_CTRL_M << QCA8K_QOS_PORT_WRR_CTRL_S));
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static int
++qca8k_fop_port_weight_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_port_weight_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_port_weight_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_port_weight_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++static int
++qca8k_fop_hol_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ int i;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ seq_printf(seq, "id enable wred max ingress egress\n");
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ u32 ctrl0 = qca8k_read(priv, QCA8K_REG_QOS_PORT_HOL_CTRL0(i));
++ u32 ctrl1 = qca8k_read(priv, QCA8K_REG_QOS_PORT_HOL_CTRL1(i));
++ int enable = !!(ctrl1 & QCA8K_QOS_PORT_HOL1_QUEUE_ENABLE);
++ int wred = !!(ctrl1 & QCA8K_QOS_PORT_HOL1_QUEUE_WRED);
++ u32 ingress = ctrl1 & QCA8K_QOS_PORT_HOL1_INGRESS_M;
++ int max = ctrl0 >> (QCA8K_QOS_PORT_HOL0_QUEUE_S * 6);
++ u32 egress = ctrl0 & QCA8K_QOS_PORT_HOL0_EGRESS_M;
++
++ seq_printf(seq, "%d %d %d %02x %x %06x\n", i,
++ enable, wred, max, ingress, egress);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static ssize_t
++qca8k_fop_hol_write(struct file *file, const char __user * ubuf,
++ size_t len, loff_t *offp)
++{
++ struct seq_file *m = file->private_data;
++ struct qca8k_priv *priv = m->private;
++ char *tok[11] = { 0 };
++ int ret = -1, i, cnt;
++ unsigned long id, enable, wred, ingress, egress[7];
++
++ /* port enable wred ingress e0 e1 e2 e3 e4 e5 emax */
++ cnt = qca8k_debugfs_tokenize(ubuf, len, tok, ARRAY_SIZE(tok));
++ if (cnt < 11)
++ goto out;
++
++ if (kstrtoul(tok[0], 10, &id))
++ goto out;
++
++ if (id >= QCA8K_NUM_PORTS)
++ goto out;
++
++ if (kstrtoul(tok[1], 16, &enable))
++ goto out;
++
++ if (kstrtoul(tok[2], 16, &wred))
++ goto out;
++
++ if (kstrtoul(tok[3], 16, &ingress))
++ goto out;
++
++ for (i = 0; i < 7; i++)
++ if (kstrtoul(tok[4 + i], 16, &egress[i]))
++ goto out;
++
++ qca8k_qos_hol(priv, id, enable, wred, egress, ingress);
++
++ ret = len;
++out:
++ if (tok[0])
++ kfree(tok[0]);
++
++ return ret;
++}
++
++static int
++qca8k_fop_hol_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_hol_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_hol_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_hol_open,
++ .write = qca8k_fop_hol_write,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++static int
++qca8k_fop_flow_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ int i;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ seq_printf(seq, "id xon xoff\n");
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ u32 thd = qca8k_read(priv, QCA8K_REG_QOS_PORT_FLOW_THD(i));
++
++ seq_printf(seq, "%d %2x %2x\n", i,
++ (thd >> 16) & 0xff, thd & 0xff);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static int
++qca8k_fop_flow_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_flow_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_flow_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_flow_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++static int
++qca8k_fop_port_priority_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ int i;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ seq_printf(seq, "id da vlan tos\n");
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ u32 pri = qca8k_read(priv, QCA8K_REG_QOS_PORT_PRI_CTRL(i));
++ int da = !!(pri & QCA8K_QOS_PORT_PRI_CTRL_DA);
++ int vlan = !!(pri & QCA8K_QOS_PORT_PRI_CTRL_VLAN);
++ int tos = !!(pri & QCA8K_QOS_PORT_PRI_CTRL_TOS);
++
++ seq_printf(seq, "%d %d %d %d\n", i, da, vlan, tos);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static int
++qca8k_fop_port_priority_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_port_priority_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_port_priority_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_port_priority_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++static int
++qca8k_fop_acl_priority_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++
++ seq_printf(seq, "ipv4: %d\nipv6: %d\n",
++ priv->offload->acl_ipv4_prio,
++ priv->offload->acl_ipv6_prio);
++
++ return 0;
++}
++
++static int
++qca8k_fop_acl_priority_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_acl_priority_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_acl_priority_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_acl_priority_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++static int
++qca8k_fop_tos_priority_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ int i;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ seq_printf(seq, "id tos\n");
++ for (i = 0; i < 8; i++) {
++ u32 tos = qca8k_read(priv, QCA8K_REG_TOS_PRI_MAP(i));
++
++ seq_printf(seq, "%d %08X\n", i, tos);
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static int
++qca8k_fop_tos_priority_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_tos_priority_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_tos_priority_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_tos_priority_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++static int
++qca8k_fop_queue_remap_read(struct seq_file *seq, void *v)
++{
++ struct qca8k_priv *priv = seq->private;
++ u32 addr = QCA8K_REG_PORT_QUEUE_REMAP;
++ int i;
++
++ qca8k_mutex_lock(&priv->reg_mutex);
++ seq_printf(seq, "id q0 q1 q2 q3 q4 q5\n");
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ u32 remap = qca8k_read(priv, addr);
++ int j;
++
++ remap = qca8k_read(priv, addr);
++ seq_printf(seq, "%d ", i);
++ for (j = 0; j < 4; j++)
++ seq_printf(seq, "%d ",
++ (remap & BIT(7 + (j *8))) ? ((remap >> (j * 8)) & 0xf) : (-1));
++ addr += 4;
++ if ((i < 1) || (i > 4)) {
++ remap = qca8k_read(priv, addr);
++ for (j = 0; j < 2; j++)
++ seq_printf(seq, "%d ",
++ (remap & BIT(7 + (j *8))) ? ((remap >> (j * 8)) & 0xf) : (-1));
++ addr += 4;
++ }
++ seq_printf(seq, "\n");
++ }
++ qca8k_mutex_unlock(&priv->reg_mutex);
++
++ return 0;
++}
++
++static int
++qca8k_fop_queue_remap_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, qca8k_fop_queue_remap_read, inode->i_private);
++}
++
++static const struct file_operations qca8k_queue_remap_ops = {
++ .owner = THIS_MODULE,
++ .read = seq_read,
++ .open = qca8k_fop_queue_remap_open,
++ .release = seq_release,
++ .llseek = seq_lseek,
++};
++
++static void
++qca8k_qos_port_priority(struct qca8k_priv *priv, int port, int da,
++ int vlan, int tos)
++{
++ u32 val = qca8k_read(priv, QCA8K_REG_QOS_PORT_PRI_CTRL(port));
++
++ val &= ~QCA8K_QOS_PORT_PRI_CTRL_M;
++ if (da)
++ val |= QCA8K_QOS_PORT_PRI_CTRL_DA;
++ if (vlan)
++ val |= QCA8K_QOS_PORT_PRI_CTRL_VLAN;
++ if (tos)
++ val |= QCA8K_QOS_PORT_PRI_CTRL_TOS;
++ qca8k_write(priv, QCA8K_REG_QOS_PORT_PRI_CTRL(port), val);
++}
++
++void
++qca8k_qos_init(struct qca8k_priv *priv)
++{
++ unsigned long egress_wan[] = { 3, 4, 4, 4, 6, 8, 0x19 };
++ unsigned long egress_lan[] = { 3, 4, 6, 8, 0, 0, 0x19};
++
++ /* setup head of line blocking */
++ qca8k_qos_hol(priv, 0, 1, 1, egress_wan, 6);
++ qca8k_qos_hol(priv, 1, 1, 1, egress_lan, 6);
++ qca8k_qos_hol(priv, 2, 1, 1, egress_lan, 6);
++ qca8k_qos_hol(priv, 3, 1, 1, egress_lan, 6);
++ qca8k_qos_hol(priv, 4, 1, 1, egress_lan, 6);
++ qca8k_qos_hol(priv, 5, 1, 1, egress_wan, 6);
++ qca8k_qos_hol(priv, 6, 1, 1, egress_wan, 6);
++
++ /* set all acl rate controllers to counter mode */
++ qca8k_write(priv, QCA8K_REG_ACL_COUNTER_RST, 0xffffffff);
++ qca8k_write(priv, QCA8K_REG_ACL_POLICY_MODE, 0xffffffff);
++ qca8k_write(priv, QCA8K_REG_ACL_COUNTER_MODE, 0xffffffff);
++ qca8k_write(priv, QCA8K_REG_ACL_COUNTER_RST, 0x0);
++
++ debugfs_create_file("dbg", 0600, priv->offload->qosdir, priv, &qca8k_qos_ops);
++ debugfs_create_file("egress_shaper", 0600, priv->offload->qosdir, priv, &qca8k_egress_shaper_ops);
++ debugfs_create_file("hol", 0600, priv->offload->qosdir, priv, &qca8k_hol_ops);
++ debugfs_create_file("port_weight", 0600, priv->offload->qosdir, priv, &qca8k_port_weight_ops);
++ debugfs_create_file("flow", 0600, priv->offload->qosdir, priv, &qca8k_flow_ops);
++ debugfs_create_file("port_priority", 0600, priv->offload->qosdir, priv, &qca8k_port_priority_ops);
++ debugfs_create_file("acl_priority", 0600, priv->offload->qosdir, priv, &qca8k_acl_priority_ops);
++ debugfs_create_file("tos_priority", 0600, priv->offload->qosdir, priv, &qca8k_tos_priority_ops);
++ debugfs_create_file("queue_remap", 0600, priv->offload->qosdir, priv, &qca8k_queue_remap_ops);
++
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_route.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_route.c
+@@ -0,0 +1,108 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/inetdevice.h>
++#include <net/ip6_route.h>
++
++#include "qca8k.h"
++
++static u32 qca8k_link_local[4] = { 0x000080fe, 0, 0, 0 };
++
++void
++qca8k_route_ip6_worker(struct qca8k_priv *priv)
++{
++ struct qca8k_arp arp = { 0 };
++ struct in6_addr *gateway;
++ struct in6_addr des_addr = IN6ADDR_ANY_INIT;
++ struct rt6_info *rt;
++
++ /* figure out what the gateway is */
++ rt = rt6_lookup(&init_net, &des_addr, &priv->offload->lanip6, 0, 0);
++ if (!rt)
++ return;
++ gateway = &rt->rt6i_gateway;
++
++ /* has the gateway changed ? */
++ if (!memcmp(priv->offload->ipv6_gateway, gateway, 16))
++ return;
++
++ /* make sure that there is an ARP entry for the gateway */
++ if (qca8k_arp_search(priv, &arp, gateway->in6_u.u6_addr32, 1))
++ return;
++
++ /* setup the default route */
++ qca8k_acl_write_route_v6(priv, QCA8K_ACL_IPV6_GATEWAY, qca8k_link_local,
++ 0x10, LAN_MASK, arp.idx, 1);
++
++ /* keep track of the ARP entries index to make sure that we
++ dont flush the entry */
++ priv->offload->ipv6_gateway_arp = arp.idx;
++
++ /* keep track of the gateway. we dont need to reset the route
++ until the gateway changed */
++ memcpy(priv->offload->ipv6_gateway, gateway, 16);
++}
++
++void
++qca8k_route_ip6_addr_add(struct qca8k_priv *priv, struct in6_addr ip, int prefix_len)
++{
++ int i;
++
++ if (ipv6_addr_type(&ip) & IPV6_ADDR_LINKLOCAL)
++ return;
++
++ priv->offload->lanip6 = ip;
++
++ for (i = 0; i < QCA8K_ACL_IPV6_MAX; i++) {
++ if (priv->offload->ipv6_lanip[i].valid)
++ continue;
++ priv->offload->ipv6_lanip[i].valid = 1;
++ priv->offload->ipv6_lanip[i].prefix_len = prefix_len;
++ priv->offload->ipv6_lanip[i].ip = ip;
++
++ qca8k_acl_write_route_v6(priv, i, ip.in6_u.u6_addr32,
++ prefix_len, LAN_MASK, -1, 0);
++ return;
++ }
++ qca8k_info(priv, " ipv6 lan ip table is full\n");
++}
++
++void
++qca8k_route_ip6_addr_del(struct qca8k_priv *priv, struct in6_addr ip,
++ int prefix_len)
++{
++ int i;
++
++ if (ipv6_addr_type(&ip) & IPV6_ADDR_LINKLOCAL)
++ return;
++
++ for (i = 0; i < QCA8K_ACL_IPV6_MAX; i++) {
++ if (!priv->offload->ipv6_lanip[i].valid)
++ continue;
++
++ if (memcmp(&ip, &priv->offload->ipv6_lanip[i].ip, sizeof(ip)))
++ continue;
++
++ priv->offload->ipv6_lanip[i].valid = 0;
++
++ qca8k_acl_flush_route_v6(priv, i);
++ return;
++ }
++ qca8k_info(priv, " trying to remove unknown ipv6 lan ip\n");
++}
++
++void
++qca8k_route_init(struct qca8k_priv *priv)
++{
++ priv->offload->ipv6_gateway_arp = -1;
++}
+Index: linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_thread.c
+===================================================================
+--- /dev/null
++++ linux-4.9.34/drivers/net/dsa/qca8k_offload/qca8k_thread.c
+@@ -0,0 +1,70 @@
++/*
++ * Copyright (c) 2016 John Crispin <john@phrozen.org>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/kthread.h>
++
++#include "qca8k.h"
++
++static int
++qca8k_thread(void *param)
++{
++ struct qca8k_priv *priv = param;
++ int tout = 0;
++
++ while (1) {
++ if (kthread_should_stop())
++ break;
++
++ tout++;
++
++ if (priv->offload->ipv4_gateway && (priv->offload->ipv4_gateway_arp < 0))
++ qca8k_fib_apply_route(priv);
++ if ((tout % QCA8K_ROUTE_TIMEOUT) == 0)
++ qca8k_route_ip6_worker(priv);
++ if ((tout % QCA8K_CT_SCAN_TIMEOUT) == 0)
++ qca8k_ct_scanner(priv);
++ if ((tout % QCA8K_CT_AGING_TIMEOUT) == 0)
++ qca8k_ct_ager(priv);
++ if ((tout % QCA8K_ARP_EXPIRE_TIMEOUT) == 0)
++ qca8k_arp_expire(priv);
++ msleep_interruptible(HZ);
++ }
++
++ return 0;
++}
++
++int
++qca8k_thread_start(struct qca8k_priv *priv)
++{
++ priv->offload->thread = kthread_create(qca8k_thread, priv, "qca8k_offload");
++
++ if (IS_ERR(priv->offload->thread)) {
++ int err = PTR_ERR(priv->offload->thread);
++
++ priv->offload->thread = NULL;
++ qca8k_info(priv, "failed to create kernel thread\n");
++
++ return err;
++ }
++
++ wake_up_process(priv->offload->thread);
++
++ return 0;
++}
++
++void
++qca8k_thread_stop(struct qca8k_priv *priv)
++{
++ if (priv->offload && priv->offload->thread)
++ kthread_stop(priv->offload->thread);
++}