From dd3bdac6d1dcd98d4d494052f7df31ca21558d6f Mon Sep 17 00:00:00 2001 From: John Crispin Date: Wed, 5 Jul 2017 10:13:33 +0200 Subject: [PATCH] ipq806x: convert to using qca8k Signed-off-by: John Crispin --- .../ipq806x/base-files/etc/board.d/02_network | 35 +- target/linux/ipq806x/config-4.9 | 3 - .../arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 98 +- .../arch/arm/boot/dts/qcom-ipq8064-c2600.dts | 98 +- .../arch/arm/boot/dts/qcom-ipq8064-d7800.dts | 104 +- .../arch/arm/boot/dts/qcom-ipq8064-db149.dts | 105 +- .../arch/arm/boot/dts/qcom-ipq8064-ea8500.dts | 98 +- .../arch/arm/boot/dts/qcom-ipq8064-r7500.dts | 98 +- .../arm/boot/dts/qcom-ipq8064-r7500v2.dts | 98 +- .../arm/boot/dts/qcom-ipq8064-vr2600v.dts | 98 +- .../arm/boot/dts/qcom-ipq8065-nbg6817.dts | 116 +- .../arch/arm/boot/dts/qcom-ipq8065-r7800.dts | 121 +- target/linux/ipq806x/modules.mk | 17 + ...-slave-device-inheret-the-MAC-of-the.patch | 44 + ...901-netfilter-qca8k-add-CT-extension.patch | 316 ++ .../902-net-dsa-add-destroy-callback.patch | 52 + ...sa-qca8k-properly-name-the-ARL-table.patch | 275 + ...qca8k-add-support-for-MDB-offloading.patch | 174 + ...sa-qca8k-add-port-arl-learning-limit.patch | 77 + .../patches-4.9/905-net-dsa-multi-cpu.patch | 247 + .../905-net-dsa-qca8k-add-destroy-hooks.patch | 57 + ...ca8k-allow-swapping-of-mac0-and-mac6.patch | 48 + ...k-add-support-for-multiple-cpu-ports.patch | 221 + ...8-net-dsa-qca8k-add-offloading-hooks.patch | 150 + ...9-net-dsa-qca8k-add-offloading-layer.patch | 4830 +++++++++++++++++ .../linux/ipq806x/patches-4.9/910-dts.patch | 117 + 26 files changed, 7498 insertions(+), 199 deletions(-) create mode 100644 target/linux/ipq806x/patches-4.9/900-net-dsa-make-the-slave-device-inheret-the-MAC-of-the.patch create mode 100644 target/linux/ipq806x/patches-4.9/901-netfilter-qca8k-add-CT-extension.patch create mode 100644 target/linux/ipq806x/patches-4.9/902-net-dsa-add-destroy-callback.patch create mode 100644 target/linux/ipq806x/patches-4.9/902-net-dsa-qca8k-properly-name-the-ARL-table.patch create mode 100644 target/linux/ipq806x/patches-4.9/903-net-dsa-qca8k-add-support-for-MDB-offloading.patch create mode 100644 target/linux/ipq806x/patches-4.9/904-net-dsa-qca8k-add-port-arl-learning-limit.patch create mode 100644 target/linux/ipq806x/patches-4.9/905-net-dsa-multi-cpu.patch create mode 100644 target/linux/ipq806x/patches-4.9/905-net-dsa-qca8k-add-destroy-hooks.patch create mode 100644 target/linux/ipq806x/patches-4.9/906-net-dsa-qca8k-allow-swapping-of-mac0-and-mac6.patch create mode 100644 target/linux/ipq806x/patches-4.9/907-net-dsa-qca8k-add-support-for-multiple-cpu-ports.patch create mode 100644 target/linux/ipq806x/patches-4.9/908-net-dsa-qca8k-add-offloading-hooks.patch create mode 100644 target/linux/ipq806x/patches-4.9/909-net-dsa-qca8k-add-offloading-layer.patch create mode 100644 target/linux/ipq806x/patches-4.9/910-dts.patch diff --git a/target/linux/ipq806x/base-files/etc/board.d/02_network b/target/linux/ipq806x/base-files/etc/board.d/02_network index bd81a1ebff77..1b06fa9408c1 100755 --- a/target/linux/ipq806x/base-files/etc/board.d/02_network +++ b/target/linux/ipq806x/base-files/etc/board.d/02_network @@ -14,25 +14,27 @@ board=$(ipq806x_board_name) case "$board" in ap148 |\ -c2600 |\ +db149) + ucidef_set_interface_lan "lan1 lan2 lan3 lan4" + ucidef_set_interface_wan "wan" + ;; d7800 |\ r7500 |\ r7500v2 |\ -r7800 |\ -vr2600v) - ucidef_add_switch "switch0" \ - "1:lan" "2:lan" "3:lan" "4:lan" "6@eth1" "5:wan" "0@eth0" +r7800) + ucidef_set_interface_lan "lan1 lan2 lan3 lan4" + ucidef_set_interface_wan "wan" + ucidef_set_interface_macaddr "lan" "$(mtd_get_mac_binary art 0)" ;; -db149) - ucidef_set_interface_lan "eth1 eth2 eth3" - ucidef_add_switch "switch0" \ - "1:lan" "2:lan" "3:lan" "4:lan" "6u@eth1" "5:wan" "0u@eth0" +c2600) + ucidef_set_interface_lan "lan1 lan2 lan3 lan4" + ucidef_set_interface_wan "wan" + ucidef_set_interface_macaddr "lan" "$(mtd_get_mac_binary default-mac 8)" ;; ea8500) - hw_mac_addr=$(mtd_get_mac_ascii devinfo hw_mac_addr) - ucidef_add_switch "switch0" \ - "0@eth0" "1:lan" "2:lan" "3:lan" "4:lan" "5:wan" + ucidef_set_interface_lan "lan1 lan2 lan3 lan4" + ucidef_set_interface_wan "wan" ucidef_set_interface_macaddr "lan" "$hw_mac_addr" ucidef_set_interface_macaddr "wan" "$hw_mac_addr" ;; @@ -43,11 +45,16 @@ fritz4040) ;; nbg6817) hw_mac_addr=$(mtd_get_mac_ascii 0:APPSBLENV ethaddr) - ucidef_add_switch "switch0" \ - "1:lan" "2:lan" "3:lan" "4:lan" "6@eth1" "5:wan" "0@eth0" + ucidef_set_interface_lan "lan1 lan2 lan3 lan4" + ucidef_set_interface_wan "wan" ucidef_set_interface_macaddr "lan" "$hw_mac_addr" ucidef_set_interface_macaddr "wan" "$(macaddr_add $hw_mac_addr 1)" ;; +vr2600v) + ucidef_set_interface_lan "lan1 lan2 lan3 lan4" + ucidef_set_interface_wan "wan" + ucidef_set_interface_macaddr "lan" "$(mtd_get_mac_binary default-mac 0)" + ;; *) echo "Unsupported hardware. Network interfaces not intialized" ;; diff --git a/target/linux/ipq806x/config-4.9 b/target/linux/ipq806x/config-4.9 index a2dd40272e66..09024cf1e71d 100644 --- a/target/linux/ipq806x/config-4.9 +++ b/target/linux/ipq806x/config-4.9 @@ -3,7 +3,6 @@ CONFIG_ALIGNMENT_TRAP=y CONFIG_APQ_GCC_8084=y CONFIG_APQ_MMCC_8084=y CONFIG_AR40XX_PHY=y -CONFIG_AR8216_PHY=y CONFIG_ARCH_CLOCKSOURCE_DATA=y CONFIG_ARCH_HAS_ELF_RANDOMIZE=y CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y @@ -458,8 +457,6 @@ CONFIG_SPMI_MSM_PMIC_ARB=y CONFIG_SRCU=y CONFIG_STMMAC_ETH=y CONFIG_STMMAC_PLATFORM=y -CONFIG_SWCONFIG=y -CONFIG_SWCONFIG_LEDS=y CONFIG_SWIOTLB=y CONFIG_SWPHY=y CONFIG_SWP_EMULATE=y diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ap148.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ap148.dts index fa4f05bdbeb4..e1466ae335b0 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ap148.dts @@ -195,24 +195,96 @@ pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; + phy_port1: phy@0 { reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0x6a545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - >; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; + phy_port2: phy@1 { + reg = <1>; + }; + + phy_port3: phy@2 { + reg = <2>; + }; + + phy_port4: phy@3 { + reg = <3>; + }; + + phy_port5: phy@4 { reg = <4>; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan1"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan2"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan3"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan4"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac1: ethernet@37200000 { diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-c2600.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-c2600.dts index 80bc5dfa0467..e7c07f7b7b57 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-c2600.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-c2600.dts @@ -362,24 +362,96 @@ pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; + phy_port1: phy@0 { reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0x6a545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - >; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; + phy_port2: phy@1 { + reg = <1>; + }; + + phy_port3: phy@2 { + reg = <2>; + }; + + phy_port4: phy@3 { + reg = <3>; + }; + + phy_port5: phy@4 { reg = <4>; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan1"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan2"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan3"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan4"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac1: ethernet@37200000 { diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-d7800.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-d7800.dts index c1a4c82a285e..b89ec17c4a8a 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-d7800.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-d7800.dts @@ -285,36 +285,112 @@ pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; + phy_port1: phy@0 { reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0x6a545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - >; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; + phy_port2: phy@1 { + reg = <1>; + }; + + phy_port3: phy@2 { + reg = <2>; + }; + + phy_port4: phy@3 { + reg = <3>; + }; + + phy_port5: phy@4 { reg = <4>; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan4"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan3"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan2"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan1"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac1: ethernet@37200000 { status = "ok"; phy-mode = "rgmii"; - phy-handle = <&phy4>; qcom,id = <1>; pinctrl-0 = <&rgmii2_pins>; pinctrl-names = "default"; mtd-mac-address = <&art 6>; + + fixed-link { + speed = <1000>; + full-duplex; + }; }; gmac2: ethernet@37400000 { diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-db149.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-db149.dts index 4c56866077c0..120978106088 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-db149.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-db149.dts @@ -168,41 +168,102 @@ pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; + phy_port1: phy@0 { reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0x6a545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - >; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; - reg = <4>; + phy_port2: phy@1 { + reg = <1>; + }; + + phy_port3: phy@2 { + reg = <2>; }; - phy6: ethernet-phy@6 { - device_type = "ethernet-phy"; - reg = <6>; + phy_port4: phy@3 { + reg = <3>; }; - phy7: ethernet-phy@7 { - device_type = "ethernet-phy"; - reg = <7>; + phy_port5: phy@4 { + reg = <4>; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan4"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan3"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan2"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan1"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac0: ethernet@37000000 { status = "ok"; phy-mode = "rgmii"; qcom,id = <0>; - phy-handle = <&phy4>; pinctrl-0 = <&rgmii0_pins>; pinctrl-names = "default"; @@ -223,14 +284,12 @@ status = "ok"; phy-mode = "sgmii"; qcom,id = <2>; - phy-handle = <&phy6>; }; gmac3: ethernet@37600000 { status = "ok"; phy-mode = "sgmii"; qcom,id = <3>; - phy-handle = <&phy7>; }; }; }; diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ea8500.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ea8500.dts index 761fa43179f6..9db70f3d9471 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ea8500.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ea8500.dts @@ -300,24 +300,96 @@ pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; + phy_port1: phy@0 { reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0x6a545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - >; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; + phy_port2: phy@1 { + reg = <1>; + }; + + phy_port3: phy@2 { + reg = <2>; + }; + + phy_port4: phy@3 { + reg = <3>; + }; + + phy_port5: phy@4 { reg = <4>; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan1"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan2"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan3"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan4"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac1: ethernet@37200000 { diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500.dts index 2ea856d88b5b..0c8f00d90b7e 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500.dts @@ -251,24 +251,96 @@ pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; + phy_port1: phy@0 { reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0x6a545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - >; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; + phy_port2: phy@1 { + reg = <1>; + }; + + phy_port3: phy@2 { + reg = <2>; + }; + + phy_port4: phy@3 { + reg = <3>; + }; + + phy_port5: phy@4 { reg = <4>; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan4"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan3"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan2"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan1"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac1: ethernet@37200000 { diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500v2.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500v2.dts index a21cf18bee6d..badbc8c4e80c 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500v2.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500v2.dts @@ -287,24 +287,96 @@ pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; + phy_port1: phy@0 { reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0xaa545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - >; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; + phy_port2: phy@1 { + reg = <1>; + }; + + phy_port3: phy@2 { + reg = <2>; + }; + + phy_port4: phy@3 { + reg = <3>; + }; + + phy_port5: phy@4 { reg = <4>; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan4"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan3"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan2"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan1"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac1: ethernet@37200000 { diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-vr2600v.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-vr2600v.dts index b55a98d229c0..8c7ce197e336 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-vr2600v.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-vr2600v.dts @@ -275,24 +275,96 @@ pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; + phy_port1: phy@0 { reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0x6a545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - >; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; + phy_port2: phy@1 { + reg = <1>; + }; + + phy_port3: phy@2 { + reg = <2>; + }; + + phy_port4: phy@3 { + reg = <3>; + }; + + phy_port5: phy@4 { reg = <4>; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan4"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan3"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan2"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan1"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac1: ethernet@37200000 { diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-nbg6817.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-nbg6817.dts index 5fe14da26816..f1609e638c23 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-nbg6817.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-nbg6817.dts @@ -238,42 +238,96 @@ pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; + phy_port1: phy@0 { reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0xaa545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - 0x00970 0x1e864443 /* QM_PORT0_CTRL0 */ - 0x00974 0x000001c6 /* QM_PORT0_CTRL1 */ - 0x00978 0x19008643 /* QM_PORT1_CTRL0 */ - 0x0097c 0x000001c6 /* QM_PORT1_CTRL1 */ - 0x00980 0x19008643 /* QM_PORT2_CTRL0 */ - 0x00984 0x000001c6 /* QM_PORT2_CTRL1 */ - 0x00988 0x19008643 /* QM_PORT3_CTRL0 */ - 0x0098c 0x000001c6 /* QM_PORT3_CTRL1 */ - 0x00990 0x19008643 /* QM_PORT4_CTRL0 */ - 0x00994 0x000001c6 /* QM_PORT4_CTRL1 */ - 0x00998 0x1e864443 /* QM_PORT5_CTRL0 */ - 0x0099c 0x000001c6 /* QM_PORT5_CTRL1 */ - 0x009a0 0x1e864443 /* QM_PORT6_CTRL0 */ - 0x009a4 0x000001c6 /* QM_PORT6_CTRL1 */ - >; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; + phy_port2: phy@1 { + reg = <1>; + }; + + phy_port3: phy@2 { + reg = <2>; + }; + + phy_port4: phy@3 { + reg = <3>; + }; + + phy_port5: phy@4 { reg = <4>; - qca,ar8327-initvals = < - 0x000e4 0x6a545 /* MAC_POWER_SEL */ - 0x0000c 0x80 /* PAD6_MODE */ - >; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan1"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan2"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan3"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan4"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac1: ethernet@37200000 { diff --git a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-r7800.dts b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-r7800.dts index 403054cc97fe..36987474a1e0 100644 --- a/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-r7800.dts +++ b/target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-r7800.dts @@ -393,48 +393,97 @@ gpios = <&qcom_pinmux 1 GPIO_ACTIVE_HIGH &qcom_pinmux 0 GPIO_ACTIVE_HIGH>; pinctrl-0 = <&mdio0_pins>; pinctrl-names = "default"; + + phy_port1: phy@0 { + reg = <0>; + }; + + phy_port2: phy@1 { + reg = <1>; + }; + phy_port3: phy@2 { + reg = <2>; + }; - phy0: ethernet-phy@0 { - device_type = "ethernet-phy"; - reg = <0>; - qca,ar8327-initvals = < - 0x00004 0x7600000 /* PAD0_MODE */ - 0x00008 0x1000000 /* PAD5_MODE */ - 0x0000c 0x80 /* PAD6_MODE */ - 0x000e4 0xaa545 /* MAC_POWER_SEL */ - 0x000e0 0xc74164de /* SGMII_CTRL */ - 0x0007c 0x4e /* PORT0_STATUS */ - 0x00094 0x4e /* PORT6_STATUS */ - 0x00970 0x1e864443 /* QM_PORT0_CTRL0 */ - 0x00974 0x000001c6 /* QM_PORT0_CTRL1 */ - 0x00978 0x19008643 /* QM_PORT1_CTRL0 */ - 0x0097c 0x000001c6 /* QM_PORT1_CTRL1 */ - 0x00980 0x19008643 /* QM_PORT2_CTRL0 */ - 0x00984 0x000001c6 /* QM_PORT2_CTRL1 */ - 0x00988 0x19008643 /* QM_PORT3_CTRL0 */ - 0x0098c 0x000001c6 /* QM_PORT3_CTRL1 */ - 0x00990 0x19008643 /* QM_PORT4_CTRL0 */ - 0x00994 0x000001c6 /* QM_PORT4_CTRL1 */ - 0x00998 0x1e864443 /* QM_PORT5_CTRL0 */ - 0x0099c 0x000001c6 /* QM_PORT5_CTRL1 */ - 0x009a0 0x1e864443 /* QM_PORT6_CTRL0 */ - 0x009a4 0x000001c6 /* QM_PORT6_CTRL1 */ - >; - qca,ar8327-vlans = < - 0x1 0x5e /* VLAN1 Ports 1/2/3/4/6 */ - 0x2 0x21 /* VLAN2 Ports 0/5 */ - >; + phy_port4: phy@3 { + reg = <3>; }; - phy4: ethernet-phy@4 { - device_type = "ethernet-phy"; + phy_port5: phy@4 { reg = <4>; - qca,ar8327-initvals = < - 0x000e4 0x6a545 /* MAC_POWER_SEL */ - 0x0000c 0x80 /* PAD6_MODE */ - >; }; + + switch0@16 { + compatible = "qca,qca8337"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <16>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@1 { + reg = <1>; + label = "lan4"; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan3"; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan2"; + phy-handle = <&phy_port3>; + }; + + port@4 { + reg = <4>; + label = "lan1"; + phy-handle = <&phy_port4>; + }; + + port@5 { + reg = <5>; + label = "wan"; + phy-handle = <&phy_port5>; + }; + + /* + * Disabled until DSA supports multiple CPUs, + * otherwise it causes undefined behavior. + * + * port@6 { + * reg = <6>; + * label = "cpu"; + * ethernet = <&gmac2>; + * phy-mode = "sgmii"; + * + * fixed-link { + * speed = <1000>; + * full-duplex; + * }; + * }; + */ + }; + }; }; gmac1: ethernet@37200000 { diff --git a/target/linux/ipq806x/modules.mk b/target/linux/ipq806x/modules.mk index 6f1ca2d2a438..f6a2ac438964 100644 --- a/target/linux/ipq806x/modules.mk +++ b/target/linux/ipq806x/modules.mk @@ -29,3 +29,20 @@ define KernelPackage/usb-phy-qcom-dwc3/description endef $(eval $(call KernelPackage,usb-phy-qcom-dwc3)) + +define KernelPackage/qca8k-offload + TITLE:=QCA8337 HW NAT offloading driver + SUBMENU:=Network Devices + DEPENDS:=@TARGET_ipq806x +kmod-nf-conntrack + KCONFIG:= \ + CONFIG_NET_DSA_QCA8K_OFFLOAD \ + CONFIG_NF_CONNTRACK_QCA8K + FILES:= $(LINUX_DIR)/drivers/net/dsa/qca8k_offload/qca8k_offload.ko + AUTOLOAD:=$(call AutoProbe,qca8k_offload) +endef + +define KernelPackage/qca8k-offload/description + This driver provides QCA8337 offloading support +endef + +$(eval $(call KernelPackage,qca8k-offload)) diff --git a/target/linux/ipq806x/patches-4.9/900-net-dsa-make-the-slave-device-inheret-the-MAC-of-the.patch b/target/linux/ipq806x/patches-4.9/900-net-dsa-make-the-slave-device-inheret-the-MAC-of-the.patch new file mode 100644 index 000000000000..d16a94931e20 --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/900-net-dsa-make-the-slave-device-inheret-the-MAC-of-the.patch @@ -0,0 +1,44 @@ +From 171b14b660f35f593748ac62bbdbb43ace2c582d Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Tue, 1 Nov 2016 01:44:15 +0100 +Subject: [PATCH 10/22] net: dsa: make the slave device inheret the MAC of the + parent + +This patch makes all slave devices inherit the parent devices MAC. + +Signed-off-by: John Crispin +--- + net/dsa/slave.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +Index: linux-4.9.34/net/dsa/slave.c +=================================================================== +--- linux-4.9.34.orig/net/dsa/slave.c ++++ linux-4.9.34/net/dsa/slave.c +@@ -175,6 +175,18 @@ static int dsa_slave_close(struct net_de + return 0; + } + ++static int dsa_slave_init(struct net_device *dev) ++{ ++ struct dsa_slave_priv *p = netdev_priv(dev); ++ struct net_device *master = p->master; ++ struct sockaddr sa; ++ ++ ether_addr_copy(sa.sa_data, master->dev_addr); ++ eth_mac_addr(dev, &sa); ++ ++ return 0; ++} ++ + static void dsa_slave_change_rx_flags(struct net_device *dev, int change) + { + struct dsa_slave_priv *p = netdev_priv(dev); +@@ -1012,6 +1024,7 @@ static const struct ethtool_ops dsa_slav + static const struct net_device_ops dsa_slave_netdev_ops = { + .ndo_open = dsa_slave_open, + .ndo_stop = dsa_slave_close, ++ .ndo_init = dsa_slave_init, + .ndo_start_xmit = dsa_slave_xmit, + .ndo_change_rx_flags = dsa_slave_change_rx_flags, + .ndo_set_rx_mode = dsa_slave_set_rx_mode, diff --git a/target/linux/ipq806x/patches-4.9/901-netfilter-qca8k-add-CT-extension.patch b/target/linux/ipq806x/patches-4.9/901-netfilter-qca8k-add-CT-extension.patch new file mode 100644 index 000000000000..257eca289167 --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/901-netfilter-qca8k-add-CT-extension.patch @@ -0,0 +1,316 @@ +From 8541425fb399861a3c92f4a02f16f98e7e6aa47a Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Tue, 1 Nov 2016 01:55:27 +0100 +Subject: [PATCH 05/22] netfilter: qca8k: add CT extension + +this allows us to track our offloaded state inside the actual conntrack +entry. + +Signed-off-by: John Crispin +--- + include/net/netfilter/nf_conntrack_extend.h | 4 ++ + include/net/netfilter/nf_conntrack_qca8k.h | 70 +++++++++++++++++++++++++++ + net/netfilter/Kconfig | 11 +++++ + net/netfilter/Makefile | 3 ++ + net/netfilter/nf_conntrack_core.c | 15 ++++++ + net/netfilter/nf_conntrack_proto_tcp.c | 7 ++- + net/netfilter/nf_conntrack_qca8k.c | 58 ++++++++++++++++++++++ + 7 files changed, 167 insertions(+), 1 deletion(-) + create mode 100644 include/net/netfilter/nf_conntrack_qca8k.h + create mode 100644 net/netfilter/nf_conntrack_qca8k.c + +Index: linux-4.9.34/include/net/netfilter/nf_conntrack_extend.h +=================================================================== +--- linux-4.9.34.orig/include/net/netfilter/nf_conntrack_extend.h ++++ linux-4.9.34/include/net/netfilter/nf_conntrack_extend.h +@@ -30,6 +30,9 @@ enum nf_ct_ext_id { + #if IS_ENABLED(CONFIG_NF_CONNTRACK_RTCACHE) + NF_CT_EXT_RTCACHE, + #endif ++#if IS_ENABLED(CONFIG_NF_CONNTRACK_QCA8K) ++ NF_CT_EXT_QCA8K, ++#endif + NF_CT_EXT_NUM, + }; + +@@ -43,6 +46,7 @@ enum nf_ct_ext_id { + #define NF_CT_EXT_LABELS_TYPE struct nf_conn_labels + #define NF_CT_EXT_SYNPROXY_TYPE struct nf_conn_synproxy + #define NF_CT_EXT_RTCACHE_TYPE struct nf_conn_rtcache ++#define NF_CT_EXT_QCA8K_TYPE struct nf_conn_qca8k + + /* Extensions: optional stuff which isn't permanently in struct. */ + struct nf_ct_ext { +Index: linux-4.9.34/include/net/netfilter/nf_conntrack_qca8k.h +=================================================================== +--- /dev/null ++++ linux-4.9.34/include/net/netfilter/nf_conntrack_qca8k.h +@@ -0,0 +1,70 @@ ++#ifndef _NF_CONNTRACK_QCA8K_H ++#define _NF_CONNTRACK_QCA8K_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++struct qca8k_priv; ++ ++struct nf_conn_qca8k { ++ struct nf_conn *ct; ++ int idx; ++ u64 counter; ++ int fail; ++ struct qca8k_priv *priv; ++}; ++ ++static inline ++struct nf_conn_qca8k *nf_ct_qca8k_find(const struct nf_conn *ct) ++{ ++#if defined(CONFIG_NF_CONNTRACK_QCA8K) || defined(CONFIG_NF_CONNTRACK_QCA8K_MODULE) ++ return nf_ct_ext_find(ct, NF_CT_EXT_QCA8K); ++#else ++ return NULL; ++#endif ++} ++ ++static inline ++struct nf_conn_qca8k *nf_ct_qca8k_ext_add(struct nf_conn *ct, ++ gfp_t gfp) ++{ ++#if defined(CONFIG_NF_CONNTRACK_QCA8K) || defined(CONFIG_NF_CONNTRACK_QCA8K_MODULE) ++ struct nf_conn_qca8k *qca8k_ext; ++ ++ qca8k_ext = nf_ct_ext_add(ct, NF_CT_EXT_QCA8K, gfp); ++ if (qca8k_ext == NULL) ++ return NULL; ++ ++ qca8k_ext->idx = -1; ++ qca8k_ext->ct = ct; ++ qca8k_ext->counter = qca8k_ext->fail = 0; ++ ++ return qca8k_ext; ++#else ++ return NULL; ++#endif ++}; ++ ++#if defined(CONFIG_NF_CONNTRACK_QCA8K) || defined(CONFIG_NF_CONNTRACK_QCA8K_MODULE) ++int nf_conntrack_qca8k_init(void); ++void nf_conntrack_qca8k_fini(void); ++#else ++static inline int nf_conntrack_qca8k_init(void) ++{ ++ return 0; ++} ++ ++static inline void nf_conntrack_qca8k_fini(void) ++{ ++ return; ++} ++#endif /* CONFIG_NF_CONNTRACK_QCA8K */ ++ ++#if defined(CONFIG_NF_CONNTRACK_QCA8K) || defined(CONFIG_NF_CONNTRACK_QCA8K_MODULE) ++extern void (*nf_ct_qca8k_destroy)(struct nf_conn *ct, struct nf_conn_qca8k *conn); ++#endif ++ ++#endif /* _NF_CONNTRACK_QCA8K_H */ +Index: linux-4.9.34/net/netfilter/Kconfig +=================================================================== +--- linux-4.9.34.orig/net/netfilter/Kconfig ++++ linux-4.9.34/net/netfilter/Kconfig +@@ -147,6 +147,17 @@ config NF_CONNTRACK_TIMESTAMP + + If unsure, say `N'. + ++config NF_CONNTRACK_QCA8K ++ tristate "QCA8K offload entries in conntrack objectt" ++ depends on NETFILTER_ADVANCED ++ depends on NF_CONNTRACK ++ help ++ If this option is enabled, the connection tracking code will ++ be able to track connections offloaded to the QCA8K switch core ++ ++ To compile it as a module, choose M here. If unsure, say N. ++ The module will be called nf_conntrack_rtcache. ++ + config NF_CONNTRACK_LABELS + bool + help +Index: linux-4.9.34/net/netfilter/Makefile +=================================================================== +--- linux-4.9.34.orig/net/netfilter/Makefile ++++ linux-4.9.34/net/netfilter/Makefile +@@ -1,6 +1,7 @@ + netfilter-objs := core.o nf_log.o nf_queue.o nf_sockopt.o + +-nf_conntrack-y := nf_conntrack_core.o nf_conntrack_standalone.o nf_conntrack_expect.o nf_conntrack_helper.o nf_conntrack_proto.o nf_conntrack_l3proto_generic.o nf_conntrack_proto_generic.o nf_conntrack_proto_tcp.o nf_conntrack_proto_udp.o nf_conntrack_extend.o nf_conntrack_acct.o nf_conntrack_seqadj.o ++nf_conntrack-y := nf_conntrack_core.o nf_conntrack_standalone.o nf_conntrack_expect.o nf_conntrack_helper.o nf_conntrack_proto.o nf_conntrack_l3proto_generic.o nf_conntrack_proto_generic.o nf_conntrack_proto_tcp.o nf_conntrack_proto_udp.o nf_conntrack_extend.o nf_conntrack_acct.o nf_conntrack_seqadj.o nf_conntrack_qca8k.o ++ + nf_conntrack-$(CONFIG_NF_CONNTRACK_TIMEOUT) += nf_conntrack_timeout.o + nf_conntrack-$(CONFIG_NF_CONNTRACK_TIMESTAMP) += nf_conntrack_timestamp.o + nf_conntrack-$(CONFIG_NF_CONNTRACK_EVENTS) += nf_conntrack_ecache.o +@@ -19,6 +20,7 @@ obj-$(CONFIG_NF_CONNTRACK) += nf_conntra + # optional conntrack route cache extension + obj-$(CONFIG_NF_CONNTRACK_RTCACHE) += nf_conntrack_rtcache.o + ++ + # SCTP protocol connection tracking + obj-$(CONFIG_NF_CT_PROTO_DCCP) += nf_conntrack_proto_dccp.o + obj-$(CONFIG_NF_CT_PROTO_GRE) += nf_conntrack_proto_gre.o +Index: linux-4.9.34/net/netfilter/nf_conntrack_core.c +=================================================================== +--- linux-4.9.34.orig/net/netfilter/nf_conntrack_core.c ++++ linux-4.9.34/net/netfilter/nf_conntrack_core.c +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1153,6 +1154,8 @@ init_conntrack(struct net *net, struct n + timeouts = l4proto->get_timeouts(net); + } + ++ nf_ct_qca8k_ext_add(ct, GFP_ATOMIC); ++ + if (!l4proto->new(ct, skb, dataoff, timeouts)) { + nf_conntrack_free(ct); + pr_debug("can't track with proto module\n"); +@@ -1591,6 +1594,12 @@ void nf_ct_iterate_cleanup(struct net *n + return; + + while ((ct = get_next_corpse(net, iter, data, &bucket)) != NULL) { ++ struct nf_conn_qca8k *qca8k_ext; ++ ++ qca8k_ext = nf_ct_qca8k_find(ct); ++ if (qca8k_ext && qca8k_ext->idx >= 0) ++ continue; ++ + /* Time to push up daises... */ + + nf_ct_delete(ct, portid, report); +@@ -1906,6 +1915,10 @@ int nf_conntrack_init_start(void) + if (ret < 0) + goto err_proto; + ++ ret = nf_conntrack_qca8k_init(); ++ if (ret < 0) ++ goto err_qca8k; ++ + /* Set up fake conntrack: to never be deleted, not in any hashes */ + for_each_possible_cpu(cpu) { + struct nf_conn *ct = &per_cpu(nf_conntrack_untracked, cpu); +@@ -1922,6 +1935,8 @@ int nf_conntrack_init_start(void) + + err_proto: + nf_conntrack_seqadj_fini(); ++err_qca8k: ++ nf_conntrack_qca8k_fini(); + err_seqadj: + nf_conntrack_labels_fini(); + err_labels: +Index: linux-4.9.34/net/netfilter/nf_conntrack_proto_tcp.c +=================================================================== +--- linux-4.9.34.orig/net/netfilter/nf_conntrack_proto_tcp.c ++++ linux-4.9.34/net/netfilter/nf_conntrack_proto_tcp.c +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -823,6 +824,7 @@ static int tcp_packet(struct nf_conn *ct + { + struct net *net = nf_ct_net(ct); + struct nf_tcp_net *tn = tcp_pernet(net); ++ struct nf_conn_qca8k *qca8k_ext; + struct nf_conntrack_tuple *tuple; + enum tcp_conntrack new_state, old_state; + enum ip_conntrack_dir dir; +@@ -1037,7 +1039,10 @@ static int tcp_packet(struct nf_conn *ct + break; + } + +- if (!tcp_in_window(ct, &ct->proto.tcp, dir, index, ++ qca8k_ext = nf_ct_qca8k_find(ct); ++// if (qca8k_ext) ++// printk("%s:%s[%d]%d\n", __FILE__, __func__, __LINE__, qca8k_ext->idx); ++ if ((!qca8k_ext || (qca8k_ext->idx < 0)) && !tcp_in_window(ct, &ct->proto.tcp, dir, index, + skb, dataoff, th, pf)) { + spin_unlock_bh(&ct->lock); + return -NF_ACCEPT; +Index: linux-4.9.34/net/netfilter/nf_conntrack_qca8k.c +=================================================================== +--- /dev/null ++++ linux-4.9.34/net/netfilter/nf_conntrack_qca8k.c +@@ -0,0 +1,64 @@ ++/* ++ * (C) 2016 John Crispin ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation (or any later at your option). ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++void (*nf_ct_qca8k_destroy)(struct nf_conn *ct, struct nf_conn_qca8k *conn) __read_mostly; ++EXPORT_SYMBOL_GPL(nf_ct_qca8k_destroy); ++ ++static void nf_conn_qca8k_destroy(struct nf_conn *ct) ++{ ++ struct nf_conn_qca8k *conn = nf_ct_qca8k_find(ct); ++ ++ if (!conn || !nf_ct_qca8k_destroy) ++ return; ++ ++ nf_ct_qca8k_destroy(ct, conn); ++} ++ ++static struct nf_ct_ext_type qca8k_extend __read_mostly = { ++ .len = sizeof(struct nf_conn_qca8k), ++ .align = __alignof__(struct nf_conn_qca8k), ++ .id = NF_CT_EXT_QCA8K, ++ .destroy = nf_conn_qca8k_destroy, ++}; ++ ++int nf_conntrack_qca8k_init(void) ++{ ++ int ret = nf_ct_extend_register(&qca8k_extend); ++ if (ret < 0) ++ pr_err("nf_ct_qca8k: Unable to register qca8k extension.\n"); ++ nf_ct_qca8k_destroy = NULL; ++ return ret; ++} ++EXPORT_SYMBOL_GPL(nf_conntrack_qca8k_init); ++ ++void nf_conntrack_qca8k_fini(void) ++{ ++ nf_ct_extend_unregister(&qca8k_extend); ++} ++EXPORT_SYMBOL_GPL(nf_conntrack_qca8k_fini); ++ ++MODULE_DESCRIPTION("Qualcomm switch offloading driver"); ++MODULE_AUTHOR("John Crispin +Date: Tue, 1 Nov 2016 01:43:07 +0100 +Subject: [PATCH 09/22] net: dsa: add destroy callback + +propagate the shutdown/destroy event to the swith driver. This allows QCA8k +to do an ordered shutdown upon reboot. + +Signed-off-by: John Crispin +--- + include/net/dsa.h | 5 +++++ + net/dsa/dsa.c | 4 ++++ + 2 files changed, 9 insertions(+) + +Index: linux-4.9.34/include/net/dsa.h +=================================================================== +--- linux-4.9.34.orig/include/net/dsa.h ++++ linux-4.9.34/include/net/dsa.h +@@ -256,6 +256,11 @@ struct dsa_switch_ops { + u32 (*get_phy_flags)(struct dsa_switch *ds, int port); + + /* ++ * Shutdown ++ */ ++ void (*destroy)(struct dsa_switch *ds); ++ ++ /* + * Access to the switch's PHY registers. + */ + int (*phy_read)(struct dsa_switch *ds, int port, int regnum); +Index: linux-4.9.34/net/dsa/dsa.c +=================================================================== +--- linux-4.9.34.orig/net/dsa/dsa.c ++++ linux-4.9.34/net/dsa/dsa.c +@@ -519,6 +519,9 @@ static void dsa_switch_destroy(struct ds + hwmon_device_unregister(ds->hwmon_dev); + #endif + ++ if (ds->ops->destroy) ++ ds->ops->destroy(ds); ++ + /* Destroy network devices for physical switch ports. */ + for (port = 0; port < DSA_MAX_PORTS; port++) { + if (!(ds->enabled_port_mask & (1 << port))) +@@ -1030,6 +1033,7 @@ static int dsa_remove(struct platform_de + + static void dsa_shutdown(struct platform_device *pdev) + { ++ dsa_remove(pdev); + } + + static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev, diff --git a/target/linux/ipq806x/patches-4.9/902-net-dsa-qca8k-properly-name-the-ARL-table.patch b/target/linux/ipq806x/patches-4.9/902-net-dsa-qca8k-properly-name-the-ARL-table.patch new file mode 100644 index 000000000000..eb43c9e4b73d --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/902-net-dsa-qca8k-properly-name-the-ARL-table.patch @@ -0,0 +1,275 @@ +From fb216c26cd0f7136e2ddfe136001f7fee6704329 Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Tue, 1 Nov 2016 01:39:09 +0100 +Subject: [PATCH 14/22] net: dsa: qca8k: properly name the ARL table + +FDB and MDB entries both get stored inside the ARL table. Currently the +code uses "fdb_" as a namespace for the functions adding and deleting +entries. Change the code to use "arl_" as a namespace instead. + +Signed-off-by: John Crispin +--- + drivers/net/dsa/qca8k.c | 83 +++++++++++++++++++++++++---------------------- + drivers/net/dsa/qca8k.h | 22 ++++++++----- + 2 files changed, 59 insertions(+), 46 deletions(-) + +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 +@@ -287,7 +287,7 @@ qca8k_busy_wait(struct qca8k_priv *priv, + } + + static void +-qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb) ++qca8k_arl_read(struct qca8k_priv *priv, struct qca8k_arl *arl) + { + u32 reg[4]; + int i; +@@ -297,22 +297,26 @@ qca8k_fdb_read(struct qca8k_priv *priv, + reg[i] = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4)); + + /* vid - 83:72 */ +- fdb->vid = (reg[2] >> QCA8K_ATU_VID_S) & QCA8K_ATU_VID_M; ++ arl->vid = (reg[2] >> QCA8K_ATU_VID_S) & QCA8K_ATU_VID_M; + /* aging - 67:64 */ +- fdb->aging = reg[2] & QCA8K_ATU_STATUS_M; ++ arl->aging = reg[2] & QCA8K_ATU_STATUS_M; + /* portmask - 54:48 */ +- fdb->port_mask = (reg[1] >> QCA8K_ATU_PORT_S) & QCA8K_ATU_PORT_M; ++ arl->port_mask = (reg[1] >> QCA8K_ATU_PORT_S) & QCA8K_ATU_PORT_M; + /* mac - 47:0 */ +- fdb->mac[0] = (reg[1] >> QCA8K_ATU_ADDR0_S) & 0xff; +- fdb->mac[1] = reg[1] & 0xff; +- fdb->mac[2] = (reg[0] >> QCA8K_ATU_ADDR2_S) & 0xff; +- fdb->mac[3] = (reg[0] >> QCA8K_ATU_ADDR3_S) & 0xff; +- fdb->mac[4] = (reg[0] >> QCA8K_ATU_ADDR4_S) & 0xff; +- fdb->mac[5] = reg[0] & 0xff; ++ arl->mac[0] = (reg[1] >> QCA8K_ATU_ADDR0_S) & 0xff; ++ arl->mac[1] = reg[1] & 0xff; ++ arl->mac[2] = (reg[0] >> QCA8K_ATU_ADDR2_S) & 0xff; ++ arl->mac[3] = (reg[0] >> QCA8K_ATU_ADDR3_S) & 0xff; ++ arl->mac[4] = (reg[0] >> QCA8K_ATU_ADDR4_S) & 0xff; ++ arl->mac[5] = reg[0] & 0xff; ++ ++ /* is this a FDB or MDB entry ? */ ++ if (!is_multicast_ether_addr(arl->mac)) ++ arl->type = QCA8K_ARL_MDB; + } + + static void +-qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac, ++qca8k_arl_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac, + u8 aging) + { + u32 reg[3] = { 0 }; +@@ -338,7 +342,7 @@ qca8k_fdb_write(struct qca8k_priv *priv, + } + + static int +-qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port) ++qca8k_arl_access(struct qca8k_priv *priv, enum qca8k_arl_cmd cmd, int port) + { + u32 reg; + +@@ -358,7 +362,7 @@ qca8k_fdb_access(struct qca8k_priv *priv + return -1; + + /* Check for table full violation when adding an entry */ +- if (cmd == QCA8K_FDB_LOAD) { ++ if (cmd == QCA8K_ARL_LOAD) { + reg = qca8k_read(priv, QCA8K_REG_ATU_FUNC); + if (reg & QCA8K_ATU_FUNC_FULL) + return -1; +@@ -368,50 +372,50 @@ qca8k_fdb_access(struct qca8k_priv *priv + } + + static int +-qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port) ++qca8k_arl_next(struct qca8k_priv *priv, struct qca8k_arl *arl, int port) + { + int ret; + +- qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging); +- ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port); ++ qca8k_arl_write(priv, arl->vid, arl->port_mask, arl->mac, arl->aging); ++ ret = qca8k_arl_access(priv, QCA8K_ARL_NEXT, port); + if (ret >= 0) +- qca8k_fdb_read(priv, fdb); ++ qca8k_arl_read(priv, arl); + + return ret; + } + + static int +-qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, ++qca8k_arl_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, + u16 vid, u8 aging) + { + int ret; + + mutex_lock(&priv->reg_mutex); +- qca8k_fdb_write(priv, vid, port_mask, mac, aging); +- ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); ++ qca8k_arl_write(priv, vid, port_mask, mac, aging); ++ ret = qca8k_arl_access(priv, QCA8K_ARL_LOAD, -1); + mutex_unlock(&priv->reg_mutex); + + return ret; + } + + static int +-qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid) ++qca8k_arl_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid) + { + int ret; + + mutex_lock(&priv->reg_mutex); +- qca8k_fdb_write(priv, vid, port_mask, mac, 0); +- ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); ++ qca8k_arl_write(priv, vid, port_mask, mac, 0); ++ ret = qca8k_arl_access(priv, QCA8K_ARL_PURGE, -1); + mutex_unlock(&priv->reg_mutex); + + return ret; + } + + static void +-qca8k_fdb_flush(struct qca8k_priv *priv) ++qca8k_arl_flush(struct qca8k_priv *priv) + { + mutex_lock(&priv->reg_mutex); +- qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1); ++ qca8k_arl_access(priv, QCA8K_ARL_FLUSH, -1); + mutex_unlock(&priv->reg_mutex); + } + +@@ -580,7 +584,7 @@ qca8k_setup(struct dsa_switch *ds) + } + + /* Flush the FDB table */ +- qca8k_fdb_flush(priv); ++ qca8k_arl_flush(priv); + + return 0; + } +@@ -822,14 +826,14 @@ qca8k_port_disable(struct dsa_switch *ds + } + + static int +-qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr, ++qca8k_port_arl_insert(struct qca8k_priv *priv, const u8 *addr, + u16 port_mask, u16 vid) + { + /* Set the vid to the port vlan id if no vid is set */ + if (!vid) + vid = 1; + +- return qca8k_fdb_add(priv, addr, port_mask, vid, ++ return qca8k_arl_add(priv, addr, port_mask, vid, + QCA8K_ATU_STATUS_STATIC); + } + +@@ -845,7 +849,7 @@ qca8k_port_fdb_prepare(struct dsa_switch + * when port_fdb_add is called an entry is still available. Otherwise + * the last free entry might have been used up by auto learning + */ +- return qca8k_port_fdb_insert(priv, fdb->addr, 0, fdb->vid); ++ return qca8k_port_arl_insert(priv, fdb->addr, 0, fdb->vid); + } + + static void +@@ -857,7 +861,7 @@ qca8k_port_fdb_add(struct dsa_switch *ds + u16 port_mask = BIT(port); + + /* Update the FDB entry adding the port_mask */ +- qca8k_port_fdb_insert(priv, fdb->addr, port_mask, fdb->vid); ++ qca8k_port_arl_insert(priv, fdb->addr, port_mask, fdb->vid); + } + + static int +@@ -871,7 +875,7 @@ qca8k_port_fdb_del(struct dsa_switch *ds + if (!vid) + vid = 1; + +- return qca8k_fdb_del(priv, fdb->addr, port_mask, vid); ++ return qca8k_arl_del(priv, fdb->addr, port_mask, vid); + } + + static int +@@ -880,18 +884,21 @@ qca8k_port_fdb_dump(struct dsa_switch *d + int (*cb)(struct switchdev_obj *obj)) + { + struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; +- struct qca8k_fdb _fdb = { 0 }; +- int cnt = QCA8K_NUM_FDB_RECORDS; ++ struct qca8k_arl arl = { 0 }; ++ int cnt = QCA8K_NUM_ARL_RECORDS; + int ret = 0; + + mutex_lock(&priv->reg_mutex); +- while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) { +- if (!_fdb.aging) ++ while (cnt-- && !qca8k_arl_next(priv, &arl, port)) { ++ if (!arl.aging) + break; + +- ether_addr_copy(fdb->addr, _fdb.mac); +- fdb->vid = _fdb.vid; +- if (_fdb.aging == QCA8K_ATU_STATUS_STATIC) ++ if (arl.type != QCA8K_ARL_FDB) ++ continue; ++ ++ ether_addr_copy(fdb->addr, arl.mac); ++ fdb->vid = arl.vid; ++ if (arl.aging == QCA8K_ATU_STATUS_STATIC) + fdb->ndm_state = NUD_NOARP; + else + fdb->ndm_state = NUD_REACHABLE; +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 +@@ -24,7 +24,7 @@ + #define PHY_ID_QCA8337 0x004dd036 + #define QCA8K_ID_QCA8337 0x13 + +-#define QCA8K_NUM_FDB_RECORDS 2048 ++#define QCA8K_NUM_ARL_RECORDS 2048 + + #define QCA8K_CPU_PORT 0 + +@@ -147,12 +147,17 @@ enum { + QCA8K_PORT_SPEED_ERR = 3, + }; + +-enum qca8k_fdb_cmd { +- QCA8K_FDB_FLUSH = 1, +- QCA8K_FDB_LOAD = 2, +- QCA8K_FDB_PURGE = 3, +- QCA8K_FDB_NEXT = 6, +- QCA8K_FDB_SEARCH = 7, ++enum qca8k_arl_cmd { ++ QCA8K_ARL_FLUSH = 1, ++ QCA8K_ARL_LOAD = 2, ++ QCA8K_ARL_PURGE = 3, ++ QCA8K_ARL_NEXT = 6, ++ QCA8K_ARL_SEARCH = 7, ++}; ++ ++enum qca8k_arl_type { ++ QCA8K_ARL_FDB = 0, ++ QCA8K_ARL_MDB + }; + + struct ar8xxx_port_status { +@@ -175,7 +180,8 @@ struct qca8k_mib_desc { + const char *name; + }; + +-struct qca8k_fdb { ++struct qca8k_arl { ++ enum qca8k_arl_type type; + u16 vid; + u8 port_mask; + u8 aging; diff --git a/target/linux/ipq806x/patches-4.9/903-net-dsa-qca8k-add-support-for-MDB-offloading.patch b/target/linux/ipq806x/patches-4.9/903-net-dsa-qca8k-add-support-for-MDB-offloading.patch new file mode 100644 index 000000000000..2d8918a85d7e --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/903-net-dsa-qca8k-add-support-for-MDB-offloading.patch @@ -0,0 +1,174 @@ +From 25d533b01b60fe436dad793284d5e8828800e95d Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Tue, 1 Nov 2016 01:40:37 +0100 +Subject: [PATCH 15/22] net: dsa: qca8k: add support for MDB offloading + +Currently the driver only supports writing static FDB entries to the ARL +table. Add the missing driver callbacks so that we can also write MDB +entries to the ARL table. + +Signed-off-by: John Crispin +--- + drivers/net/dsa/qca8k.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++ + drivers/net/dsa/qca8k.h | 2 + + 2 files changed, 109 insertions(+) + +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 +@@ -372,6 +372,20 @@ qca8k_arl_access(struct qca8k_priv *priv + } + + static int ++qca8k_arl_search(struct qca8k_priv *priv, struct qca8k_arl *arl, const u8 *mac, ++ u16 vid) ++{ ++ int ret; ++ ++ qca8k_arl_write(priv, vid, 0, mac, 0); ++ ret = qca8k_arl_access(priv, QCA8K_ARL_SEARCH, 0); ++ if (ret >= 0) ++ qca8k_arl_read(priv, arl); ++ ++ return ret; ++} ++ ++static int + qca8k_arl_next(struct qca8k_priv *priv, struct qca8k_arl *arl, int port) + { + int ret; +@@ -551,6 +565,9 @@ qca8k_setup(struct dsa_switch *ds) + BIT(0) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S | + BIT(0) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S); + ++ /* Disable MDB learning */ ++ qca8k_reg_clear(priv, QCA8K_REG_ARL_CTRL, QCA8K_ARL_CTRL_IGMP_JOIN_EN); ++ + /* Setup connection between CPU port & user ports */ + for (i = 0; i < DSA_MAX_PORTS; i++) { + /* CPU port gets connected to all user ports of the switch */ +@@ -838,6 +855,31 @@ qca8k_port_arl_insert(struct qca8k_priv + } + + static int ++qca8k_port_arl_modify(struct dsa_switch *ds, struct qca8k_priv *priv, ++ const u8 *addr, u16 port, u16 vid, int add) ++{ ++ struct qca8k_arl arl = { 0 }; ++ u16 port_mask = BIT(port); ++ ++ /* Set the vid to the port vlan id if no vid is set */ ++ if (!vid) ++ vid = 1; ++ ++ if (qca8k_arl_search(priv, &arl, addr, vid) >= 0) { ++ if (add) ++ port_mask |= arl.port_mask; ++ else ++ port_mask &= ~arl.port_mask; ++ } ++ ++ if (!add && (port_mask == BIT(dsa_port_upstream_port(ds, port)))) ++ return qca8k_arl_del(priv, addr, port_mask, vid); ++ ++ return qca8k_arl_add(priv, addr, port_mask, vid, ++ QCA8K_ATU_STATUS_STATIC); ++} ++ ++static int + qca8k_port_fdb_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_fdb *fdb, + struct switchdev_trans *trans) +@@ -918,6 +960,67 @@ qca8k_get_tag_protocol(struct dsa_switch + return DSA_TAG_PROTO_QCA; + } + ++static int ++qca8k_port_mdb_prepare(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct switchdev_trans *trans) ++{ ++ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; ++ ++ return qca8k_port_arl_modify(ds, priv, mdb->addr, ++ dsa_port_upstream_port(ds, port), ++ mdb->vid, 1); ++} ++ ++static void ++qca8k_port_mdb_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct switchdev_trans *trans) ++{ ++ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; ++ ++ qca8k_port_arl_modify(ds, priv, mdb->addr, port, mdb->vid, 1); ++} ++ ++static int ++qca8k_port_mdb_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb) ++{ ++ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; ++ ++ return qca8k_port_arl_modify(ds, priv, mdb->addr, port, mdb->vid, 0); ++} ++ ++static int ++qca8K_port_mdb_dump(struct dsa_switch *ds, int port, ++ struct switchdev_obj_port_mdb *mdb, ++ int (*cb)(struct switchdev_obj *obj)) ++{ ++ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; ++ struct qca8k_arl arl = { 0 }; ++ int cnt = QCA8K_NUM_ARL_RECORDS; ++ int ret = 0; ++ ++ mutex_lock(&priv->reg_mutex); ++ ++ while (cnt-- && !qca8k_arl_next(priv, &arl, port)) { ++ if (!arl.aging) ++ break; ++ ++ if (arl.type != QCA8K_ARL_MDB) ++ continue; ++ ++ ether_addr_copy(mdb->addr, arl.mac); ++ mdb->vid = arl.vid; ++ ret = cb(&mdb->obj); ++ if (ret) ++ break; ++ } ++ mutex_unlock(&priv->reg_mutex); ++ ++ return ret; ++} ++ + static struct dsa_switch_ops qca8k_switch_ops = { + .get_tag_protocol = qca8k_get_tag_protocol, + .setup = qca8k_setup, +@@ -937,6 +1040,10 @@ static struct dsa_switch_ops qca8k_switc + .port_fdb_add = qca8k_port_fdb_add, + .port_fdb_del = qca8k_port_fdb_del, + .port_fdb_dump = qca8k_port_fdb_dump, ++ .port_mdb_prepare = qca8k_port_mdb_prepare, ++ .port_mdb_add = qca8k_port_mdb_add, ++ .port_mdb_del = qca8k_port_mdb_del, ++ .port_mdb_dump = qca8K_port_mdb_dump + }; + + static int +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 +@@ -103,6 +103,8 @@ + #define QCA8K_ATU_FUNC_FULL BIT(12) + #define QCA8K_ATU_FUNC_PORT_M 0xf + #define QCA8K_ATU_FUNC_PORT_S 8 ++#define QCA8K_REG_ARL_CTRL 0x618 ++#define QCA8K_ARL_CTRL_IGMP_JOIN_EN BIT(28) + #define QCA8K_REG_GLOBAL_FW_CTRL0 0x620 + #define QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN BIT(10) + #define QCA8K_REG_GLOBAL_FW_CTRL1 0x624 diff --git a/target/linux/ipq806x/patches-4.9/904-net-dsa-qca8k-add-port-arl-learning-limit.patch b/target/linux/ipq806x/patches-4.9/904-net-dsa-qca8k-add-port-arl-learning-limit.patch new file mode 100644 index 000000000000..fa06f006c722 --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/904-net-dsa-qca8k-add-port-arl-learning-limit.patch @@ -0,0 +1,77 @@ +From faae7eef37163613254bc5255334fff292cb148e Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Mon, 14 Nov 2016 17:39:01 +0100 +Subject: [PATCH 16/22] net: dsa: qca8k: add port arl learning limit + +Signed-off-by: John Crispin +--- + drivers/net/dsa/qca8k.c | 19 +++++++++++++++++++ + drivers/net/dsa/qca8k.h | 8 ++++++++ + 2 files changed, 27 insertions(+) + +diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c +index e3749ee..a43de2a 100644 +--- a/drivers/net/dsa/qca8k.c ++++ b/drivers/net/dsa/qca8k.c +@@ -434,6 +434,20 @@ qca8k_arl_flush(struct qca8k_priv *priv) + } + + static void ++qca8k_arl_port_limit(struct qca8k_priv *priv, int port, int limit) ++{ ++ u32 val = 0; ++ ++ val &= ~QCA8K_PORT_LEARN_LIMIT_EN; ++ if (limit) ++ val |= QCA8K_PORT_LEARN_LIMIT_EN; ++ val &= ~QCA8K_PORT_LEARN_LIMIT_CNT_M; ++ val |= limit & QCA8K_PORT_LEARN_LIMIT_CNT_M; ++ val |= QCA8K_PORT_LEARN_LIMIT_STATUS; ++ qca8k_write(priv, QCA8K_REG_PORT_LEARN_LIMIT(port), val); ++} ++ ++static void + qca8k_mib_init(struct qca8k_priv *priv) + { + mutex_lock(&priv->reg_mutex); +@@ -594,6 +608,11 @@ qca8k_setup(struct dsa_switch *ds) + if (ds->enabled_port_mask & BIT(i)) + qca8k_port_set_status(priv, i, 0); + ++ /* set the maximum SA learning limit for each user port */ ++ for (i = 1; i < QCA8K_NUM_PORTS; i++) ++ if (ds->enabled_port_mask & BIT(i)) ++ qca8k_arl_port_limit(priv, i, QCA8K_SA_LEARN_LIMIT); ++ + /* Forward all unknown frames to CPU port for Linux processing */ + qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1, + BIT(0) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S | +diff --git a/drivers/net/dsa/qca8k.h b/drivers/net/dsa/qca8k.h +index c0a2a13..6193073 100644 +--- a/drivers/net/dsa/qca8k.h ++++ b/drivers/net/dsa/qca8k.h +@@ -122,6 +122,11 @@ + #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_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) ++ + + /* Pkt edit registers */ + #define QCA8K_EGRESS_VLAN(x) (0x0c70 + (4 * (x / 2))) +@@ -142,6 +147,9 @@ + #define MII_ATH_MMD_ADDR 0x0d + #define MII_ATH_MMD_DATA 0x0e + ++/* the maximum number of SA addresses a user port may learn */ ++#define QCA8K_SA_LEARN_LIMIT 512 ++ + enum { + QCA8K_PORT_SPEED_10M = 0, + QCA8K_PORT_SPEED_100M = 1, +-- +1.7.10.4 + diff --git a/target/linux/ipq806x/patches-4.9/905-net-dsa-multi-cpu.patch b/target/linux/ipq806x/patches-4.9/905-net-dsa-multi-cpu.patch new file mode 100644 index 000000000000..230586e15a88 --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/905-net-dsa-multi-cpu.patch @@ -0,0 +1,247 @@ +Index: linux-4.9.34/include/net/dsa.h +=================================================================== +--- linux-4.9.34.orig/include/net/dsa.h ++++ linux-4.9.34/include/net/dsa.h +@@ -144,6 +144,8 @@ struct dsa_port { + struct device_node *dn; + unsigned int ageing_time; + u8 stp_state; ++ struct net_device *ethernet; ++ int upstream; + }; + + struct dsa_switch { +@@ -204,7 +206,7 @@ struct dsa_switch { + + static inline bool dsa_is_cpu_port(struct dsa_switch *ds, int p) + { +- return !!(ds->index == ds->dst->cpu_switch && p == ds->dst->cpu_port); ++ return !!(ds->cpu_port_mask & (1 << p)); + } + + static inline bool dsa_is_dsa_port(struct dsa_switch *ds, int p) +@@ -217,6 +219,11 @@ static inline bool dsa_is_port_initializ + return ds->enabled_port_mask & (1 << p) && ds->ports[p].netdev; + } + ++static inline bool dsa_is_upstream_port(struct dsa_switch *ds, int p) ++{ ++ return dsa_is_cpu_port(ds, p) || dsa_is_dsa_port(ds, p); ++} ++ + static inline u8 dsa_upstream_port(struct dsa_switch *ds) + { + struct dsa_switch_tree *dst = ds->dst; +@@ -233,6 +240,18 @@ static inline u8 dsa_upstream_port(struc + return ds->rtable[dst->cpu_switch]; + } + ++static inline u8 dsa_port_upstream_port(struct dsa_switch *ds, int port) ++{ ++ /* ++ * If this port has a specific upstream cpu port, use it, ++ * otherwise use the switch default. ++ */ ++ if (ds->ports[port].upstream) ++ return ds->ports[port].upstream; ++ else ++ return dsa_upstream_port(ds); ++} ++ + struct switchdev_trans; + struct switchdev_obj; + struct switchdev_obj_port_fdb; +Index: linux-4.9.34/net/dsa/dsa2.c +=================================================================== +--- linux-4.9.34.orig/net/dsa/dsa2.c ++++ linux-4.9.34/net/dsa/dsa2.c +@@ -248,8 +248,6 @@ static int dsa_cpu_port_apply(struct dev + return err; + } + +- ds->cpu_port_mask |= BIT(index); +- + return 0; + } + +@@ -259,6 +257,10 @@ static void dsa_cpu_port_unapply(struct + dsa_cpu_dsa_destroy(port); + ds->cpu_port_mask &= ~BIT(index); + ++ if (ds->ports[index].ethernet) { ++ dev_put(ds->ports[index].ethernet); ++ ds->ports[index].ethernet = NULL; ++ } + } + + static int dsa_user_port_apply(struct device_node *port, u32 index, +@@ -479,6 +481,29 @@ static int dsa_cpu_parse(struct device_n + + dst->rcv = dst->tag_ops->rcv; + ++ dev_hold(ethernet_dev); ++ ds->ports[index].ethernet = ethernet_dev; ++ ds->cpu_port_mask |= BIT(index); ++ ++ return 0; ++} ++ ++static int dsa_user_parse(struct device_node *port, u32 index, ++ struct dsa_switch *ds) ++{ ++ struct device_node *cpu_port; ++ const unsigned int *cpu_port_reg; ++ int cpu_port_index; ++ ++ cpu_port = of_parse_phandle(port, "cpu", 0); ++ if (cpu_port) { ++ cpu_port_reg = of_get_property(cpu_port, "reg", NULL); ++ if (!cpu_port_reg) ++ return -EINVAL; ++ cpu_port_index = be32_to_cpup(cpu_port_reg); ++ ds->ports[index].upstream = cpu_port_index; ++ } ++ + return 0; + } + +@@ -486,18 +511,19 @@ static int dsa_ds_parse(struct dsa_switc + { + struct device_node *port; + u32 index; +- int err; ++ int err = 0; + + for (index = 0; index < DSA_MAX_PORTS; index++) { + port = ds->ports[index].dn; + if (!port) + continue; + +- if (dsa_port_is_cpu(port)) { ++ if (dsa_port_is_cpu(port)) + err = dsa_cpu_parse(port, index, dst, ds); +- if (err) +- return err; +- } ++ else if (!dsa_port_is_dsa(port)) ++ err = dsa_user_parse(port, index, ds); ++ if (err) ++ return err; + } + + pr_info("DSA: switch %d %d parsed\n", dst->tree, ds->index); +Index: linux-4.9.34/net/dsa/dsa_priv.h +=================================================================== +--- linux-4.9.34.orig/net/dsa/dsa_priv.h ++++ linux-4.9.34/net/dsa/dsa_priv.h +@@ -43,6 +43,7 @@ struct dsa_slave_priv { + int old_duplex; + + struct net_device *bridge_dev; ++ struct net_device *master; + #ifdef CONFIG_NET_POLL_CONTROLLER + struct netpoll *netpoll; + #endif +Index: linux-4.9.34/net/dsa/slave.c +=================================================================== +--- linux-4.9.34.orig/net/dsa/slave.c ++++ linux-4.9.34/net/dsa/slave.c +@@ -61,7 +61,7 @@ static int dsa_slave_get_iflink(const st + { + struct dsa_slave_priv *p = netdev_priv(dev); + +- return p->parent->dst->master_netdev->ifindex; ++ return p->master->ifindex; + } + + static inline bool dsa_port_is_bridged(struct dsa_slave_priv *p) +@@ -96,7 +96,7 @@ static void dsa_port_set_stp_state(struc + static int dsa_slave_open(struct net_device *dev) + { + struct dsa_slave_priv *p = netdev_priv(dev); +- struct net_device *master = p->parent->dst->master_netdev; ++ struct net_device *master = p->master; + struct dsa_switch *ds = p->parent; + u8 stp_state = dsa_port_is_bridged(p) ? + BR_STATE_BLOCKING : BR_STATE_FORWARDING; +@@ -151,7 +151,7 @@ out: + static int dsa_slave_close(struct net_device *dev) + { + struct dsa_slave_priv *p = netdev_priv(dev); +- struct net_device *master = p->parent->dst->master_netdev; ++ struct net_device *master = p->master; + struct dsa_switch *ds = p->parent; + + if (p->phy) +@@ -188,7 +188,7 @@ static int dsa_slave_init(struct net_dev + static void dsa_slave_change_rx_flags(struct net_device *dev, int change) + { + struct dsa_slave_priv *p = netdev_priv(dev); +- struct net_device *master = p->parent->dst->master_netdev; ++ struct net_device *master = p->master; + + if (change & IFF_ALLMULTI) + dev_set_allmulti(master, dev->flags & IFF_ALLMULTI ? 1 : -1); +@@ -199,7 +199,7 @@ static void dsa_slave_change_rx_flags(st + static void dsa_slave_set_rx_mode(struct net_device *dev) + { + struct dsa_slave_priv *p = netdev_priv(dev); +- struct net_device *master = p->parent->dst->master_netdev; ++ struct net_device *master = p->master; + + dev_mc_sync(master, dev); + dev_uc_sync(master, dev); +@@ -208,7 +208,7 @@ static void dsa_slave_set_rx_mode(struct + static int dsa_slave_set_mac_address(struct net_device *dev, void *a) + { + struct dsa_slave_priv *p = netdev_priv(dev); +- struct net_device *master = p->parent->dst->master_netdev; ++ struct net_device *master = p->master; + struct sockaddr *addr = a; + int err; + +@@ -643,7 +643,7 @@ static netdev_tx_t dsa_slave_xmit(struct + /* Queue the SKB for transmission on the parent interface, but + * do not modify its EtherType + */ +- nskb->dev = p->parent->dst->master_netdev; ++ nskb->dev = p->master; + dev_queue_xmit(nskb); + + return NETDEV_TX_OK; +@@ -955,7 +955,7 @@ static int dsa_slave_netpoll_setup(struc + { + struct dsa_slave_priv *p = netdev_priv(dev); + struct dsa_switch *ds = p->parent; +- struct net_device *master = ds->dst->master_netdev; ++ struct net_device *master = p->master; + struct netpoll *netpoll; + int err = 0; + +@@ -1246,11 +1246,16 @@ int dsa_slave_create(struct dsa_switch * + struct net_device *master; + struct net_device *slave_dev; + struct dsa_slave_priv *p; ++ int port_cpu = ds->ports[port].upstream; + int ret; + +- master = ds->dst->master_netdev; +- if (ds->master_netdev) ++ if (port_cpu && ds->ports[port_cpu].ethernet) ++ master = ds->ports[port_cpu].ethernet; ++ else if (ds->master_netdev) + master = ds->master_netdev; ++ else ++ master = ds->dst->master_netdev; ++ master->dsa_ptr = (void *)ds->dst; + + slave_dev = alloc_netdev(sizeof(struct dsa_slave_priv), name, + NET_NAME_UNKNOWN, ether_setup); +@@ -1276,6 +1281,7 @@ int dsa_slave_create(struct dsa_switch * + p->parent = ds; + p->port = port; + p->xmit = dst->tag_ops->xmit; ++ p->master = master; + + p->old_pause = -1; + p->old_link = -1; diff --git a/target/linux/ipq806x/patches-4.9/905-net-dsa-qca8k-add-destroy-hooks.patch b/target/linux/ipq806x/patches-4.9/905-net-dsa-qca8k-add-destroy-hooks.patch new file mode 100644 index 000000000000..696520c13812 --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/905-net-dsa-qca8k-add-destroy-hooks.patch @@ -0,0 +1,57 @@ +From 4bfbe574825d41c12ca88c4f81b4e02e83710b6c Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Tue, 1 Nov 2016 01:51:50 +0100 +Subject: [PATCH 19/22] net: dsa: qca8k: add destroy hooks + +Reset the switch prior to a reboot. + +Signed-off-by: John Crispin +--- + drivers/net/dsa/qca8k.c | 10 ++++++++++ + drivers/net/dsa/qca8k.h | 1 + + 2 files changed, 11 insertions(+) + +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 +@@ -672,6 +672,15 @@ qca8k_get_ethtool_stats(struct dsa_switc + } + } + ++static void ++qca8k_destroy(struct dsa_switch *ds) ++{ ++ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; ++ ++ qca8k_reg_set(priv, QCA8K_REG_MASK_CTRL, ++ QCA8K_MASK_CTRL_SW_RESET); ++} ++ + static int + qca8k_get_sset_count(struct dsa_switch *ds) + { +@@ -1050,6 +1059,7 @@ static struct dsa_switch_ops qca8k_switc + .get_sset_count = qca8k_get_sset_count, + .get_eee = qca8k_get_eee, + .set_eee = qca8k_set_eee, ++ .destroy = qca8k_destroy, + .port_enable = qca8k_port_enable, + .port_disable = qca8k_port_disable, + .port_stp_state_set = qca8k_port_stp_state_set, +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 +@@ -30,9 +30,11 @@ + + /* Global control registers */ + #define QCA8K_REG_MASK_CTRL 0x000 ++#define QCA8K_MASK_CTRL_SW_RESET BIT(31) + #define QCA8K_MASK_CTRL_ID_M 0xff + #define QCA8K_MASK_CTRL_ID_S 8 + #define QCA8K_REG_PORT0_PAD_CTRL 0x004 ++#define QCA8K_PORT0_PAD_CTRL_MAC06_EXCHG BIT(31) + #define QCA8K_REG_PORT5_PAD_CTRL 0x008 + #define QCA8K_REG_PORT6_PAD_CTRL 0x00c + #define QCA8K_PORT_PAD_RGMII_EN BIT(26) diff --git a/target/linux/ipq806x/patches-4.9/906-net-dsa-qca8k-allow-swapping-of-mac0-and-mac6.patch b/target/linux/ipq806x/patches-4.9/906-net-dsa-qca8k-allow-swapping-of-mac0-and-mac6.patch new file mode 100644 index 000000000000..5be875cbf658 --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/906-net-dsa-qca8k-allow-swapping-of-mac0-and-mac6.patch @@ -0,0 +1,48 @@ +From fd18a10a9f172dcc78629669ce60304924c5a2fb Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Thu, 15 Dec 2016 04:40:55 +0100 +Subject: [PATCH 17/22] net: dsa: qca8k: allow swapping of mac0 and mac6 + +The switch allows us to swap the internal wirering of the two cpu ports. +For the HW offloading to work the ethernet MAC conencting to the LAN +ports must be wired to cpu port 0. There is HW in the wild that does not +fulfill this requirement. On these boards we need to swap the cpu ports. + +Signed-off-by: John Crispin +--- + drivers/net/dsa/qca8k.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +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 +@@ -518,6 +518,18 @@ qca8k_port_set_status(struct qca8k_priv + qca8k_reg_clear(priv, QCA8K_REG_PORT_STATUS(port), mask); + } + ++static void ++qca8k_exchange_mac06(struct qca8k_priv *priv, struct device_node *np) ++{ ++ u32 val = qca8k_read(priv, QCA8K_REG_PORT0_PAD_CTRL); ++ ++ if (of_property_read_bool(np, "qca,exchange_mac06")) ++ val |= QCA8K_PORT0_PAD_CTRL_MAC06_EXCHG; ++ else ++ val &= ~QCA8K_PORT0_PAD_CTRL_MAC06_EXCHG; ++ qca8k_write(priv, QCA8K_REG_PORT0_PAD_CTRL, val); ++} ++ + static int + qca8k_setup(struct dsa_switch *ds) + { +@@ -538,6 +550,9 @@ qca8k_setup(struct dsa_switch *ds) + if (IS_ERR(priv->regmap)) + pr_warn("regmap initialization failed"); + ++ /* Exchange MAC0 and MAC6 */ ++ qca8k_exchange_mac06(priv, priv->ds->dev->of_node); ++ + /* Initialize CPU port pad mode (xMII type, delays...) */ + phy_mode = of_get_phy_mode(ds->ports[ds->dst->cpu_port].dn); + if (phy_mode < 0) { diff --git a/target/linux/ipq806x/patches-4.9/907-net-dsa-qca8k-add-support-for-multiple-cpu-ports.patch b/target/linux/ipq806x/patches-4.9/907-net-dsa-qca8k-add-support-for-multiple-cpu-ports.patch new file mode 100644 index 000000000000..f95e63390615 --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/907-net-dsa-qca8k-add-support-for-multiple-cpu-ports.patch @@ -0,0 +1,221 @@ +From 191cfe1b462e2fb1c10be747b76c77ea11120f73 Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Thu, 15 Dec 2016 06:01:15 +0100 +Subject: [PATCH 18/22] net: dsa: qca8k: add support for multiple cpu ports + +With the subsystem now supporting multiple cpu ports, we need to make some +changes to the driver as it currently has the cpu port hardcoded as port0. +The patch moves the setup logic for the cpu port into one loop which +iterates over all cpu ports and sets them up. Additionally the bridge +join/leave logic needs a small fix to work with having a cpu port other +than 0. + +Signed-off-by: John Crispin +--- + drivers/net/dsa/qca8k.c | 142 +++++++++++++++++++++++++++-------------------- + drivers/net/dsa/qca8k.h | 3 +- + 2 files changed, 82 insertions(+), 63 deletions(-) + +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 +@@ -530,11 +530,25 @@ qca8k_exchange_mac06(struct qca8k_priv * + qca8k_write(priv, QCA8K_REG_PORT0_PAD_CTRL, val); + } + ++static void ++qca8k_setup_flooding(struct qca8k_priv *priv, int port_mask, int enable) ++{ ++ u32 mask = (port_mask << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S) | ++ (port_mask << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S) | ++ (port_mask << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S) | ++ (port_mask << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S); ++ ++ if (enable) ++ qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL1, mask); ++ else ++ qca8k_reg_clear(priv, QCA8K_REG_GLOBAL_FW_CTRL1, mask); ++} ++ + static int + qca8k_setup(struct dsa_switch *ds) + { + struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; +- int ret, i, phy_mode = -1; ++ int ret, i; + + /* Make sure that port 0 is the cpu port */ + if (!dsa_is_cpu_port(ds, 0)) { +@@ -550,32 +564,52 @@ qca8k_setup(struct dsa_switch *ds) + if (IS_ERR(priv->regmap)) + pr_warn("regmap initialization failed"); + +- /* Exchange MAC0 and MAC6 */ +- qca8k_exchange_mac06(priv, priv->ds->dev->of_node); +- +- /* Initialize CPU port pad mode (xMII type, delays...) */ +- phy_mode = of_get_phy_mode(ds->ports[ds->dst->cpu_port].dn); +- if (phy_mode < 0) { +- pr_err("Can't find phy-mode for master device\n"); +- return phy_mode; +- } +- ret = qca8k_set_pad_ctrl(priv, QCA8K_CPU_PORT, phy_mode); +- if (ret < 0) +- return ret; +- +- /* Enable CPU Port */ ++ /* Tell the switch that port0 is a cpu port */ + qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0, + QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN); +- qca8k_port_set_status(priv, QCA8K_CPU_PORT, 1); +- priv->port_sts[QCA8K_CPU_PORT].enabled = 1; + + /* Enable MIB counters */ + qca8k_mib_init(priv); + +- /* Enable QCA header mode on the cpu port */ +- qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(QCA8K_CPU_PORT), +- QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S | +- QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S); ++ /* Setup the cpu ports */ ++ for (i = 0; i < QCA8K_NUM_PORTS; i++) { ++ struct net_device *netdev; ++ int phy_mode = -1; ++ ++ if (!dsa_is_cpu_port(ds, i)) ++ continue; ++ ++ netdev = ds->ports[i].ethernet; ++ if (!netdev) { ++ pr_err("Can't find netdev for port%d\n", i); ++ return -ENODEV; ++ } ++ ++ /* Initialize CPU port pad mode (xMII type, delays...) */ ++ phy_mode = of_get_phy_mode(netdev->dev.parent->of_node); ++ if (phy_mode < 0) { ++ pr_err("Can't find phy-mode for port:%d\n", i); ++ return phy_mode; ++ } ++ ret = qca8k_set_pad_ctrl(priv, i, phy_mode); ++ if (ret < 0) ++ return ret; ++ ++ /* Enable QCA header mode on the cpu port */ ++ qca8k_write(priv, ++ QCA8K_REG_PORT_HDR_CTRL(i), ++ QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S | ++ QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S); ++ ++ qca8k_port_set_status(priv, i, 1); ++ priv->port_sts[i].enabled = 1; ++ ++ /* Forward all unknown frames to CPU port for Linux processing */ ++ qca8k_setup_flooding(priv, BIT(i), 1); ++ } ++ ++ /* Exchange MAC0 and MAC6 */ ++ qca8k_exchange_mac06(priv, priv->ds->dev->of_node); + + /* Disable forwarding by default on all ports */ + for (i = 0; i < QCA8K_NUM_PORTS; i++) +@@ -592,46 +626,33 @@ qca8k_setup(struct dsa_switch *ds) + if (ds->enabled_port_mask & BIT(i)) + qca8k_arl_port_limit(priv, i, QCA8K_SA_LEARN_LIMIT); + +- /* Forward all unknown frames to CPU port for Linux processing */ +- qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1, +- BIT(0) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S | +- BIT(0) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S | +- BIT(0) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S | +- BIT(0) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S); +- + /* Disable MDB learning */ + qca8k_reg_clear(priv, QCA8K_REG_ARL_CTRL, QCA8K_ARL_CTRL_IGMP_JOIN_EN); + +- /* Setup connection between CPU port & user ports */ +- for (i = 0; i < DSA_MAX_PORTS; i++) { +- /* CPU port gets connected to all user ports of the switch */ +- if (dsa_is_cpu_port(ds, i)) { +- qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(QCA8K_CPU_PORT), +- QCA8K_PORT_LOOKUP_MEMBER, +- ds->enabled_port_mask); +- } ++ /* Setup user ports and connections to CPU ports */ ++ for (i = 0; i < QCA8K_NUM_PORTS; i++) { ++ int shift = 16 * (i % 2); ++ int cpu_port; + +- /* Invividual user ports get connected to CPU port only */ +- if (ds->enabled_port_mask & BIT(i)) { +- int shift = 16 * (i % 2); +- +- qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i), +- QCA8K_PORT_LOOKUP_MEMBER, +- BIT(QCA8K_CPU_PORT)); +- +- /* Enable ARP Auto-learning by default */ +- qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i), +- QCA8K_PORT_LOOKUP_LEARN); +- +- /* For port based vlans to work we need to set the +- * default egress vid +- */ +- qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i), +- 0xffff << shift, 1 << shift); +- qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i), +- QCA8K_PORT_VLAN_CVID(1) | +- QCA8K_PORT_VLAN_SVID(1)); +- } ++ if (!(ds->enabled_port_mask & BIT(i))) ++ continue; ++ ++ cpu_port = dsa_port_upstream_port(ds, i); ++ qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i), BIT(cpu_port)); ++ qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(cpu_port), BIT(i)); ++ ++ /* Enable ARP Auto-learning by default */ ++ qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i), ++ QCA8K_PORT_LOOKUP_LEARN); ++ ++ /* For port based vlans to work we need to set the ++ * default egress vid ++ */ ++ qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i), ++ 0xffff << shift, 1 << shift); ++ qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i), ++ QCA8K_PORT_VLAN_CVID(1) | ++ QCA8K_PORT_VLAN_SVID(1)); + } + + /* Flush the FDB table */ +@@ -814,7 +835,7 @@ qca8k_port_bridge_join(struct dsa_switch + struct net_device *bridge) + { + struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; +- int port_mask = BIT(QCA8K_CPU_PORT); ++ int port_mask = 0; + int i; + + priv->port_sts[port].bridge_dev = bridge; +@@ -832,8 +853,7 @@ qca8k_port_bridge_join(struct dsa_switch + port_mask |= BIT(i); + } + /* Add all other ports to this ports portvlan mask */ +- qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), +- QCA8K_PORT_LOOKUP_MEMBER, port_mask); ++ qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(port), port_mask); + + return 0; + } +@@ -860,7 +880,8 @@ qca8k_port_bridge_leave(struct dsa_switc + * this port + */ + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), +- QCA8K_PORT_LOOKUP_MEMBER, BIT(QCA8K_CPU_PORT)); ++ QCA8K_PORT_LOOKUP_MEMBER, ++ BIT(dsa_port_upstream_port(ds, i))); + } + + static int diff --git a/target/linux/ipq806x/patches-4.9/908-net-dsa-qca8k-add-offloading-hooks.patch b/target/linux/ipq806x/patches-4.9/908-net-dsa-qca8k-add-offloading-hooks.patch new file mode 100644 index 000000000000..769652f01ac1 --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/908-net-dsa-qca8k-add-offloading-hooks.patch @@ -0,0 +1,150 @@ +From 8402a58cd1968ce6893b17fa4615349015b333c8 Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Tue, 1 Nov 2016 01:53:34 +0100 +Subject: [PATCH 20/22] net: dsa: qca8k: add offloading hooks + +Modify the driver to accomodate the requirements of the offloading code. +Do so by making the register access functions none static and calling the +init/exit functions. + +Signed-off-by: John Crispin +--- + drivers/net/dsa/qca8k.c | 27 ++++++++++++--------------- + drivers/net/dsa/qca8k.h | 24 ++++++++++++++++++++++++ + 2 files changed, 36 insertions(+), 15 deletions(-) + +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 +@@ -34,6 +34,9 @@ + .name = (_n), \ + } + ++struct qca8k_priv *qca8k_priv = NULL; ++EXPORT_SYMBOL_GPL(qca8k_priv); ++ + static const struct qca8k_mib_desc ar8327_mib[] = { + MIB_DESC(1, 0x00, "RxBroad"), + MIB_DESC(1, 0x04, "RxPause"), +@@ -146,7 +149,7 @@ qca8k_set_page(struct mii_bus *bus, u16 + qca8k_current_page = page; + } + +-static u32 ++u32 + qca8k_read(struct qca8k_priv *priv, u32 reg) + { + u16 r1, r2, page; +@@ -163,8 +166,9 @@ qca8k_read(struct qca8k_priv *priv, u32 + + return val; + } ++EXPORT_SYMBOL_GPL(qca8k_read); + +-static void ++void + qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val) + { + u16 r1, r2, page; +@@ -178,8 +182,9 @@ qca8k_write(struct qca8k_priv *priv, u32 + + mutex_unlock(&priv->bus->mdio_lock); + } ++EXPORT_SYMBOL_GPL(qca8k_write); + +-static u32 ++u32 + qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 val) + { + u16 r1, r2, page; +@@ -199,18 +204,7 @@ qca8k_rmw(struct qca8k_priv *priv, u32 r + + return ret; + } +- +-static void +-qca8k_reg_set(struct qca8k_priv *priv, u32 reg, u32 val) +-{ +- qca8k_rmw(priv, reg, 0, val); +-} +- +-static void +-qca8k_reg_clear(struct qca8k_priv *priv, u32 reg, u32 val) +-{ +- qca8k_rmw(priv, reg, val, 0); +-} ++EXPORT_SYMBOL_GPL(qca8k_rmw); + + static int + qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val) +@@ -1116,6 +1110,7 @@ qca8k_sw_probe(struct mdio_device *mdiod + { + struct qca8k_priv *priv; + u32 id; ++ int ret; + + /* allocate the private data struct so that we can probe the switches + * ID register +@@ -1143,7 +1138,12 @@ qca8k_sw_probe(struct mdio_device *mdiod + mutex_init(&priv->reg_mutex); + dev_set_drvdata(&mdiodev->dev, priv); + +- return dsa_register_switch(priv->ds, priv->ds->dev->of_node); ++ ++ ret = dsa_register_switch(priv->ds, priv->ds->dev->of_node); ++ if (!ret) ++ qca8k_priv = priv; ++ ++ return ret; + } + + static void +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 +@@ -178,13 +178,17 @@ struct ar8xxx_port_status { + int enabled; + }; + ++struct qca8k_offload *offload; ++ + struct qca8k_priv { + struct regmap *regmap; + struct mii_bus *bus; + struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS]; + struct dsa_switch *ds; + struct mutex reg_mutex; ++ struct qca8k_offload *offload; + }; ++extern struct qca8k_priv *qca8k_priv; + + struct qca8k_mib_desc { + unsigned int size; +@@ -200,4 +204,25 @@ struct qca8k_arl { + u8 mac[6]; + }; + ++u32 ++qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 val); ++ ++u32 ++qca8k_read(struct qca8k_priv *priv, u32 reg); ++ ++void ++qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val); ++ ++static inline void ++qca8k_reg_set(struct qca8k_priv *priv, u32 reg, u32 val) ++{ ++ qca8k_rmw(priv, reg, 0, val); ++} ++ ++static inline void ++qca8k_reg_clear(struct qca8k_priv *priv, u32 reg, u32 val) ++{ ++ qca8k_rmw(priv, reg, val, 0); ++} ++ + #endif /* __QCA8K_H */ diff --git a/target/linux/ipq806x/patches-4.9/909-net-dsa-qca8k-add-offloading-layer.patch b/target/linux/ipq806x/patches-4.9/909-net-dsa-qca8k-add-offloading-layer.patch new file mode 100644 index 000000000000..cfaa2ec87a4b --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/909-net-dsa-qca8k-add-offloading-layer.patch @@ -0,0 +1,4830 @@ +From b07ffec3cb03542e2169ab95fe74c4b6261777c8 Mon Sep 17 00:00:00 2001 +From: John Crispin +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 +--- + 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 ++#include ++#include ++ ++#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 ++ * ++ * 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 ++#include ++#include ++#include ++#include ++ ++#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 ++ * ++ * 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 ++ * ++ * 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 ++ * ++ * 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 ++ * ++ * 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 ++ * ++ * 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 ++ ++#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 ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#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 ++ * ++ * 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 ++#include ++ ++#include ++#include ++#include ++ ++#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 ++ * ++ * 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 ++ * ++ * 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 ++ ++#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 ++ * ++ * 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 ++ * ++ * 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 ++ * ++ * 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 ++ * ++ * 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 ++ * ++ * 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 ++ * ++ * 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 ++ * ++ * 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 ++ * ++ * 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 ++#include ++ ++#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 ++ * ++ * 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 ++ ++#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); ++} diff --git a/target/linux/ipq806x/patches-4.9/910-dts.patch b/target/linux/ipq806x/patches-4.9/910-dts.patch new file mode 100644 index 000000000000..0274349900ed --- /dev/null +++ b/target/linux/ipq806x/patches-4.9/910-dts.patch @@ -0,0 +1,117 @@ +Index: linux-4.9.34/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +=================================================================== +--- linux-4.9.34.orig/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ linux-4.9.34/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -225,66 +225,66 @@ + ports { + #address-cells = <1>; + #size-cells = <0>; +- port@0 { ++ cpu_port1: port@0 { + reg = <0>; + label = "cpu"; + ethernet = <&gmac1>; + phy-mode = "rgmii"; + +- fixed-link { +- speed = <1000>; +- full-duplex; +- }; ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; + }; + + port@1 { + reg = <1>; + label = "lan1"; ++ cpu = <&cpu_port1>; + phy-handle = <&phy_port1>; + }; + + port@2 { + reg = <2>; + label = "lan2"; ++ cpu = <&cpu_port1>; + phy-handle = <&phy_port2>; + }; + + port@3 { + reg = <3>; + label = "lan3"; ++ cpu = <&cpu_port1>; + phy-handle = <&phy_port3>; + }; + +- port@4 { +- reg = <4>; ++ port@5{ ++ reg = <5>; + label = "lan4"; +- phy-handle = <&phy_port4>; ++ cpu = <&cpu_port1>; ++ phy-handle = <&phy_port5>; + }; + +- port@5 { +- reg = <5>; ++ port@4 { ++ reg = <4>; + label = "wan"; +- phy-handle = <&phy_port5>; ++ cpu = <&cpu_port2>; ++ phy-handle = <&phy_port4>; + }; + +- /* +- * Disabled until DSA supports multiple CPUs, +- * otherwise it causes undefined behavior. +- * +- * port@6 { +- * reg = <6>; +- * label = "cpu"; +- * ethernet = <&gmac2>; +- * phy-mode = "sgmii"; +- * +- * fixed-link { +- * speed = <1000>; +- * full-duplex; +- * }; +- * }; +- */ ++ cpu_port2: port@6 { ++ reg = <6>; ++ label = "cpu"; ++ ethernet = <&gmac2>; ++ phy-mode = "sgmii"; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; + }; +- }; ++ }; + }; + + gmac1: ethernet@37200000 { +@@ -295,6 +295,8 @@ + pinctrl-0 = <&rgmii2_pins>; + pinctrl-names = "default"; + ++ mac-address = [00 11 22 33 44 55]; ++ + fixed-link { + speed = <1000>; + full-duplex; +@@ -306,6 +308,8 @@ + phy-mode = "sgmii"; + qcom,id = <2>; + ++ mac-address = [00 11 22 33 44 56]; ++ + fixed-link { + speed = <1000>; + full-duplex; -- 2.30.2