]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/commitdiff
net: ethernet: mtk_eth_soc: add flow offloading support
authorFelix Fietkau <nbd@nbd.name>
Wed, 24 Mar 2021 01:30:54 +0000 (02:30 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 24 Mar 2021 19:48:40 +0000 (12:48 -0700)
This adds support for offloading IPv4 routed flows, including SNAT/DNAT,
one VLAN, PPPoE and DSA.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mediatek/Makefile
drivers/net/ethernet/mediatek/mtk_eth_soc.c
drivers/net/ethernet/mediatek/mtk_eth_soc.h
drivers/net/ethernet/mediatek/mtk_ppe_offload.c [new file with mode: 0644]

index 871dc3e113e25c14d2a24889de930371d89cd4d1..79d4cdbbcbf5bb0623ce6f39f7359758bba97585 100644 (file)
@@ -4,5 +4,5 @@
 #
 
 obj-$(CONFIG_NET_MEDIATEK_SOC) += mtk_eth.o
-mtk_eth-y := mtk_eth_soc.o mtk_sgmii.o mtk_eth_path.o mtk_ppe.o mtk_ppe_debugfs.o
+mtk_eth-y := mtk_eth_soc.o mtk_sgmii.o mtk_eth_path.o mtk_ppe.o mtk_ppe_debugfs.o mtk_ppe_offload.o
 obj-$(CONFIG_NET_MEDIATEK_STAR_EMAC) += mtk_star_emac.o
index 67605b9a391649ad474467dff4c067b5558dfa86..0396f0db855f47fbfc2622ca42223d3882fcc6c1 100644 (file)
@@ -2843,6 +2843,7 @@ static const struct net_device_ops mtk_netdev_ops = {
 #ifdef CONFIG_NET_POLL_CONTROLLER
        .ndo_poll_controller    = mtk_poll_controller,
 #endif
+       .ndo_setup_tc           = mtk_eth_setup_tc,
 };
 
 static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
@@ -3104,6 +3105,10 @@ static int mtk_probe(struct platform_device *pdev)
                                   eth->base + MTK_ETH_PPE_BASE, 2);
                if (err)
                        goto err_free_dev;
+
+               err = mtk_eth_offload_init(eth);
+               if (err)
+                       goto err_free_dev;
        }
 
        for (i = 0; i < MTK_MAX_DEVS; i++) {
index 66ed537bc133e024dc9ed9600dc401ef0dd18f36..1a6750c08bb9d548f829fa43a0f17d9858fe03a4 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/u64_stats_sync.h>
 #include <linux/refcount.h>
 #include <linux/phylink.h>
+#include <linux/rhashtable.h>
 #include "mtk_ppe.h"
 
 #define MTK_QDMA_PAGE_SIZE     2048
@@ -41,7 +42,8 @@
                                 NETIF_F_HW_VLAN_CTAG_RX | \
                                 NETIF_F_SG | NETIF_F_TSO | \
                                 NETIF_F_TSO6 | \
-                                NETIF_F_IPV6_CSUM)
+                                NETIF_F_IPV6_CSUM |\
+                                NETIF_F_HW_TC)
 #define MTK_HW_FEATURES_MT7628 (NETIF_F_SG | NETIF_F_RXCSUM)
 #define NEXT_DESP_IDX(X, Y)    (((X) + 1) & ((Y) - 1))
 
@@ -914,6 +916,7 @@ struct mtk_eth {
        int                             ip_align;
 
        struct mtk_ppe                  ppe;
+       struct rhashtable               flow_table;
 };
 
 /* struct mtk_mac -    the structure that holds the info about the MACs of the
@@ -958,4 +961,9 @@ int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id);
 int mtk_gmac_gephy_path_setup(struct mtk_eth *eth, int mac_id);
 int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id);
 
+int mtk_eth_offload_init(struct mtk_eth *eth);
+int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
+                    void *type_data);
+
+
 #endif /* MTK_ETH_H */
diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
new file mode 100644 (file)
index 0000000..d0c4678
--- /dev/null
@@ -0,0 +1,485 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
+ */
+
+#include <linux/if_ether.h>
+#include <linux/rhashtable.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <net/flow_offload.h>
+#include <net/pkt_cls.h>
+#include <net/dsa.h>
+#include "mtk_eth_soc.h"
+
+struct mtk_flow_data {
+       struct ethhdr eth;
+
+       union {
+               struct {
+                       __be32 src_addr;
+                       __be32 dst_addr;
+               } v4;
+       };
+
+       __be16 src_port;
+       __be16 dst_port;
+
+       struct {
+               u16 id;
+               __be16 proto;
+               u8 num;
+       } vlan;
+       struct {
+               u16 sid;
+               u8 num;
+       } pppoe;
+};
+
+struct mtk_flow_entry {
+       struct rhash_head node;
+       unsigned long cookie;
+       u16 hash;
+};
+
+static const struct rhashtable_params mtk_flow_ht_params = {
+       .head_offset = offsetof(struct mtk_flow_entry, node),
+       .head_offset = offsetof(struct mtk_flow_entry, cookie),
+       .key_len = sizeof(unsigned long),
+       .automatic_shrinking = true,
+};
+
+static u32
+mtk_eth_timestamp(struct mtk_eth *eth)
+{
+       return mtk_r32(eth, 0x0010) & MTK_FOE_IB1_BIND_TIMESTAMP;
+}
+
+static int
+mtk_flow_set_ipv4_addr(struct mtk_foe_entry *foe, struct mtk_flow_data *data,
+                      bool egress)
+{
+       return mtk_foe_entry_set_ipv4_tuple(foe, egress,
+                                           data->v4.src_addr, data->src_port,
+                                           data->v4.dst_addr, data->dst_port);
+}
+
+static void
+mtk_flow_offload_mangle_eth(const struct flow_action_entry *act, void *eth)
+{
+       void *dest = eth + act->mangle.offset;
+       const void *src = &act->mangle.val;
+
+       if (act->mangle.offset > 8)
+               return;
+
+       if (act->mangle.mask == 0xffff) {
+               src += 2;
+               dest += 2;
+       }
+
+       memcpy(dest, src, act->mangle.mask ? 2 : 4);
+}
+
+
+static int
+mtk_flow_mangle_ports(const struct flow_action_entry *act,
+                     struct mtk_flow_data *data)
+{
+       u32 val = ntohl(act->mangle.val);
+
+       switch (act->mangle.offset) {
+       case 0:
+               if (act->mangle.mask == ~htonl(0xffff))
+                       data->dst_port = cpu_to_be16(val);
+               else
+                       data->src_port = cpu_to_be16(val >> 16);
+               break;
+       case 2:
+               data->dst_port = cpu_to_be16(val);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int
+mtk_flow_mangle_ipv4(const struct flow_action_entry *act,
+                    struct mtk_flow_data *data)
+{
+       __be32 *dest;
+
+       switch (act->mangle.offset) {
+       case offsetof(struct iphdr, saddr):
+               dest = &data->v4.src_addr;
+               break;
+       case offsetof(struct iphdr, daddr):
+               dest = &data->v4.dst_addr;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       memcpy(dest, &act->mangle.val, sizeof(u32));
+
+       return 0;
+}
+
+static int
+mtk_flow_get_dsa_port(struct net_device **dev)
+{
+#if IS_ENABLED(CONFIG_NET_DSA)
+       struct dsa_port *dp;
+
+       dp = dsa_port_from_netdev(*dev);
+       if (IS_ERR(dp))
+               return -ENODEV;
+
+       if (dp->cpu_dp->tag_ops->proto != DSA_TAG_PROTO_MTK)
+               return -ENODEV;
+
+       *dev = dp->cpu_dp->master;
+
+       return dp->index;
+#else
+       return -ENODEV;
+#endif
+}
+
+static int
+mtk_flow_set_output_device(struct mtk_eth *eth, struct mtk_foe_entry *foe,
+                          struct net_device *dev)
+{
+       int pse_port, dsa_port;
+
+       dsa_port = mtk_flow_get_dsa_port(&dev);
+       if (dsa_port >= 0)
+               mtk_foe_entry_set_dsa(foe, dsa_port);
+
+       if (dev == eth->netdev[0])
+               pse_port = 1;
+       else if (dev == eth->netdev[1])
+               pse_port = 2;
+       else
+               return -EOPNOTSUPP;
+
+       mtk_foe_entry_set_pse_port(foe, pse_port);
+
+       return 0;
+}
+
+static int
+mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f)
+{
+       struct flow_rule *rule = flow_cls_offload_flow_rule(f);
+       struct flow_action_entry *act;
+       struct mtk_flow_data data = {};
+       struct mtk_foe_entry foe;
+       struct net_device *odev = NULL;
+       struct mtk_flow_entry *entry;
+       int offload_type = 0;
+       u16 addr_type = 0;
+       u32 timestamp;
+       u8 l4proto = 0;
+       int err = 0;
+       int hash;
+       int i;
+
+       if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_META)) {
+               struct flow_match_meta match;
+
+               flow_rule_match_meta(rule, &match);
+       } else {
+               return -EOPNOTSUPP;
+       }
+
+       if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
+               struct flow_match_control match;
+
+               flow_rule_match_control(rule, &match);
+               addr_type = match.key->addr_type;
+       } else {
+               return -EOPNOTSUPP;
+       }
+
+       if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
+               struct flow_match_basic match;
+
+               flow_rule_match_basic(rule, &match);
+               l4proto = match.key->ip_proto;
+       } else {
+               return -EOPNOTSUPP;
+       }
+
+       flow_action_for_each(i, act, &rule->action) {
+               switch (act->id) {
+               case FLOW_ACTION_MANGLE:
+                       if (act->mangle.htype == FLOW_ACT_MANGLE_HDR_TYPE_ETH)
+                               mtk_flow_offload_mangle_eth(act, &data.eth);
+                       break;
+               case FLOW_ACTION_REDIRECT:
+                       odev = act->dev;
+                       break;
+               case FLOW_ACTION_CSUM:
+                       break;
+               case FLOW_ACTION_VLAN_PUSH:
+                       if (data.vlan.num == 1 ||
+                           act->vlan.proto != htons(ETH_P_8021Q))
+                               return -EOPNOTSUPP;
+
+                       data.vlan.id = act->vlan.vid;
+                       data.vlan.proto = act->vlan.proto;
+                       data.vlan.num++;
+                       break;
+               case FLOW_ACTION_PPPOE_PUSH:
+                       if (data.pppoe.num == 1)
+                               return -EOPNOTSUPP;
+
+                       data.pppoe.sid = act->pppoe.sid;
+                       data.pppoe.num++;
+                       break;
+               default:
+                       return -EOPNOTSUPP;
+               }
+       }
+
+       switch (addr_type) {
+       case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
+               offload_type = MTK_PPE_PKT_TYPE_IPV4_HNAPT;
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       if (!is_valid_ether_addr(data.eth.h_source) ||
+           !is_valid_ether_addr(data.eth.h_dest))
+               return -EINVAL;
+
+       err = mtk_foe_entry_prepare(&foe, offload_type, l4proto, 0,
+                                   data.eth.h_source,
+                                   data.eth.h_dest);
+       if (err)
+               return err;
+
+       if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
+               struct flow_match_ports ports;
+
+               flow_rule_match_ports(rule, &ports);
+               data.src_port = ports.key->src;
+               data.dst_port = ports.key->dst;
+       } else {
+               return -EOPNOTSUPP;
+       }
+
+       if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
+               struct flow_match_ipv4_addrs addrs;
+
+               flow_rule_match_ipv4_addrs(rule, &addrs);
+
+               data.v4.src_addr = addrs.key->src;
+               data.v4.dst_addr = addrs.key->dst;
+
+               mtk_flow_set_ipv4_addr(&foe, &data, false);
+       }
+
+       flow_action_for_each(i, act, &rule->action) {
+               if (act->id != FLOW_ACTION_MANGLE)
+                       continue;
+
+               switch (act->mangle.htype) {
+               case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
+               case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
+                       err = mtk_flow_mangle_ports(act, &data);
+                       break;
+               case FLOW_ACT_MANGLE_HDR_TYPE_IP4:
+                       err = mtk_flow_mangle_ipv4(act, &data);
+                       break;
+               case FLOW_ACT_MANGLE_HDR_TYPE_ETH:
+                       /* handled earlier */
+                       break;
+               default:
+                       return -EOPNOTSUPP;
+               }
+
+               if (err)
+                       return err;
+       }
+
+       if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
+               err = mtk_flow_set_ipv4_addr(&foe, &data, true);
+               if (err)
+                       return err;
+       }
+
+       if (data.vlan.num == 1) {
+               if (data.vlan.proto != htons(ETH_P_8021Q))
+                       return -EOPNOTSUPP;
+
+               mtk_foe_entry_set_vlan(&foe, data.vlan.id);
+       }
+       if (data.pppoe.num == 1)
+               mtk_foe_entry_set_pppoe(&foe, data.pppoe.sid);
+
+       err = mtk_flow_set_output_device(eth, &foe, odev);
+       if (err)
+               return err;
+
+       entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+               return -ENOMEM;
+
+       entry->cookie = f->cookie;
+       timestamp = mtk_eth_timestamp(eth);
+       hash = mtk_foe_entry_commit(&eth->ppe, &foe, timestamp);
+       if (hash < 0) {
+               err = hash;
+               goto free;
+       }
+
+       entry->hash = hash;
+       err = rhashtable_insert_fast(&eth->flow_table, &entry->node,
+                                    mtk_flow_ht_params);
+       if (err < 0)
+               goto clear_flow;
+
+       return 0;
+clear_flow:
+       mtk_foe_entry_clear(&eth->ppe, hash);
+free:
+       kfree(entry);
+       return err;
+}
+
+static int
+mtk_flow_offload_destroy(struct mtk_eth *eth, struct flow_cls_offload *f)
+{
+       struct mtk_flow_entry *entry;
+
+       entry = rhashtable_lookup(&eth->flow_table, &f->cookie,
+                                 mtk_flow_ht_params);
+       if (!entry)
+               return -ENOENT;
+
+       mtk_foe_entry_clear(&eth->ppe, entry->hash);
+       rhashtable_remove_fast(&eth->flow_table, &entry->node,
+                              mtk_flow_ht_params);
+       kfree(entry);
+
+       return 0;
+}
+
+static int
+mtk_flow_offload_stats(struct mtk_eth *eth, struct flow_cls_offload *f)
+{
+       struct mtk_flow_entry *entry;
+       int timestamp;
+       u32 idle;
+
+       entry = rhashtable_lookup(&eth->flow_table, &f->cookie,
+                                 mtk_flow_ht_params);
+       if (!entry)
+               return -ENOENT;
+
+       timestamp = mtk_foe_entry_timestamp(&eth->ppe, entry->hash);
+       if (timestamp < 0)
+               return -ETIMEDOUT;
+
+       idle = mtk_eth_timestamp(eth) - timestamp;
+       f->stats.lastused = jiffies - idle * HZ;
+
+       return 0;
+}
+
+static int
+mtk_eth_setup_tc_block_cb(enum tc_setup_type type, void *type_data, void *cb_priv)
+{
+       struct flow_cls_offload *cls = type_data;
+       struct net_device *dev = cb_priv;
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth = mac->hw;
+
+       if (!tc_can_offload(dev))
+               return -EOPNOTSUPP;
+
+       if (type != TC_SETUP_CLSFLOWER)
+               return -EOPNOTSUPP;
+
+       switch (cls->command) {
+       case FLOW_CLS_REPLACE:
+               return mtk_flow_offload_replace(eth, cls);
+       case FLOW_CLS_DESTROY:
+               return mtk_flow_offload_destroy(eth, cls);
+       case FLOW_CLS_STATS:
+               return mtk_flow_offload_stats(eth, cls);
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static int
+mtk_eth_setup_tc_block(struct net_device *dev, struct flow_block_offload *f)
+{
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth = mac->hw;
+       static LIST_HEAD(block_cb_list);
+       struct flow_block_cb *block_cb;
+       flow_setup_cb_t *cb;
+
+       if (!eth->ppe.foe_table)
+               return -EOPNOTSUPP;
+
+       if (f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
+               return -EOPNOTSUPP;
+
+       cb = mtk_eth_setup_tc_block_cb;
+       f->driver_block_list = &block_cb_list;
+
+       switch (f->command) {
+       case FLOW_BLOCK_BIND:
+               block_cb = flow_block_cb_lookup(f->block, cb, dev);
+               if (block_cb) {
+                       flow_block_cb_incref(block_cb);
+                       return 0;
+               }
+               block_cb = flow_block_cb_alloc(cb, dev, dev, NULL);
+               if (IS_ERR(block_cb))
+                       return PTR_ERR(block_cb);
+
+               flow_block_cb_add(block_cb, f);
+               list_add_tail(&block_cb->driver_list, &block_cb_list);
+               return 0;
+       case FLOW_BLOCK_UNBIND:
+               block_cb = flow_block_cb_lookup(f->block, cb, dev);
+               if (!block_cb)
+                       return -ENOENT;
+
+               if (flow_block_cb_decref(block_cb)) {
+                       flow_block_cb_remove(block_cb, f);
+                       list_del(&block_cb->driver_list);
+               }
+               return 0;
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
+                    void *type_data)
+{
+       if (type == TC_SETUP_FT)
+               return mtk_eth_setup_tc_block(dev, type_data);
+
+       return -EOPNOTSUPP;
+}
+
+int mtk_eth_offload_init(struct mtk_eth *eth)
+{
+       if (!eth->ppe.foe_table)
+               return 0;
+
+       return rhashtable_init(&eth->flow_table, &mtk_flow_ht_params);
+}