net: dsa: vsc73xx: implement FDB operations
authorPawel Dembicki <paweldembicki@gmail.com>
Tue, 27 Aug 2024 12:39:38 +0000 (14:39 +0200)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 3 Sep 2024 08:22:58 +0000 (10:22 +0200)
This commit introduces implementations of three functions:
.port_fdb_dump
.port_fdb_add
.port_fdb_del

The FDB database organization is the same as in other old Vitesse chips:
It has 2048 rows and 4 columns (buckets). The row index is calculated by
the hash function 'vsc73xx_calc_hash' and the FDB entry must be placed
exactly into row[hash]. The chip selects the bucket number by itself.

Signed-off-by: Pawel Dembicki <paweldembicki@gmail.com>
Link: https://patch.msgid.link/20240827123938.582789-1-paweldembicki@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
drivers/net/dsa/vitesse-vsc73xx-core.c
drivers/net/dsa/vitesse-vsc73xx.h

index 4d693c0..1f2acd3 100644 (file)
@@ -46,6 +46,8 @@
 #define VSC73XX_BLOCK_MII_EXTERNAL     0x1 /* External MDIO subblock */
 
 #define CPU_PORT       6 /* CPU port */
+#define VSC73XX_NUM_FDB_ROWS   2048
+#define VSC73XX_NUM_BUCKETS    4
 
 /* MAC Block registers */
 #define VSC73XX_MAC_CFG                0x00
 #define VSC73XX_SRCMASKS_MIRROR                        BIT(26)
 #define VSC73XX_SRCMASKS_PORTS_MASK            GENMASK(7, 0)
 
+#define VSC73XX_MACHDATA_VID                   GENMASK(27, 16)
+#define VSC73XX_MACHDATA_MAC0                  GENMASK(15, 8)
+#define VSC73XX_MACHDATA_MAC1                  GENMASK(7, 0)
+#define VSC73XX_MACLDATA_MAC2                  GENMASK(31, 24)
+#define VSC73XX_MACLDATA_MAC3                  GENMASK(23, 16)
+#define VSC73XX_MACLDATA_MAC4                  GENMASK(15, 8)
+#define VSC73XX_MACLDATA_MAC5                  GENMASK(7, 0)
+
+#define VSC73XX_HASH0_VID_FROM_MASK            GENMASK(5, 0)
+#define VSC73XX_HASH0_MAC0_FROM_MASK           GENMASK(7, 4)
+#define VSC73XX_HASH1_MAC0_FROM_MASK           GENMASK(3, 0)
+#define VSC73XX_HASH1_MAC1_FROM_MASK           GENMASK(7, 1)
+#define VSC73XX_HASH2_MAC1_FROM_MASK           BIT(0)
+#define VSC73XX_HASH2_MAC2_FROM_MASK           GENMASK(7, 0)
+#define VSC73XX_HASH2_MAC3_FROM_MASK           GENMASK(7, 6)
+#define VSC73XX_HASH3_MAC3_FROM_MASK           GENMASK(5, 0)
+#define VSC73XX_HASH3_MAC4_FROM_MASK           GENMASK(7, 3)
+#define VSC73XX_HASH4_MAC4_FROM_MASK           GENMASK(2, 0)
+
+#define VSC73XX_HASH0_VID_TO_MASK              GENMASK(9, 4)
+#define VSC73XX_HASH0_MAC0_TO_MASK             GENMASK(3, 0)
+#define VSC73XX_HASH1_MAC0_TO_MASK             GENMASK(10, 7)
+#define VSC73XX_HASH1_MAC1_TO_MASK             GENMASK(6, 0)
+#define VSC73XX_HASH2_MAC1_TO_MASK             BIT(10)
+#define VSC73XX_HASH2_MAC2_TO_MASK             GENMASK(9, 2)
+#define VSC73XX_HASH2_MAC3_TO_MASK             GENMASK(1, 0)
+#define VSC73XX_HASH3_MAC3_TO_MASK             GENMASK(10, 5)
+#define VSC73XX_HASH3_MAC4_TO_MASK             GENMASK(4, 0)
+#define VSC73XX_HASH4_MAC4_TO_MASK             GENMASK(10, 8)
+
+#define VSC73XX_MACTINDX_SHADOW                        BIT(13)
+#define VSC73XX_MACTINDX_BUCKET_MSK            GENMASK(12, 11)
+#define VSC73XX_MACTINDX_INDEX_MSK             GENMASK(10, 0)
+
 #define VSC73XX_MACACCESS_CPU_COPY             BIT(14)
 #define VSC73XX_MACACCESS_FWD_KILL             BIT(13)
 #define VSC73XX_MACACCESS_IGNORE_VLAN          BIT(12)
@@ -332,6 +368,13 @@ struct vsc73xx_counter {
        const char *name;
 };
 
+struct vsc73xx_fdb {
+       u16 vid;
+       u8 port;
+       u8 mac[ETH_ALEN];
+       bool valid;
+};
+
 /* Counters are named according to the MIB standards where applicable.
  * Some counters are custom, non-standard. The standard counters are
  * named in accordance with RFC2819, RFC2021 and IEEE Std 802.3-2002 Annex
@@ -804,6 +847,7 @@ static int vsc73xx_setup(struct dsa_switch *ds)
 
        ds->untag_bridge_pvid = true;
        ds->max_num_bridges = DSA_TAG_8021Q_MAX_NUM_BRIDGES;
+       ds->fdb_isolation = true;
 
        /* Issue RESET */
        vsc73xx_write(vsc, VSC73XX_BLOCK_SYSTEM, 0, VSC73XX_GLORESET,
@@ -1854,6 +1898,312 @@ static void vsc73xx_port_stp_state_set(struct dsa_switch *ds, int port,
                vsc73xx_refresh_fwd_map(ds, port, state);
 }
 
+static u16 vsc73xx_calc_hash(const unsigned char *addr, u16 vid)
+{
+       /* VID 5-0, MAC 47-44 */
+       u16 hash = FIELD_PREP(VSC73XX_HASH0_VID_TO_MASK,
+                             FIELD_GET(VSC73XX_HASH0_VID_FROM_MASK, vid)) |
+                  FIELD_PREP(VSC73XX_HASH0_MAC0_TO_MASK,
+                             FIELD_GET(VSC73XX_HASH0_MAC0_FROM_MASK, addr[0]));
+       /* MAC 43-33 */
+       hash ^= FIELD_PREP(VSC73XX_HASH1_MAC0_TO_MASK,
+                          FIELD_GET(VSC73XX_HASH1_MAC0_FROM_MASK, addr[0])) |
+               FIELD_PREP(VSC73XX_HASH1_MAC1_TO_MASK,
+                          FIELD_GET(VSC73XX_HASH1_MAC1_FROM_MASK, addr[1]));
+       /* MAC 32-22 */
+       hash ^= FIELD_PREP(VSC73XX_HASH2_MAC1_TO_MASK,
+                          FIELD_GET(VSC73XX_HASH2_MAC1_FROM_MASK, addr[1])) |
+               FIELD_PREP(VSC73XX_HASH2_MAC2_TO_MASK,
+                          FIELD_GET(VSC73XX_HASH2_MAC2_FROM_MASK, addr[2])) |
+               FIELD_PREP(VSC73XX_HASH2_MAC3_TO_MASK,
+                          FIELD_GET(VSC73XX_HASH2_MAC3_FROM_MASK, addr[3]));
+       /* MAC 21-11 */
+       hash ^= FIELD_PREP(VSC73XX_HASH3_MAC3_TO_MASK,
+                          FIELD_GET(VSC73XX_HASH3_MAC3_FROM_MASK, addr[3])) |
+               FIELD_PREP(VSC73XX_HASH3_MAC4_TO_MASK,
+                          FIELD_GET(VSC73XX_HASH3_MAC4_FROM_MASK, addr[4]));
+       /* MAC 10-0 */
+       hash ^= FIELD_PREP(VSC73XX_HASH4_MAC4_TO_MASK,
+                          FIELD_GET(VSC73XX_HASH4_MAC4_FROM_MASK, addr[4])) |
+               addr[5];
+
+       return hash;
+}
+
+static int
+vsc73xx_port_wait_for_mac_table_cmd(struct vsc73xx *vsc)
+{
+       int ret, err;
+       u32 val;
+
+       ret = read_poll_timeout(vsc73xx_read, err,
+                               err < 0 ||
+                               ((val & VSC73XX_MACACCESS_CMD_MASK) ==
+                                VSC73XX_MACACCESS_CMD_IDLE),
+                               VSC73XX_POLL_SLEEP_US, VSC73XX_POLL_TIMEOUT_US,
+                               false, vsc, VSC73XX_BLOCK_ANALYZER,
+                               0, VSC73XX_MACACCESS, &val);
+       if (ret)
+               return ret;
+       return err;
+}
+
+static int vsc73xx_port_read_mac_table_row(struct vsc73xx *vsc, u16 index,
+                                          struct vsc73xx_fdb *fdb)
+{
+       int ret, i;
+       u32 val;
+
+       if (!fdb)
+               return -EINVAL;
+       if (index >= VSC73XX_NUM_FDB_ROWS)
+               return -EINVAL;
+
+       for (i = 0; i < VSC73XX_NUM_BUCKETS; i++) {
+               ret = vsc73xx_write(vsc, VSC73XX_BLOCK_ANALYZER, 0,
+                                   VSC73XX_MACTINDX,
+                                   (i ? 0 : VSC73XX_MACTINDX_SHADOW) |
+                                   FIELD_PREP(VSC73XX_MACTINDX_BUCKET_MSK, i) |
+                                   index);
+               if (ret)
+                       return ret;
+
+               ret = vsc73xx_port_wait_for_mac_table_cmd(vsc);
+               if (ret)
+                       return ret;
+
+               ret = vsc73xx_update_bits(vsc, VSC73XX_BLOCK_ANALYZER, 0,
+                                         VSC73XX_MACACCESS,
+                                         VSC73XX_MACACCESS_CMD_MASK,
+                                         VSC73XX_MACACCESS_CMD_READ_ENTRY);
+               if (ret)
+                       return ret;
+
+               ret = vsc73xx_port_wait_for_mac_table_cmd(vsc);
+               if (ret)
+                       return ret;
+
+               ret = vsc73xx_read(vsc, VSC73XX_BLOCK_ANALYZER, 0,
+                                  VSC73XX_MACACCESS, &val);
+               if (ret)
+                       return ret;
+
+               fdb[i].valid = FIELD_GET(VSC73XX_MACACCESS_VALID, val);
+               if (!fdb[i].valid)
+                       continue;
+
+               fdb[i].port = FIELD_GET(VSC73XX_MACACCESS_DEST_IDX_MASK, val);
+
+               ret = vsc73xx_read(vsc, VSC73XX_BLOCK_ANALYZER, 0,
+                                  VSC73XX_MACHDATA, &val);
+               if (ret)
+                       return ret;
+
+               fdb[i].vid = FIELD_GET(VSC73XX_MACHDATA_VID, val);
+               fdb[i].mac[0] = FIELD_GET(VSC73XX_MACHDATA_MAC0, val);
+               fdb[i].mac[1] = FIELD_GET(VSC73XX_MACHDATA_MAC1, val);
+
+               ret = vsc73xx_read(vsc, VSC73XX_BLOCK_ANALYZER, 0,
+                                  VSC73XX_MACLDATA, &val);
+               if (ret)
+                       return ret;
+
+               fdb[i].mac[2] = FIELD_GET(VSC73XX_MACLDATA_MAC2, val);
+               fdb[i].mac[3] = FIELD_GET(VSC73XX_MACLDATA_MAC3, val);
+               fdb[i].mac[4] = FIELD_GET(VSC73XX_MACLDATA_MAC4, val);
+               fdb[i].mac[5] = FIELD_GET(VSC73XX_MACLDATA_MAC5, val);
+       }
+
+       return ret;
+}
+
+static int
+vsc73xx_fdb_operation(struct vsc73xx *vsc, const unsigned char *addr, u16 vid,
+                     u16 hash, u16 cmd_mask, u16 cmd_val)
+{
+       int ret;
+       u32 val;
+
+       val = FIELD_PREP(VSC73XX_MACHDATA_VID, vid) |
+             FIELD_PREP(VSC73XX_MACHDATA_MAC0, addr[0]) |
+             FIELD_PREP(VSC73XX_MACHDATA_MAC1, addr[1]);
+       ret = vsc73xx_write(vsc, VSC73XX_BLOCK_ANALYZER, 0, VSC73XX_MACHDATA,
+                           val);
+       if (ret)
+               return ret;
+
+       val = FIELD_PREP(VSC73XX_MACLDATA_MAC2, addr[2]) |
+             FIELD_PREP(VSC73XX_MACLDATA_MAC3, addr[3]) |
+             FIELD_PREP(VSC73XX_MACLDATA_MAC4, addr[4]) |
+             FIELD_PREP(VSC73XX_MACLDATA_MAC5, addr[5]);
+       ret = vsc73xx_write(vsc, VSC73XX_BLOCK_ANALYZER, 0, VSC73XX_MACLDATA,
+                           val);
+       if (ret)
+               return ret;
+
+       ret = vsc73xx_write(vsc, VSC73XX_BLOCK_ANALYZER, 0, VSC73XX_MACTINDX,
+                           hash);
+       if (ret)
+               return ret;
+
+       ret = vsc73xx_port_wait_for_mac_table_cmd(vsc);
+       if (ret)
+               return ret;
+
+       ret = vsc73xx_update_bits(vsc, VSC73XX_BLOCK_ANALYZER, 0,
+                                 VSC73XX_MACACCESS, cmd_mask, cmd_val);
+       if (ret)
+               return ret;
+
+       return vsc73xx_port_wait_for_mac_table_cmd(vsc);
+}
+
+static int vsc73xx_fdb_del_entry(struct vsc73xx *vsc, int port,
+                                const unsigned char *addr, u16 vid)
+{
+       struct vsc73xx_fdb fdb[VSC73XX_NUM_BUCKETS];
+       u16 hash = vsc73xx_calc_hash(addr, vid);
+       int bucket, ret;
+
+       mutex_lock(&vsc->fdb_lock);
+
+       ret = vsc73xx_port_read_mac_table_row(vsc, hash, fdb);
+       if (ret)
+               goto err;
+
+       for (bucket = 0; bucket < VSC73XX_NUM_BUCKETS; bucket++) {
+               if (fdb[bucket].valid && fdb[bucket].port == port &&
+                   ether_addr_equal(addr, fdb[bucket].mac))
+                       break;
+       }
+
+       if (bucket == VSC73XX_NUM_BUCKETS) {
+               /* Can't find MAC in MAC table */
+               ret = -ENODATA;
+               goto err;
+       }
+
+       ret = vsc73xx_fdb_operation(vsc, addr, vid, hash,
+                                   VSC73XX_MACACCESS_CMD_MASK,
+                                   VSC73XX_MACACCESS_CMD_FORGET);
+err:
+       mutex_unlock(&vsc->fdb_lock);
+       return ret;
+}
+
+static int vsc73xx_fdb_add_entry(struct vsc73xx *vsc, int port,
+                                const unsigned char *addr, u16 vid)
+{
+       struct vsc73xx_fdb fdb[VSC73XX_NUM_BUCKETS];
+       u16 hash = vsc73xx_calc_hash(addr, vid);
+       int bucket, ret;
+       u32 val;
+
+       mutex_lock(&vsc->fdb_lock);
+
+       ret = vsc73xx_port_read_mac_table_row(vsc, hash, fdb);
+       if (ret)
+               goto err;
+
+       for (bucket = 0; bucket < VSC73XX_NUM_BUCKETS; bucket++) {
+               if (!fdb[bucket].valid)
+                       break;
+       }
+
+       if (bucket == VSC73XX_NUM_BUCKETS) {
+               /* Bucket is full */
+               ret = -EOVERFLOW;
+               goto err;
+       }
+
+       val = VSC73XX_MACACCESS_VALID | VSC73XX_MACACCESS_LOCKED |
+             FIELD_PREP(VSC73XX_MACACCESS_DEST_IDX_MASK, port) |
+             VSC73XX_MACACCESS_CMD_LEARN;
+       ret = vsc73xx_fdb_operation(vsc, addr, vid, hash,
+                                   VSC73XX_MACACCESS_VALID |
+                                   VSC73XX_MACACCESS_LOCKED |
+                                   VSC73XX_MACACCESS_DEST_IDX_MASK |
+                                   VSC73XX_MACACCESS_CMD_MASK, val);
+err:
+       mutex_unlock(&vsc->fdb_lock);
+       return ret;
+}
+
+static int vsc73xx_fdb_add(struct dsa_switch *ds, int port,
+                          const unsigned char *addr, u16 vid, struct dsa_db db)
+{
+       struct vsc73xx *vsc = ds->priv;
+
+       if (!vid) {
+               switch (db.type) {
+               case DSA_DB_PORT:
+                       vid = dsa_tag_8021q_standalone_vid(db.dp);
+                       break;
+               case DSA_DB_BRIDGE:
+                       vid = dsa_tag_8021q_bridge_vid(db.bridge.num);
+                       break;
+               default:
+                       return -EOPNOTSUPP;
+               }
+       }
+
+       return vsc73xx_fdb_add_entry(vsc, port, addr, vid);
+}
+
+static int vsc73xx_fdb_del(struct dsa_switch *ds, int port,
+                          const unsigned char *addr, u16 vid, struct dsa_db db)
+{
+       struct vsc73xx *vsc = ds->priv;
+
+       if (!vid) {
+               switch (db.type) {
+               case DSA_DB_PORT:
+                       vid = dsa_tag_8021q_standalone_vid(db.dp);
+                       break;
+               case DSA_DB_BRIDGE:
+                       vid = dsa_tag_8021q_bridge_vid(db.bridge.num);
+                       break;
+               default:
+                       return -EOPNOTSUPP;
+               }
+       }
+
+       return vsc73xx_fdb_del_entry(vsc, port, addr, vid);
+}
+
+static int vsc73xx_port_fdb_dump(struct dsa_switch *ds,
+                                int port, dsa_fdb_dump_cb_t *cb, void *data)
+{
+       struct vsc73xx_fdb fdb[VSC73XX_NUM_BUCKETS];
+       struct vsc73xx *vsc = ds->priv;
+       u16 i, bucket;
+       int err = 0;
+
+       mutex_lock(&vsc->fdb_lock);
+
+       for (i = 0; i < VSC73XX_NUM_FDB_ROWS; i++) {
+               err = vsc73xx_port_read_mac_table_row(vsc, i, fdb);
+               if (err)
+                       goto unlock;
+
+               for (bucket = 0; bucket < VSC73XX_NUM_BUCKETS; bucket++) {
+                       if (!fdb[bucket].valid || fdb[bucket].port != port)
+                               continue;
+
+                       /* We need to hide dsa_8021q VLANs from the user */
+                       if (vid_is_dsa_8021q(fdb[bucket].vid))
+                               fdb[bucket].vid = 0;
+
+                       err = cb(fdb[bucket].mac, fdb[bucket].vid, false, data);
+                       if (err)
+                               goto unlock;
+               }
+       }
+unlock:
+       mutex_unlock(&vsc->fdb_lock);
+       return err;
+}
+
 static const struct phylink_mac_ops vsc73xx_phylink_mac_ops = {
        .mac_config = vsc73xx_mac_config,
        .mac_link_down = vsc73xx_mac_link_down,
@@ -1876,6 +2226,9 @@ static const struct dsa_switch_ops vsc73xx_ds_ops = {
        .port_bridge_join = dsa_tag_8021q_bridge_join,
        .port_bridge_leave = dsa_tag_8021q_bridge_leave,
        .port_change_mtu = vsc73xx_change_mtu,
+       .port_fdb_add = vsc73xx_fdb_add,
+       .port_fdb_del = vsc73xx_fdb_del,
+       .port_fdb_dump = vsc73xx_port_fdb_dump,
        .port_max_mtu = vsc73xx_get_max_mtu,
        .port_stp_state_set = vsc73xx_port_stp_state_set,
        .port_vlan_filtering = vsc73xx_port_vlan_filtering,
@@ -2006,6 +2359,8 @@ int vsc73xx_probe(struct vsc73xx *vsc)
                return -ENODEV;
        }
 
+       mutex_init(&vsc->fdb_lock);
+
        eth_random_addr(vsc->addr);
        dev_info(vsc->dev,
                 "MAC for control frames: %02X:%02X:%02X:%02X:%02X:%02X\n",
index 3ca579a..3c30e14 100644 (file)
@@ -45,6 +45,7 @@ struct vsc73xx_portinfo {
  * @vlans: List of configured vlans. Contains port mask and untagged status of
  *     every vlan configured in port vlan operation. It doesn't cover tag_8021q
  *     vlans.
+ * @fdb_lock: Mutex protects fdb access
  */
 struct vsc73xx {
        struct device                   *dev;
@@ -57,6 +58,7 @@ struct vsc73xx {
        void                            *priv;
        struct vsc73xx_portinfo         portinfo[VSC73XX_MAX_NUM_PORTS];
        struct list_head                vlans;
+       struct mutex                    fdb_lock;
 };
 
 /**