From: Wei Fang <[email protected]>

Wire up the port_bridge_join, port_bridge_leave and port_vlan_filtering
DSA callbacks to support both VLAN-unaware and VLAN-aware bridge modes.

For VLAN-unaware bridges, each bridge instance is assigned a dedicated
internal PVID via NETC_VLAN_UNAWARE_PVID(bridge.num), counting down
from VID 4095. A VFT entry is created for this PVID with hardware MAC
learning and flood-on-miss forwarding enabled. The CPU port is included
as a VFT member so frames can reach the host. The reserved VID range is
blocked in port_vlan_add to prevent user-space conflicts.

Only one VLAN-aware bridge is supported at a time; this constraint is
enforced in port_bridge_join and port_vlan_filtering. The per-port PVID
is tracked in software and written to the BPDVR register whenever VLAN
filtering is active.

FDB operations are extended to the bridge database: when vid is zero the
VLAN-unaware PVID for the bridge is substituted. Dynamic entries learned
autonomously by the hardware are handled by falling back to a
key-element-data delete via ntmp_fdbt_delete_entry_by_keye() when an
entry is absent from the software shadow list. Internal PVIDs are
translated back to VID 0 in port_fdb_dump before reporting to
user-space.

Host flood rules are removed from the ingress port filter table when a
port joins a bridge to avoid bypassing FDB lookup and MAC learning.

Signed-off-by: Wei Fang <[email protected]>
---
 drivers/net/dsa/netc/netc_main.c   | 310 +++++++++++++++++++++++++++--
 drivers/net/dsa/netc/netc_switch.h |   2 +
 2 files changed, 297 insertions(+), 15 deletions(-)

diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index a97121dda237..5b58ce06beb8 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -695,10 +695,16 @@ static int netc_port_del_fdb_entry(struct netc_port *np,
 
        entry = netc_lookup_fdb_entry(priv, addr, vid);
        if (unlikely(!entry))
-               /* Currently only single port mode is supported, MAC learning
-                * is disabled, so there is no dynamically learned FDB entry.
-                * We need to support deleting dynamically FDB entry when the
-                * bridge mode is supported.
+               /* The hardware-learned dynamic FDB entries cannot be deleted
+                * through .port_fdb_del() interface.
+                * For NTF_MASTER path: Since hardware-learned dynamic FDB
+                * entries are never synchronized back to the bridge software
+                * database. br_fdb_delete() -> br_fdb_find() cannot find the
+                * FDB entry, so .port_fdb_del() will not be called.
+                * For NTF_SELF path: dsa_user_netdev_ops does not implement
+                * ndo_fdb_del(), so rtnl_fdb_del() falls back to
+                * ndo_dflt_fdb_del(), which only supports NUD_PERMANENT static
+                * entries and rejects all others with -EINVAL.
                 */
                goto unlock_fdbt;
 
@@ -1274,6 +1280,16 @@ static int netc_port_add_vlan_entry(struct netc_port 
*np, u16 vid,
        entry->ect_gid = NTMP_NULL_ENTRY_ID;
 
        bitmap_stg = BIT(index) | VFT_STG_ID(0);
+       /* If the VID is a VLAN-unaware PVID, the CPU port needs to be
+        * a member of this VLAN.
+        */
+       if (dsa_port_is_user(np->dp) &&
+           vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) {
+               struct dsa_port *cpu_dp = np->dp->cpu_dp;
+
+               bitmap_stg |= BIT(cpu_dp->index);
+       }
+
        cfg = FIELD_PREP(VFT_MLO, MLO_HW) |
              FIELD_PREP(VFT_MFO, MFO_NO_MATCH_FLOOD);
 
@@ -1311,11 +1327,16 @@ static int netc_port_add_vlan_entry(struct netc_port 
*np, u16 vid,
        return err;
 }
 
-static bool netc_port_vlan_egress_rule_changed(struct netc_vlan_entry *entry,
+static bool netc_port_vlan_egress_rule_changed(struct netc_switch *priv,
+                                              struct netc_vlan_entry *entry,
                                               int port, bool untagged)
 {
        bool old_untagged = !!(entry->untagged_port_bitmap & BIT(port));
 
+       /* VLAN-unaware VIDs have no egress rules, so return 'false' */
+       if (entry->vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges))
+               return false;
+
        return old_untagged != untagged;
 }
 
@@ -1338,7 +1359,8 @@ static int netc_port_set_vlan_entry(struct netc_port *np, 
u16 vid,
        }
 
        /* Check whether the egress VLAN rule is changed */
-       changed = netc_port_vlan_egress_rule_changed(entry, port, untagged);
+       changed = netc_port_vlan_egress_rule_changed(priv, entry, port,
+                                                    untagged);
        if (changed) {
                entry->untagged_port_bitmap ^= BIT(port);
                err = netc_port_update_vlan_egress_rule(np, entry);
@@ -1402,6 +1424,17 @@ static int netc_port_del_vlan_entry(struct netc_port 
*np, u16 vid)
        cfge = &entry->cfge;
        vlan_port_bitmap = FIELD_GET(VFT_PORT_MEMBERSHIP,
                                     le32_to_cpu(cfge->bitmap_stg));
+       /* If the VID is a VLAN-unaware PVID, we need to clear the CPU
+        * port bit of vlan_port_bitmap, so that the VLAN entry can be
+        * deleted if no user ports use this VLAN.
+        */
+       if (dsa_port_is_user(np->dp) &&
+           vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) {
+               struct dsa_port *cpu_dp = np->dp->cpu_dp;
+
+               vlan_port_bitmap &= ~BIT(cpu_dp->index);
+       }
+
        /* If the VLAN only belongs to the current port */
        if (vlan_port_bitmap == BIT(port)) {
                err = ntmp_vft_delete_entry(&priv->ntmp, vid);
@@ -1507,17 +1540,57 @@ static int netc_port_max_mtu(struct dsa_switch *ds, int 
port)
        return NETC_MAX_FRAME_LEN - VLAN_ETH_HLEN - ETH_FCS_LEN;
 }
 
+static struct net_device *netc_classify_db(struct dsa_db db)
+{
+       switch (db.type) {
+       case DSA_DB_PORT:
+               return NULL;
+       case DSA_DB_BRIDGE:
+               return db.bridge.dev;
+       default:
+               return ERR_PTR(-EOPNOTSUPP);
+       }
+}
+
+static u16 netc_vlan_unaware_pvid(struct dsa_switch *ds,
+                                 const struct net_device *br_ndev)
+{
+       struct dsa_port *dp;
+       int br_num = -1;
+
+       if (!br_ndev)
+               return NETC_STANDALONE_PVID;
+
+       dsa_switch_for_each_available_port(dp, ds) {
+               if (dsa_port_bridge_dev_get(dp) == br_ndev) {
+                       br_num = dp->bridge->num;
+                       break;
+               }
+       }
+
+       /* The br_num is supposed to be 1 ~ ds->max_num_bridges, see
+        * dsa_bridge_num_get(). Since max_num_bridges is non-zero,
+        * so dsa_port_bridge_create() will return an error if
+        * dsa_bridge_num_get() returns 0.
+        */
+       if (WARN_ON(br_num <= 0))
+               return NETC_STANDALONE_PVID;
+
+       return NETC_VLAN_UNAWARE_PVID(br_num);
+}
+
 static int netc_port_fdb_add(struct dsa_switch *ds, int port,
                             const unsigned char *addr, u16 vid,
                             struct dsa_db db)
 {
+       struct net_device *br_ndev = netc_classify_db(db);
        struct netc_port *np = NETC_PORT(ds, port);
 
-       /* Currently, only support standalone port mode, so only
-        * NETC_STANDALONE_PVID (= 0) is supported here.
-        */
-       if (vid != NETC_STANDALONE_PVID)
-               return -EOPNOTSUPP;
+       if (IS_ERR(br_ndev))
+               return PTR_ERR(br_ndev);
+
+       if (!vid)
+               vid = netc_vlan_unaware_pvid(ds, br_ndev);
 
        return netc_port_set_fdb_entry(np, addr, vid);
 }
@@ -1526,10 +1599,14 @@ static int netc_port_fdb_del(struct dsa_switch *ds, int 
port,
                             const unsigned char *addr, u16 vid,
                             struct dsa_db db)
 {
+       struct net_device *br_ndev = netc_classify_db(db);
        struct netc_port *np = NETC_PORT(ds, port);
 
-       if (vid != NETC_STANDALONE_PVID)
-               return -EOPNOTSUPP;
+       if (IS_ERR(br_ndev))
+               return PTR_ERR(br_ndev);
+
+       if (!vid)
+               vid = netc_vlan_unaware_pvid(ds, br_ndev);
 
        return netc_port_del_fdb_entry(np, addr, vid);
 }
@@ -1565,6 +1642,8 @@ static int netc_port_fdb_dump(struct dsa_switch *ds, int 
port,
                cfg = le32_to_cpu(cfge->cfg);
                is_static = (cfg & FDBT_DYNAMIC) ? false : true;
                vid = le16_to_cpu(keye->fid);
+               if (vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges))
+                       vid = 0;
 
                err = cb(keye->mac_addr, vid, is_static, data);
                if (err)
@@ -1681,6 +1760,19 @@ static void netc_port_set_host_flood(struct dsa_switch 
*ds, int port,
        struct netc_port *np = NETC_PORT(ds, port);
        struct ipft_entry_data *old_host_flood;
 
+       /* Do not add host flood rule to ingress port filter table when
+        * the port has joined a bridge. Otherwise, the ingress frames
+        * will bypass FDB table lookup and MAC learning, so the frames
+        * will be redirected directly to the CPU port.
+        */
+       if (dsa_port_bridge_dev_get(np->dp)) {
+               netc_port_remove_host_flood(np, np->host_flood);
+               np->host_flood = NULL;
+               netc_port_wr(np, NETC_PIPFCR, 0);
+
+               return;
+       }
+
        if (np->uc == uc && np->mc == mc)
                return;
 
@@ -1702,12 +1794,87 @@ 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_single_vlan_aware_bridge(struct dsa_switch *ds,
+                                        struct netlink_ext_ack *extack)
+{
+       struct net_device *br_ndev = NULL;
+       struct dsa_port *dp;
+
+       dsa_switch_for_each_available_port(dp, ds) {
+               struct net_device *port_br = dsa_port_bridge_dev_get(dp);
+
+               if (!port_br || !br_vlan_enabled(port_br))
+                       continue;
+
+               if (!br_ndev) {
+                       br_ndev = port_br;
+                       continue;
+               }
+
+               if (br_ndev == port_br)
+                       continue;
+
+               NL_SET_ERR_MSG_MOD(extack,
+                                  "Only one VLAN-aware bridge is supported");
+
+               return -EBUSY;
+       }
+
+       return 0;
+}
+
+static int netc_port_vlan_filtering(struct dsa_switch *ds,
+                                   int port, bool vlan_aware,
+                                   struct netlink_ext_ack *extack)
+{
+       struct netc_port *np = NETC_PORT(ds, port);
+       struct net_device *br_ndev;
+       u32 pvid, val;
+       int err;
+
+       err = netc_single_vlan_aware_bridge(ds, extack);
+       if (err)
+               return err;
+
+       br_ndev = dsa_port_bridge_dev_get(np->dp);
+       pvid = netc_vlan_unaware_pvid(ds, br_ndev);
+       if (pvid == NETC_STANDALONE_PVID) {
+               vlan_aware = false;
+               goto bpdvr_config;
+       }
+
+       if (vlan_aware) {
+               err = netc_port_del_vlan_entry(np, pvid);
+               if (err)
+                       return err;
+
+               pvid = np->pvid;
+       } else {
+               err = netc_port_set_vlan_entry(np, pvid, false);
+               if (err)
+                       return err;
+       }
+
+bpdvr_config:
+       val = (vlan_aware ? 0 : BPDVR_RXVAM) | (pvid & BPDVR_VID);
+       netc_port_rmw(np, NETC_BPDVR, BPDVR_RXVAM | BPDVR_VID, val);
+
+       return 0;
+}
+
+static void netc_port_set_pvid(struct netc_port *np, u16 pvid)
+{
+       netc_port_rmw(np, NETC_BPDVR, BPDVR_VID, pvid);
+}
+
 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);
+       struct dsa_port *dp = np->dp;
        bool untagged;
+       int err;
 
        /* The 8021q layer may attempt to change NETC_STANDALONE_PVID
         * (VID 0), so we need to ignore it.
@@ -1715,20 +1882,129 @@ static int netc_port_vlan_add(struct dsa_switch *ds, 
int port,
        if (vlan->vid == NETC_STANDALONE_PVID)
                return 0;
 
+       if (vlan->vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges)) {
+               NL_SET_ERR_MSG_FMT_MOD(extack,
+                                      "VID %d~4095 reserved for VLAN-unaware 
bridge",
+                                      
NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges));
+               return -EINVAL;
+       }
+
        untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+       err = netc_port_set_vlan_entry(np, vlan->vid, untagged);
+       if (err)
+               return err;
 
-       return netc_port_set_vlan_entry(np, vlan->vid, untagged);
+       if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
+               np->pvid = vlan->vid;
+               if (dsa_port_is_vlan_filtering(dp))
+                       netc_port_set_pvid(np, vlan->vid);
+
+               return 0;
+       }
+
+       if (np->pvid != vlan->vid)
+               return 0;
+
+       /* Delete PVID */
+       np->pvid = NETC_STANDALONE_PVID;
+       if (dsa_port_is_vlan_filtering(dp))
+               netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+
+       return 0;
 }
 
 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);
+       int err;
 
        if (vlan->vid == NETC_STANDALONE_PVID)
                return 0;
 
-       return netc_port_del_vlan_entry(np, vlan->vid);
+       err = netc_port_del_vlan_entry(np, vlan->vid);
+       if (err)
+               return err;
+
+       if (np->pvid == vlan->vid) {
+               np->pvid = NETC_STANDALONE_PVID;
+
+               /* Set the port PVID to NETC_STANDALONE_PVID if the VLAN-aware
+                * bridge port has no PVID. The untagged frames will not be
+                * forwarded to other user ports, as NETC_STANDALONE_PVID VLAN
+                * entry has disabled MAC learning and flooding, and other user
+                * ports do not have FDB entries with NETC_STANDALONE_PVID.
+                */
+               if (dsa_port_is_vlan_filtering(np->dp))
+                       netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+       }
+
+       return 0;
+}
+
+static int netc_port_bridge_join(struct dsa_switch *ds, int port,
+                                struct dsa_bridge bridge,
+                                bool *tx_fwd_offload,
+                                struct netlink_ext_ack *extack)
+{
+       struct netc_port *np = NETC_PORT(ds, port);
+       u16 vlan_unaware_pvid;
+       int err;
+
+       err = netc_single_vlan_aware_bridge(ds, extack);
+       if (err)
+               return err;
+
+       netc_port_set_mlo(np, MLO_NOT_OVERRIDE);
+
+       if (br_vlan_enabled(bridge.dev))
+               goto out;
+
+       vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num);
+       err = netc_port_set_vlan_entry(np, vlan_unaware_pvid, false);
+       if (err) {
+               netc_port_set_mlo(np, MLO_DISABLE);
+               return err;
+       }
+
+       netc_port_set_pvid(np, vlan_unaware_pvid);
+
+out:
+       netc_port_remove_host_flood(np, np->host_flood);
+       np->host_flood = NULL;
+       netc_port_wr(np, NETC_PIPFCR, 0);
+
+       return 0;
+}
+
+static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
+                                  struct dsa_bridge bridge)
+{
+       struct netc_port *np = NETC_PORT(ds, port);
+       struct net_device *ndev = np->dp->user;
+       u16 vlan_unaware_pvid;
+       bool mc, uc;
+
+       netc_port_set_mlo(np, MLO_DISABLE);
+       netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+
+       uc = ndev->flags & IFF_PROMISC;
+       mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI);
+
+       if (netc_port_add_host_flood_rule(np, uc, mc))
+               dev_warn(ds->dev,
+                        "Failed to restore host flood rule on port %d\n",
+                        port);
+
+       if (br_vlan_enabled(bridge.dev))
+               return;
+
+       vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num);
+       /* There is no need to check the return value even if it fails.
+        * Because the PVID has been set to NETC_STANDALONE_PVID, the
+        * frames will not match this VLAN entry.
+        */
+       netc_port_del_vlan_entry(np, vlan_unaware_pvid);
 }
 
 static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
@@ -2009,8 +2285,11 @@ 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_filtering            = netc_port_vlan_filtering,
        .port_vlan_add                  = netc_port_vlan_add,
        .port_vlan_del                  = netc_port_vlan_del,
+       .port_bridge_join               = netc_port_bridge_join,
+       .port_bridge_leave              = netc_port_bridge_leave,
        .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,
@@ -2058,6 +2337,7 @@ static int netc_switch_probe(struct pci_dev *pdev,
        ds->ops = &netc_switch_ops;
        ds->phylink_mac_ops = &netc_phylink_mac_ops;
        ds->fdb_isolation = true;
+       ds->max_num_bridges = priv->info->num_ports - 1;
        ds->priv = priv;
        priv->ds = ds;
 
diff --git a/drivers/net/dsa/netc/netc_switch.h 
b/drivers/net/dsa/netc/netc_switch.h
index 9ff334301fbc..982c8d3a3fbf 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -33,6 +33,7 @@
 #define NETC_MAX_FRAME_LEN             9600
 
 #define NETC_STANDALONE_PVID           0
+#define NETC_VLAN_UNAWARE_PVID(br_id)  (4096 - (br_id))
 
 /* Threshold format: MANT (bits 11:4) * 2^EXP (bits 3:0)
  * Unit: Memory words (average of 20 bytes each)
@@ -79,6 +80,7 @@ struct netc_port {
        u16 enable:1;
        u16 uc:1;
        u16 mc:1;
+       u16 pvid;
        struct ipft_entry_data *host_flood;
 };
 
-- 
2.34.1


Reply via email to