ipq806x: convert to using qca8k qca8k
authorJohn Crispin <john@phrozen.org>
Wed, 5 Jul 2017 08:13:33 +0000 (10:13 +0200)
committerJohn Crispin <john@phrozen.org>
Fri, 7 Jul 2017 12:54:54 +0000 (14:54 +0200)
Signed-off-by: John Crispin <john@phrozen.org>
26 files changed:
target/linux/ipq806x/base-files/etc/board.d/02_network
target/linux/ipq806x/config-4.9
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-c2600.dts
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-d7800.dts
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-db149.dts
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-ea8500.dts
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500.dts
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-r7500v2.dts
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8064-vr2600v.dts
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-nbg6817.dts
target/linux/ipq806x/files-4.9/arch/arm/boot/dts/qcom-ipq8065-r7800.dts
target/linux/ipq806x/modules.mk
target/linux/ipq806x/patches-4.9/900-net-dsa-make-the-slave-device-inheret-the-MAC-of-the.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/901-netfilter-qca8k-add-CT-extension.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/902-net-dsa-add-destroy-callback.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/902-net-dsa-qca8k-properly-name-the-ARL-table.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/903-net-dsa-qca8k-add-support-for-MDB-offloading.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/904-net-dsa-qca8k-add-port-arl-learning-limit.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/905-net-dsa-multi-cpu.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/905-net-dsa-qca8k-add-destroy-hooks.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/906-net-dsa-qca8k-allow-swapping-of-mac0-and-mac6.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/907-net-dsa-qca8k-add-support-for-multiple-cpu-ports.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/908-net-dsa-qca8k-add-offloading-hooks.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/909-net-dsa-qca8k-add-offloading-layer.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.9/910-dts.patch [new file with mode: 0644]

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