From: Wei Fang <[email protected]>

Implement the DSA .port_vlan_add and .port_vlan_del operations to
enable VLAN-aware bridge offloading on the NETC switch.

VLAN membership is maintained in the VLAN Filter Table (VFT). Adding
the first port to a VLAN creates a new VFT entry with hardware MAC
learning and flood-on-miss forwarding; subsequent ports update the
existing entry's membership bitmap. Removing the last port deletes
the entry.

Egress tagging is handled through the Egress Treatment Table (ETT).
Each VLAN is allocated a group of ETT entries, one per available port.
Ports are assigned a sequential ett_offset during initialisation, used
to address each port's entry within the group. Untagged ports configure
the ETT to strip the outer VLAN tag; tagged ports pass frames through
unmodified. Each ETT group is optionally paired with an Egress Counter
Table (ECT) group for per-port frame counting, allocated on a
best-effort basis.

A software shadow list serialised by vft_lock tracks active VLAN state
across both port membership and egress tagging. VID 0 is used for single
port mode and is ignored by both callbacks.

Signed-off-by: Wei Fang <[email protected]>
---
 drivers/net/dsa/netc/netc_main.c   | 423 +++++++++++++++++++++++++++++
 drivers/net/dsa/netc/netc_switch.h |  24 ++
 include/linux/fsl/ntmp.h           |  15 +
 3 files changed, 462 insertions(+)

diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index d4475ad7ed6c..4db42c888470 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -37,6 +37,27 @@ static void netc_destroy_fdb_list(struct netc_switch *priv)
                netc_del_fdb_entry(entry);
 }
 
+static struct netc_vlan_entry *
+netc_lookup_vlan_entry(struct netc_switch *priv, u16 vid)
+{
+       struct netc_vlan_entry *entry;
+
+       hlist_for_each_entry(entry, &priv->vlan_list, node)
+               if (entry->vid == vid)
+                       return entry;
+
+       return NULL;
+}
+
+static void netc_destroy_vlan_list(struct netc_switch *priv)
+{
+       struct netc_vlan_entry *entry;
+       struct hlist_node *tmp;
+
+       hlist_for_each_entry_safe(entry, tmp, &priv->vlan_list, node)
+               netc_del_vlan_entry(entry);
+}
+
 static enum dsa_tag_protocol
 netc_get_tag_protocol(struct dsa_switch *ds, int port,
                      enum dsa_tag_protocol mprot)
@@ -222,6 +243,7 @@ static int netc_init_all_ports(struct netc_switch *priv)
        struct device *dev = priv->dev;
        struct netc_port *np;
        struct dsa_port *dp;
+       int ett_offset = 0;
        int err;
 
        priv->ports = devm_kcalloc(dev, priv->info->num_ports,
@@ -251,6 +273,8 @@ static int netc_init_all_ports(struct netc_switch *priv)
        dsa_switch_for_each_available_port(dp, priv->ds) {
                np = priv->ports[dp->index];
                np->dp = dp;
+               np->ett_offset = ett_offset++;
+               priv->port_bitmap |= BIT(dp->index);
 
                err = netc_port_get_info_from_dt(np, dp->dn, dev);
                if (err)
@@ -831,6 +855,8 @@ static int netc_setup(struct dsa_switch *ds)
 
        INIT_HLIST_HEAD(&priv->fdb_list);
        mutex_init(&priv->fdbt_lock);
+       INIT_HLIST_HEAD(&priv->vlan_list);
+       mutex_init(&priv->vft_lock);
 
        netc_switch_fixed_config(priv);
 
@@ -858,6 +884,7 @@ static int netc_setup(struct dsa_switch *ds)
         * hardware state.
         */
        mutex_destroy(&priv->fdbt_lock);
+       mutex_destroy(&priv->vft_lock);
        netc_free_ntmp_user(priv);
 
        return err;
@@ -867,6 +894,8 @@ static void netc_destroy_all_lists(struct netc_switch *priv)
 {
        netc_destroy_fdb_list(priv);
        mutex_destroy(&priv->fdbt_lock);
+       netc_destroy_vlan_list(priv);
+       mutex_destroy(&priv->vft_lock);
 }
 
 static void netc_free_host_flood_rules(struct netc_switch *priv)
@@ -1025,6 +1054,369 @@ static void netc_switch_get_ip_revision(struct 
netc_switch *priv)
        priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
 }
 
+static int netc_add_or_update_ett_entry(struct netc_switch *priv,
+                                       bool add, bool untagged,
+                                       u32 ett_eid, u32 ect_eid)
+{
+       struct ntmp_user *ntmp = &priv->ntmp;
+       u32 vuda_sqta = FMTEID_VUDA_SQTA;
+       struct ett_cfge_data cfge = {};
+       u16 efm_cfg = 0;
+
+       if (ect_eid != NTMP_NULL_ENTRY_ID) {
+               /* Increase egress frame counter */
+               efm_cfg |= FIELD_PREP(ETT_ECA, ETT_ECA_INC);
+               cfge.ec_eid = cpu_to_le32(ect_eid);
+       }
+
+       /* If egress rule is VLAN untagged */
+       if (untagged) {
+               /* delete outer VLAN tag */
+               vuda_sqta |= FIELD_PREP(FMTEID_VUDA, FMTEID_VUDA_DEL_OTAG);
+               /* length change: twos-complement notation */
+               efm_cfg |= FIELD_PREP(ETT_EFM_LEN_CHANGE,
+                                     ETT_FRM_LEN_DEL_VLAN);
+       }
+
+       cfge.efm_eid = cpu_to_le32(vuda_sqta);
+       cfge.efm_cfg = cpu_to_le16(efm_cfg);
+
+       return ntmp_ett_add_or_update_entry(ntmp, ett_eid, add, &cfge);
+}
+
+static int netc_add_ett_group_entries(struct netc_switch *priv,
+                                     u32 untagged_port_bitmap,
+                                     u32 ett_base_eid,
+                                     u32 ect_base_eid)
+{
+       struct netc_port **ports = priv->ports;
+       u32 ett_eid, ect_eid;
+       bool untagged;
+       int i, err;
+
+       for (i = 0; i < priv->info->num_ports; i++) {
+               if (!ports[i]->dp)
+                       continue;
+
+               untagged = !!(untagged_port_bitmap & BIT(i));
+               ett_eid = ett_base_eid + ports[i]->ett_offset;
+               ect_eid = NTMP_NULL_ENTRY_ID;
+               if (ect_base_eid != NTMP_NULL_ENTRY_ID)
+                       ect_eid = ect_base_eid + ports[i]->ett_offset;
+
+               err = netc_add_or_update_ett_entry(priv, true, untagged,
+                                                  ett_eid, ect_eid);
+               if (err)
+                       goto clear_ett_entries;
+       }
+
+       return 0;
+
+clear_ett_entries:
+       while (--i >= 0) {
+               if (!ports[i]->dp)
+                       continue;
+
+               ett_eid = ett_base_eid + ports[i]->ett_offset;
+               ntmp_ett_delete_entry(&priv->ntmp, ett_eid);
+       }
+
+       return err;
+}
+
+static int netc_add_vlan_egress_rule(struct netc_switch *priv,
+                                    struct netc_vlan_entry *entry)
+{
+       u32 num_ports = netc_num_available_ports(priv);
+       struct ntmp_user *ntmp = &priv->ntmp;
+       u32 ect_eid = NTMP_NULL_ENTRY_ID;
+       u32 ett_eid, ett_gid, ect_gid;
+       int err;
+
+       /* Step 1: Find available egress counter table entries and update
+        * these entries.
+        */
+       ect_gid = ntmp_lookup_free_eid(ntmp->ect_gid_bitmap,
+                                      ntmp->ect_bitmap_size);
+       if (ect_gid == NTMP_NULL_ENTRY_ID) {
+               dev_info(priv->dev,
+                        "No egress counter table entries available\n");
+       } else {
+               ect_eid = ect_gid * num_ports;
+               for (int i = 0; i < num_ports; i++)
+                       /* Reset the counters of the entry. There is no need
+                        * to check the return value, the only issue is that
+                        * the entry's counter might be inaccurate, but it
+                        * will not affect the functionality.
+                        */
+                       ntmp_ect_update_entry(ntmp, ect_eid + i);
+       }
+
+       /* Step 2: Find available egress treatment table entries and add
+        * these entries.
+        */
+       ett_gid = ntmp_lookup_free_eid(ntmp->ett_gid_bitmap,
+                                      ntmp->ett_bitmap_size);
+       if (ett_gid == NTMP_NULL_ENTRY_ID) {
+               dev_err(priv->dev,
+                       "No egress treatment table entries available\n");
+               err = -ENOSPC;
+               goto clear_ect_gid;
+       }
+
+       ett_eid = ett_gid * num_ports;
+       err = netc_add_ett_group_entries(priv, entry->untagged_port_bitmap,
+                                        ett_eid, ect_eid);
+       if (err)
+               goto clear_ett_gid;
+
+       entry->cfge.et_eid = cpu_to_le32(ett_eid);
+       entry->ect_gid = ect_gid;
+
+       return 0;
+
+clear_ett_gid:
+       ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid);
+
+clear_ect_gid:
+       if (ect_gid != NTMP_NULL_ENTRY_ID)
+               ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, ect_gid);
+
+       return err;
+}
+
+static void netc_delete_vlan_egress_rule(struct netc_switch *priv,
+                                        struct netc_vlan_entry *entry)
+{
+       u32 num_ports = netc_num_available_ports(priv);
+       struct ntmp_user *ntmp = &priv->ntmp;
+       u32 ett_eid, ett_gid;
+
+       ett_eid = le32_to_cpu(entry->cfge.et_eid);
+       if (ett_eid == NTMP_NULL_ENTRY_ID)
+               return;
+
+       ett_gid = ett_eid / num_ports;
+       ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid);
+       for (int i = 0; i < num_ports; i++)
+               ntmp_ett_delete_entry(ntmp, ett_eid + i);
+
+       if (entry->ect_gid == NTMP_NULL_ENTRY_ID)
+               return;
+
+       ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, entry->ect_gid);
+}
+
+static int netc_port_update_vlan_egress_rule(struct netc_port *np,
+                                            struct netc_vlan_entry *entry)
+{
+       bool untagged = !!(entry->untagged_port_bitmap & BIT(np->dp->index));
+       u32 num_ports = netc_num_available_ports(np->switch_priv);
+       u32 ett_eid = le32_to_cpu(entry->cfge.et_eid);
+       struct netc_switch *priv = np->switch_priv;
+       u32 ect_eid = NTMP_NULL_ENTRY_ID;
+       int err;
+
+       if (ett_eid == NTMP_NULL_ENTRY_ID)
+               return 0;
+
+       if (entry->ect_gid != NTMP_NULL_ENTRY_ID) {
+               /* Each ETT entry maps to an ECT entry if ect_gid is not NULL
+                * entry ID. The offset of the ECT entry corresponding to the
+                * port in the group is equal to ett_offset.
+                */
+               ect_eid = entry->ect_gid * num_ports + np->ett_offset;
+               ntmp_ect_update_entry(&priv->ntmp, ect_eid);
+       }
+
+       ett_eid += np->ett_offset;
+       err = netc_add_or_update_ett_entry(priv, false, untagged,
+                                          ett_eid, ect_eid);
+       if (err)
+               dev_err(priv->dev,
+                       "Failed to update VLAN %u egress rule on port %d\n",
+                       entry->vid, np->dp->index);
+
+       return err;
+}
+
+static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid,
+                                   bool untagged)
+{
+       struct netc_switch *priv = np->switch_priv;
+       struct netc_vlan_entry *entry;
+       struct vft_cfge_data *cfge;
+       u32 index = np->dp->index;
+       u32 bitmap_stg;
+       int err;
+       u16 cfg;
+
+       entry = kzalloc_obj(*entry);
+       if (!entry)
+               return -ENOMEM;
+
+       entry->vid = vid;
+       entry->ect_gid = NTMP_NULL_ENTRY_ID;
+
+       bitmap_stg = BIT(index) | VFT_STG_ID(0);
+       cfg = FIELD_PREP(VFT_MLO, MLO_HW) |
+             FIELD_PREP(VFT_MFO, MFO_NO_MATCH_FLOOD);
+
+       cfge = &entry->cfge;
+       cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+       cfge->bitmap_stg = cpu_to_le32(bitmap_stg);
+       cfge->fid = cpu_to_le16(vid);
+       cfge->cfg = cpu_to_le16(cfg);
+       cfge->eta_port_bitmap = cpu_to_le32(priv->port_bitmap);
+
+       if (untagged)
+               entry->untagged_port_bitmap = BIT(index);
+
+       err = netc_add_vlan_egress_rule(priv, entry);
+       if (err)
+               goto free_vlan_entry;
+
+       err = ntmp_vft_add_entry(&priv->ntmp, vid, cfge);
+       if (err) {
+               dev_err(priv->dev,
+                       "Failed to add VLAN %u entry on port %d\n",
+                       vid, index);
+               goto delete_vlan_egress_rule;
+       }
+
+       netc_add_vlan_entry(priv, entry);
+
+       return 0;
+
+delete_vlan_egress_rule:
+       netc_delete_vlan_egress_rule(priv, entry);
+free_vlan_entry:
+       kfree(entry);
+
+       return err;
+}
+
+static bool netc_port_vlan_egress_rule_changed(struct netc_vlan_entry *entry,
+                                              int port, bool untagged)
+{
+       bool old_untagged = !!(entry->untagged_port_bitmap & BIT(port));
+
+       return old_untagged != untagged;
+}
+
+static int netc_port_set_vlan_entry(struct netc_port *np, u16 vid,
+                                   bool untagged)
+{
+       struct netc_switch *priv = np->switch_priv;
+       struct netc_vlan_entry *entry;
+       struct vft_cfge_data *cfge;
+       int port = np->dp->index;
+       bool changed;
+       int err = 0;
+
+       mutex_lock(&priv->vft_lock);
+
+       entry = netc_lookup_vlan_entry(priv, vid);
+       if (!entry) {
+               err = netc_port_add_vlan_entry(np, vid, untagged);
+               goto unlock_vft;
+       }
+
+       /* Check whether the egress VLAN rule is changed */
+       changed = netc_port_vlan_egress_rule_changed(entry, port, untagged);
+       if (changed) {
+               entry->untagged_port_bitmap ^= BIT(port);
+               err = netc_port_update_vlan_egress_rule(np, entry);
+               if (err) {
+                       entry->untagged_port_bitmap ^= BIT(port);
+                       goto unlock_vft;
+               }
+       }
+
+       cfge = &entry->cfge;
+       if (cfge->bitmap_stg & cpu_to_le32(BIT(port)))
+               goto unlock_vft;
+
+       cfge->bitmap_stg |= cpu_to_le32(BIT(port));
+       err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge);
+       if (err) {
+               dev_err(priv->dev,
+                       "Failed to update VLAN %u entry on port %d\n",
+                       vid, port);
+
+               goto restore_bitmap_stg;
+       }
+
+       mutex_unlock(&priv->vft_lock);
+
+       return 0;
+
+restore_bitmap_stg:
+       cfge->bitmap_stg &= cpu_to_le32(~BIT(port));
+       if (changed) {
+               entry->untagged_port_bitmap ^= BIT(port);
+               /* Recover the corresponding ETT entry. It doesn't matter
+                * if it fails because the bit corresponding to the port
+                * in the port bitmap of the VFT entry is not set. so the
+                * frame will not match that ETT entry.
+                */
+               if (netc_port_update_vlan_egress_rule(np, entry))
+                       entry->untagged_port_bitmap ^= BIT(port);
+       }
+unlock_vft:
+       mutex_unlock(&priv->vft_lock);
+
+       return err;
+}
+
+static int netc_port_del_vlan_entry(struct netc_port *np, u16 vid)
+{
+       struct netc_switch *priv = np->switch_priv;
+       struct netc_vlan_entry *entry;
+       struct vft_cfge_data *cfge;
+       int port = np->dp->index;
+       u32 vlan_port_bitmap;
+       int err = 0;
+
+       mutex_lock(&priv->vft_lock);
+
+       entry = netc_lookup_vlan_entry(priv, vid);
+       if (!entry)
+               goto unlock_vft;
+
+       cfge = &entry->cfge;
+       vlan_port_bitmap = FIELD_GET(VFT_PORT_MEMBERSHIP,
+                                    le32_to_cpu(cfge->bitmap_stg));
+       /* If the VLAN only belongs to the current port */
+       if (vlan_port_bitmap == BIT(port)) {
+               err = ntmp_vft_delete_entry(&priv->ntmp, vid);
+               if (err)
+                       goto unlock_vft;
+
+               netc_delete_vlan_egress_rule(priv, entry);
+               netc_del_vlan_entry(entry);
+
+               goto unlock_vft;
+       }
+
+       if (!(vlan_port_bitmap & BIT(port)))
+               goto unlock_vft;
+
+       cfge->bitmap_stg &= cpu_to_le32(~BIT(port));
+       err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge);
+       if (err) {
+               cfge->bitmap_stg |= cpu_to_le32(BIT(port));
+               goto unlock_vft;
+       }
+
+       entry->untagged_port_bitmap &= ~BIT(port);
+
+unlock_vft:
+       mutex_unlock(&priv->vft_lock);
+
+       return err;
+}
+
 static int netc_port_enable(struct dsa_switch *ds, int port,
                            struct phy_device *phy)
 {
@@ -1297,6 +1689,35 @@ static void netc_port_set_host_flood(struct dsa_switch 
*ds, int port,
        netc_port_remove_host_flood(np, old_host_flood);
 }
 
+static int netc_port_vlan_add(struct dsa_switch *ds, int port,
+                             const struct switchdev_obj_port_vlan *vlan,
+                             struct netlink_ext_ack *extack)
+{
+       struct netc_port *np = NETC_PORT(ds, port);
+       bool untagged;
+
+       /* The 8021q layer may attempt to change NETC_STANDALONE_PVID
+        * (VID 0), so we need to ignore it.
+        */
+       if (vlan->vid == NETC_STANDALONE_PVID)
+               return 0;
+
+       untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+
+       return netc_port_set_vlan_entry(np, vlan->vid, untagged);
+}
+
+static int netc_port_vlan_del(struct dsa_switch *ds, int port,
+                             const struct switchdev_obj_port_vlan *vlan)
+{
+       struct netc_port *np = NETC_PORT(ds, port);
+
+       if (vlan->vid == NETC_STANDALONE_PVID)
+               return 0;
+
+       return netc_port_del_vlan_entry(np, vlan->vid);
+}
+
 static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
                                  struct phylink_config *config)
 {
@@ -1575,6 +1996,8 @@ static const struct dsa_switch_ops netc_switch_ops = {
        .port_mdb_add                   = netc_port_mdb_add,
        .port_mdb_del                   = netc_port_mdb_del,
        .port_set_host_flood            = netc_port_set_host_flood,
+       .port_vlan_add                  = netc_port_vlan_add,
+       .port_vlan_del                  = netc_port_vlan_del,
        .get_pause_stats                = netc_port_get_pause_stats,
        .get_rmon_stats                 = netc_port_get_rmon_stats,
        .get_eth_ctrl_stats             = netc_port_get_eth_ctrl_stats,
diff --git a/drivers/net/dsa/netc/netc_switch.h 
b/drivers/net/dsa/netc/netc_switch.h
index 4fbd12825b67..9ff334301fbc 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -74,6 +74,7 @@ struct netc_port {
        struct dsa_port *dp;
        struct clk *ref_clk; /* RGMII/RMII reference clock */
        struct mii_bus *emdio;
+       int ett_offset;
 
        u16 enable:1;
        u16 uc:1;
@@ -94,6 +95,14 @@ struct netc_fdb_entry {
        struct hlist_node node;
 };
 
+struct netc_vlan_entry {
+       u16 vid;
+       u32 ect_gid;
+       u32 untagged_port_bitmap;
+       struct vft_cfge_data cfge;
+       struct hlist_node node;
+};
+
 struct netc_port_stat {
        int reg;
        char name[ETH_GSTRING_LEN] __nonstring;
@@ -108,10 +117,13 @@ struct netc_switch {
        const struct netc_switch_info *info;
        struct netc_switch_regs regs;
        struct netc_port **ports;
+       u32 port_bitmap; /* bitmap of available ports */
 
        struct ntmp_user ntmp;
        struct hlist_head fdb_list;
        struct mutex fdbt_lock; /* FDB table lock */
+       struct hlist_head vlan_list;
+       struct mutex vft_lock; /* VLAN filter table lock */
 
        /* Switch hardware capabilities */
        u32 htmcapr_num_words;
@@ -153,6 +165,18 @@ static inline void netc_del_fdb_entry(struct 
netc_fdb_entry *entry)
        kfree(entry);
 }
 
+static inline void netc_add_vlan_entry(struct netc_switch *priv,
+                                      struct netc_vlan_entry *entry)
+{
+       hlist_add_head(&entry->node, &priv->vlan_list);
+}
+
+static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry)
+{
+       hlist_del(&entry->node);
+       kfree(entry);
+}
+
 int netc_switch_platform_probe(struct netc_switch *priv);
 
 /* ethtool APIs */
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index a678f1e1ee42..7677e7151fd9 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -267,6 +267,21 @@ struct bpt_cfge_data {
        __le32 fc_ports;
 };
 
+union ntmp_fmt_eid {
+       __le32 index;
+#define        FMTEID_INDEX            GENMASK(12, 0)
+       __le32 vuda_sqta;
+#define FMTEID_VUDA            GENMASK(1, 0)
+#define FMTEID_VUDA_DEL_OTAG   2
+#define FMTEID_SQTA            GENMASK(4, 2)
+#define FMTEID_SQTA_DEL                2
+#define FMTEID_VUDA_SQTA       BIT(13)
+       __le32 vara_vid;
+#define FMTEID_VID             GENMASK(11, 0)
+#define FMTEID_VARA            GENMASK(13, 12)
+#define FMTEID_VARA_VID                BIT(14)
+};
+
 #if IS_ENABLED(CONFIG_NXP_NETC_LIB)
 int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
                   const struct netc_cbdr_regs *regs);
-- 
2.34.1


Reply via email to