realtek: Add support for Layer 2 Multicast
[openwrt/staging/dedeckeh.git] / target / linux / realtek / files-5.4 / drivers / net / dsa / rtl83xx / rtl930x.c
index a80a6d89b29edf5699b7de8a252f03db59088c31..820c78165a3c8677697fb25349cc10c4aff91c3f 100644 (file)
@@ -207,12 +207,74 @@ static inline int rtl930x_mac_link_spd_sts(int p)
        return RTL930X_MAC_LINK_SPD_STS(p);
 }
 
+static u64 rtl930x_l2_hash_seed(u64 mac, u32 vid)
+{
+       u64 v = vid;
+
+       v <<= 48;
+       v |= mac;
+
+       return v;
+}
+
+/*
+ * Calculate both the block 0 and the block 1 hash by applyingthe same hash
+ * algorithm as the one used currently by the ASIC to the seed, and return
+ * both hashes in the lower and higher word of the return value since only 12 bit of
+ * the hash are significant
+ */
+static u32 rtl930x_l2_hash_key(struct rtl838x_switch_priv *priv, u64 seed)
+{
+       u32 k0, k1, h1, h2, h;
+
+       k0 = (u32) (((seed >> 55) & 0x1f) ^ ((seed >> 44) & 0x7ff)
+               ^ ((seed >> 33) & 0x7ff) ^ ((seed >> 22) & 0x7ff)
+               ^ ((seed >> 11) & 0x7ff) ^ (seed & 0x7ff));
+
+       h1 = (seed >> 11) & 0x7ff;
+       h1 = ((h1 & 0x1f) << 6) | ((h1 >> 5) & 0x3f);
+
+       h2 = (seed >> 33) & 0x7ff;
+       h2 = ((h2 & 0x3f) << 5)| ((h2 >> 6) & 0x3f);
+
+       k1 = (u32) (((seed << 55) & 0x1f) ^ ((seed >> 44) & 0x7ff) ^ h2
+                   ^ ((seed >> 22) & 0x7ff) ^ h1
+                   ^ (seed & 0x7ff));
+
+       // Algorithm choice for block 0
+       if (sw_r32(RTL930X_L2_CTRL) & BIT(0))
+               h = k1;
+       else
+               h = k0;
+
+       /* Algorithm choice for block 1
+        * Since k0 and k1 are < 2048, adding 2048 will offset the hash into the second
+        * half of hash-space
+        * 2048 is in fact the hash-table size 16384 divided by 4 hashes per bucket
+        * divided by 2 to divide the hash space in 2
+        */
+       if (sw_r32(RTL930X_L2_CTRL) & BIT(1))
+               h |= (k1 + 2048) << 16;
+       else
+               h |= (k0 + 2048) << 16;
+
+       return h;
+}
+
+/*
+ * Fills an L2 entry structure from the SoC registers
+ */
 static void rtl930x_fill_l2_entry(u32 r[], struct rtl838x_l2_entry *e)
 {
+       pr_debug("In %s valid?\n", __func__);
        e->valid = !!(r[2] & BIT(31));
        if (!e->valid)
                return;
 
+       pr_debug("In %s is valid\n", __func__);
+       e->is_ip_mc = false;
+       e->is_ipv6_mc = false;
+
        // TODO: Is there not a function to copy directly MAC memory?
        e->mac[0] = (r[0] >> 24);
        e->mac[1] = (r[0] >> 16);
@@ -221,61 +283,164 @@ static void rtl930x_fill_l2_entry(u32 r[], struct rtl838x_l2_entry *e)
        e->mac[4] = (r[1] >> 24);
        e->mac[5] = (r[1] >> 16);
 
+       e->next_hop = !!(r[2] & BIT(12));
+       e->rvid = r[1] & 0xfff;
+
        /* Is it a unicast entry? check multicast bit */
        if (!(e->mac[0] & 1)) {
                e->type = L2_UNICAST;
                e->is_static = !!(r[2] & BIT(14));
-               e->vid = r[2] & 0xfff;
-               e->rvid = r[1] & 0xfff;
                e->port = (r[2] >> 20) & 0x3ff;
                // Check for trunk port
                if (r[2] & BIT(30)) {
-                       e->stackDev = (e->port >> 9) & 1;
+                       e->is_trunk = true;
+                       e->stack_dev = (e->port >> 9) & 1;
                        e->trunk = e->port & 0x3f;
                } else {
-                       e->stackDev = (e->port >> 6) & 0xf;
+                       e->is_trunk = false;
+                       e->stack_dev = (e->port >> 6) & 0xf;
                        e->port = e->port & 0x3f;
                }
 
                e->block_da = !!(r[2] & BIT(15));
                e->block_sa = !!(r[2] & BIT(16));
                e->suspended = !!(r[2] & BIT(13));
-               e->next_hop = !!(r[2] & BIT(12));
                e->age = (r[2] >> 17) & 3;
                e->valid = true;
-
+               // the UC_VID field in hardware is used for the VID or for the route id
+               if (e->next_hop) {
+                       e->nh_route_id = r[2] & 0xfff;
+                       e->vid = 0;
+               } else {
+                       e->vid = r[2] & 0xfff;
+                       e->nh_route_id = 0;
+               }
        } else {
                e->valid = true;
                e->type = L2_MULTICAST;
-               e->mc_portmask_index = (r[2]>>6) & 0xfff;
+               e->mc_portmask_index = (r[2] >> 16) & 0x3ff;
        }
 }
 
-static u64 rtl930x_read_l2_entry_using_hash(u32 hash, u32 position, struct rtl838x_l2_entry *e)
+/*
+ * Fills the 3 SoC table registers r[] with the information of in the rtl838x_l2_entry
+ */
+static void rtl930x_fill_l2_row(u32 r[], struct rtl838x_l2_entry *e)
+{
+       u32 port;
+
+       if (!e->valid) {
+               r[0] = r[1] = r[2] = 0;
+               return;
+       }
+
+       r[2] = BIT(31); // Set valid bit
+
+       r[0] = ((u32)e->mac[0]) << 24 | ((u32)e->mac[1]) << 16 
+               | ((u32)e->mac[2]) << 8 | ((u32)e->mac[3]);
+       r[1] = ((u32)e->mac[4]) << 24 | ((u32)e->mac[5]) << 16;
+
+       r[2] |= e->next_hop ? BIT(12) : 0;
+
+       if (e->type == L2_UNICAST) {
+               r[2] |= e->is_static ? BIT(14) : 0;
+               r[1] |= e->rvid & 0xfff;
+               r[2] |= (e->port & 0x3ff) << 20;
+               if (e->is_trunk) {
+                       r[2] |= BIT(30);
+                       port = e->stack_dev << 9 | (e->port & 0x3f);
+               } else {
+                       port = (e->stack_dev & 0xf) << 6;
+                       port |= e->port & 0x3f;
+               }
+               r[2] |= port << 20;
+               r[2] |= e->block_da ? BIT(15) : 0;
+               r[2] |= e->block_sa ? BIT(17) : 0;
+               r[2] |= e->suspended ? BIT(13) : 0;
+               r[2] |= (e->age & 0x3) << 17;
+               // the UC_VID field in hardware is used for the VID or for the route id
+               if (e->next_hop)
+                       r[2] |= e->nh_route_id & 0xfff;
+               else
+                       r[2] |= e->vid & 0xfff;
+       } else { // L2_MULTICAST
+               r[2] |= (e->mc_portmask_index & 0x3ff) << 16;
+               r[2] |= e->mc_mac_index & 0x7ff;
+       }
+}
+
+/*
+ * Read an L2 UC or MC entry out of a hash bucket of the L2 forwarding table
+ * hash is the id of the bucket and pos is the position of the entry in that bucket
+ * The data read from the SoC is filled into rtl838x_l2_entry
+ */
+static u64 rtl930x_read_l2_entry_using_hash(u32 hash, u32 pos, struct rtl838x_l2_entry *e)
 {
-       u64 entry;
        u32 r[3];
        struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 0);
-       u32 idx = (0 << 14) | (hash << 2) | position;
+       u32 idx;
        int i;
+       u64 mac;
+       u64 seed;
+
+       pr_debug("%s: hash %08x, pos: %d\n", __func__, hash, pos);
+
+       /* On the RTL93xx, 2 different hash algorithms are used making it a total of
+        * 8 buckets that need to be searched, 4 for each hash-half
+        * Use second hash space when bucket is between 4 and 8 */
+       if (pos >= 4) {
+               pos -= 4;
+               hash >>= 16;
+       } else {
+               hash &= 0xffff;
+       }
+
+       idx = (0 << 14) | (hash << 2) | pos; // Search SRAM, with hash and at pos in bucket
+       pr_debug("%s: NOW hash %08x, pos: %d\n", __func__, hash, pos);
 
        rtl_table_read(q, idx);
-       for (i= 0; i < 3; i++)
+       for (i = 0; i < 3; i++)
                r[i] = sw_r32(rtl_table_data(q, i));
 
        rtl_table_release(q);
 
        rtl930x_fill_l2_entry(r, e);
+
+       pr_debug("%s: valid: %d, nh: %d\n", __func__, e->valid, e->next_hop);
        if (!e->valid)
                return 0;
 
-       entry = ((u64)r[0] << 32) | (r[1] & 0xffff0000) | e->vid;
-       return entry;
+       mac = ((u64)e->mac[0]) << 40 | ((u64)e->mac[1]) << 32 | ((u64)e->mac[2]) << 24
+               | ((u64)e->mac[3]) << 16 | ((u64)e->mac[4]) << 8 | ((u64)e->mac[5]);
+
+       seed = rtl930x_l2_hash_seed(mac, e->rvid);
+       pr_debug("%s: mac %016llx, seed %016llx\n", __func__, mac, seed);
+       // return vid with concatenated mac as unique id
+       return seed;
+}
+
+static void rtl930x_write_l2_entry_using_hash(u32 hash, u32 pos, struct rtl838x_l2_entry *e)
+{
+       u32 r[3];
+       struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 0);
+       u32 idx = (0 << 14) | (hash << 2) | pos; // Access SRAM, with hash and at pos in bucket
+       int i;
+
+       pr_info("%s: hash %d, pos %d\n", __func__, hash, pos);
+       pr_info("%s: index %d -> mac %02x:%02x:%02x:%02x:%02x:%02x\n", __func__, idx,
+               e->mac[0], e->mac[1], e->mac[2], e->mac[3],e->mac[4],e->mac[5]);
+
+       rtl930x_fill_l2_row(r, e);
+
+       for (i= 0; i < 3; i++)
+               sw_w32(r[i], rtl_table_data(q, i));
+
+       rtl_table_write(q, idx);
+       rtl_table_release(q);
 }
 
 static u64 rtl930x_read_cam(int idx, struct rtl838x_l2_entry *e)
 {
-       u64 entry;
        u32 r[3];
        struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 1);
        int i;
@@ -290,9 +455,113 @@ static u64 rtl930x_read_cam(int idx, struct rtl838x_l2_entry *e)
        if (!e->valid)
                return 0;
 
-       entry = ((u64)r[0] << 32) | (r[1] & 0xffff0000) | e->vid;
+       // return mac with concatenated vid as unique id
+       return ((u64)r[0] << 28) | ((r[1] & 0xffff0000) >> 4) | e->vid;
+}
 
-       return entry;
+static void rtl930x_write_cam(int idx, struct rtl838x_l2_entry *e)
+{
+       u32 r[3];
+       struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 1); // Access L2 Table 1
+       int i;
+
+       rtl930x_fill_l2_row(r, e);
+
+       for (i= 0; i < 3; i++)
+               sw_w32(r[i], rtl_table_data(q, i));
+
+       rtl_table_write(q, idx);
+       rtl_table_release(q);
+}
+
+static void dump_l2_entry(struct rtl838x_l2_entry *e)
+{
+       pr_info("MAC: %02x:%02x:%02x:%02x:%02x:%02x vid: %d, rvid: %d, port: %d, valid: %d\n",
+               e->mac[0], e->mac[1], e->mac[2], e->mac[3], e->mac[4], e->mac[5],
+               e->vid, e->rvid, e->port, e->valid);
+       pr_info("Type: %d, is_static: %d, is_ip_mc: %d, is_ipv6_mc: %d, block_da: %d\n",
+               e->type, e->is_static, e->is_ip_mc, e->is_ipv6_mc, e->block_da);
+       pr_info("  block_sa: %d, suspended: %d, next_hop: %d, age: %d, is_trunk: %d, trunk: %d\n",
+               e->block_sa, e->suspended, e->next_hop, e->age, e->is_trunk, e->trunk);
+       if (e->is_ip_mc || e->is_ipv6_mc)
+               pr_info("  mc_portmask_index: %d, mc_gip: %d, mc_sip: %d\n",
+                       e->mc_portmask_index, e->mc_gip, e->mc_sip);
+       pr_info("  stac_dev: %d, nh_route_id: %d, port: %d, dev_id\n",
+               e->stack_dev, e->nh_route_id, e->port);
+}
+
+/*
+ * Add an L2 nexthop entry for the L3 routing system in the SoC
+ * Use VID and MAC in rtl838x_l2_entry to identify either a free slot in the L2 hash table
+ * or mark an existing entry as a nexthop by setting it's nexthop bit
+ * Called from the L3 layer
+ * The index in the L2 hash table is filled into nh->l2_id;
+ */
+static int rtl930x_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct rtl838x_nexthop *nh)
+{
+       struct rtl838x_l2_entry e;
+       u64 seed = rtl930x_l2_hash_seed(nh->mac, nh->vid);
+       u32 key = rtl930x_l2_hash_key(priv, seed);
+       int i, idx = -1;
+       u64 entry;
+
+       pr_info("%s searching for %08llx vid %d with key %d, seed: %016llx\n",
+               __func__, nh->mac, nh->vid, key, seed);
+       
+       e.type = L2_UNICAST;
+       e.rvid = nh->fid; // Verify its the forwarding ID!!! l2_entry.un.unicast.fid
+       u64_to_ether_addr(nh->mac, &e.mac[0]);
+       e.port = RTL930X_PORT_IGNORE;
+
+       // Loop over all entries in the hash-bucket and over the second block on 93xx SoCs
+       for (i = 0; i < priv->l2_bucket_size; i++) {
+               entry = rtl930x_read_l2_entry_using_hash(key, i, &e);
+               pr_info("%s i: %d, entry %016llx, seed %016llx\n", __func__, i, entry, seed);
+               if (e.valid && e.next_hop)
+                       continue;
+               if (!e.valid || ((entry & 0x0fffffffffffffffULL) == seed)) {
+                       idx = i > 3 ? ((key >> 14) & 0xffff) | i >> 1
+                                       : ((key << 2) | i) & 0xffff;
+                       break;
+               }
+       }
+
+       pr_info("%s: found idx %d and i %d\n", __func__, idx, i);
+
+       if (idx < 0) {
+               pr_err("%s: No more L2 forwarding entries available\n", __func__);
+               return -1;
+       }
+
+       // Found an existing or empty entry, make it a nexthop entry
+       pr_info("%s BEFORE -> key %d, pos: %d, index: %d\n", __func__, key, i, idx);
+       dump_l2_entry(&e);
+       nh->l2_id = idx;
+
+       // Found an existing (e->valid is true) or empty entry, make it a nexthop entry
+       if (e.valid) {
+               nh->port = e.port;
+               nh->fid = e.rvid;
+               nh->vid = e.vid;
+               nh->dev_id = e.stack_dev;
+       } else {
+               e.valid = true;
+               e.is_static = false;
+               e.vid = nh->vid;
+               e.rvid = nh->fid;
+               e.port = RTL930X_PORT_IGNORE;
+               u64_to_ether_addr(nh->mac, &e.mac[0]);
+       }
+       e.next_hop = true;
+       // For nexthop entries, the vid field in the table is used to denote the dest mac_id
+       e.nh_route_id = nh->mac_id;
+       pr_info("%s AFTER\n", __func__);
+       dump_l2_entry(&e);
+
+       rtl930x_write_l2_entry_using_hash(idx >> 2, idx & 0x3, &e);
+
+       // _dal_longan_l2_nexthop_add
+       return 0;
 }
 
 static u64 rtl930x_read_mcast_pmask(int idx)
@@ -485,7 +754,6 @@ int rtl930x_read_phy(u32 port, u32 page, u32 reg, u32 *val)
        u32 v;
        int err = 0;
 
-//     pr_info("In %s\n", __func__);
        if (port > 63 || page > 4095 || reg > 31)
                return -ENOTSUPP;
 
@@ -531,12 +799,12 @@ int rtl930x_write_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 val)
        // Set MMD device number and register to write to
        sw_w32(devnum << 16 | (regnum & 0xffff), RTL930X_SMI_ACCESS_PHY_CTRL_3);
 
-       v = BIT(2)| BIT(1)| BIT(0); // WRITE | MMD-access | EXEC
+       v = BIT(2) | BIT(1) | BIT(0); // WRITE | MMD-access | EXEC
        sw_w32(v, RTL930X_SMI_ACCESS_PHY_CTRL_1);
 
        do {
                v = sw_r32(RTL930X_SMI_ACCESS_PHY_CTRL_1);
-       } while ( v & BIT(0));
+       } while (v & BIT(0));
 
        pr_debug("%s: port %d, regnum: %x, val: %x (err %d)\n", __func__, port, regnum, val, err);
        mutex_unlock(&smi_lock);
@@ -559,12 +827,12 @@ int rtl930x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val)
        // Set MMD device number and register to write to
        sw_w32(devnum << 16 | (regnum & 0xffff), RTL930X_SMI_ACCESS_PHY_CTRL_3);
 
-       v = BIT(1)| BIT(0); // MMD-access | EXEC
+       v = BIT(1) | BIT(0); // MMD-access | EXEC
        sw_w32(v, RTL930X_SMI_ACCESS_PHY_CTRL_1);
 
        do {
                v = sw_r32(RTL930X_SMI_ACCESS_PHY_CTRL_1);
-       } while ( v & 0x1);
+       } while (v & BIT(0));
        // There is no error-checking via BIT 25 of v, as it does not seem to be set correctly
        *val = (sw_r32(RTL930X_SMI_ACCESS_PHY_CTRL_2) & 0xffff);
        pr_debug("%s: port %d, regnum: %x, val: %x (err %d)\n", __func__, port, regnum, *val, err);
@@ -574,7 +842,6 @@ int rtl930x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val)
        return err;
 }
 
-
 /*
  * Calculate both the block 0 and the block 1 hash, and return in
  * lower and higher word of the return value since only 12 bit of
@@ -617,6 +884,7 @@ u32 rtl930x_hash(struct rtl838x_switch_priv *priv, u64 seed)
 
        return h;
 }
+
 /*
  * Enables or disables the EEE/EEEP capability of a port
  */
@@ -754,7 +1022,9 @@ const struct rtl838x_reg rtl930x_reg = {
        .mac_rx_pause_sts = RTL930X_MAC_RX_PAUSE_STS,
        .mac_tx_pause_sts = RTL930X_MAC_TX_PAUSE_STS,
        .read_l2_entry_using_hash = rtl930x_read_l2_entry_using_hash,
+       .write_l2_entry_using_hash = rtl930x_write_l2_entry_using_hash,
        .read_cam = rtl930x_read_cam,
+       .write_cam = rtl930x_write_cam,
        .vlan_port_egr_filter = RTL930X_VLAN_PORT_EGR_FLTR,
        .vlan_port_igr_filter = RTL930X_VLAN_PORT_IGR_FLTR(0),
        .vlan_port_pb = RTL930X_VLAN_PORT_PB_VLAN,