|
@@ -25,6 +25,8 @@
|
|
#include <linux/ethtool.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/if_bridge.h>
|
|
#include <linux/if_bridge.h>
|
|
#include <linux/brcmphy.h>
|
|
#include <linux/brcmphy.h>
|
|
|
|
+#include <linux/etherdevice.h>
|
|
|
|
+#include <net/switchdev.h>
|
|
|
|
|
|
#include "bcm_sf2.h"
|
|
#include "bcm_sf2.h"
|
|
#include "bcm_sf2_regs.h"
|
|
#include "bcm_sf2_regs.h"
|
|
@@ -555,6 +557,236 @@ static int bcm_sf2_sw_br_set_stp_state(struct dsa_switch *ds, int port,
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/* Address Resolution Logic routines */
|
|
|
|
+static int bcm_sf2_arl_op_wait(struct bcm_sf2_priv *priv)
|
|
|
|
+{
|
|
|
|
+ unsigned int timeout = 10;
|
|
|
|
+ u32 reg;
|
|
|
|
+
|
|
|
|
+ do {
|
|
|
|
+ reg = core_readl(priv, CORE_ARLA_RWCTL);
|
|
|
|
+ if (!(reg & ARL_STRTDN))
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ usleep_range(1000, 2000);
|
|
|
|
+ } while (timeout--);
|
|
|
|
+
|
|
|
|
+ return -ETIMEDOUT;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int bcm_sf2_arl_rw_op(struct bcm_sf2_priv *priv, unsigned int op)
|
|
|
|
+{
|
|
|
|
+ u32 cmd;
|
|
|
|
+
|
|
|
|
+ if (op > ARL_RW)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ cmd = core_readl(priv, CORE_ARLA_RWCTL);
|
|
|
|
+ cmd &= ~IVL_SVL_SELECT;
|
|
|
|
+ cmd |= ARL_STRTDN;
|
|
|
|
+ if (op)
|
|
|
|
+ cmd |= ARL_RW;
|
|
|
|
+ else
|
|
|
|
+ cmd &= ~ARL_RW;
|
|
|
|
+ core_writel(priv, cmd, CORE_ARLA_RWCTL);
|
|
|
|
+
|
|
|
|
+ return bcm_sf2_arl_op_wait(priv);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int bcm_sf2_arl_read(struct bcm_sf2_priv *priv, u64 mac,
|
|
|
|
+ u16 vid, struct bcm_sf2_arl_entry *ent, u8 *idx,
|
|
|
|
+ bool is_valid)
|
|
|
|
+{
|
|
|
|
+ unsigned int i;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ ret = bcm_sf2_arl_op_wait(priv);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ /* Read the 4 bins */
|
|
|
|
+ for (i = 0; i < 4; i++) {
|
|
|
|
+ u64 mac_vid;
|
|
|
|
+ u32 fwd_entry;
|
|
|
|
+
|
|
|
|
+ mac_vid = core_readq(priv, CORE_ARLA_MACVID_ENTRY(i));
|
|
|
|
+ fwd_entry = core_readl(priv, CORE_ARLA_FWD_ENTRY(i));
|
|
|
|
+ bcm_sf2_arl_to_entry(ent, mac_vid, fwd_entry);
|
|
|
|
+
|
|
|
|
+ if (ent->is_valid && is_valid) {
|
|
|
|
+ *idx = i;
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* This is the MAC we just deleted */
|
|
|
|
+ if (!is_valid && (mac_vid & mac))
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return -ENOENT;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int bcm_sf2_arl_op(struct bcm_sf2_priv *priv, int op, int port,
|
|
|
|
+ const unsigned char *addr, u16 vid, bool is_valid)
|
|
|
|
+{
|
|
|
|
+ struct bcm_sf2_arl_entry ent;
|
|
|
|
+ u32 fwd_entry;
|
|
|
|
+ u64 mac, mac_vid = 0;
|
|
|
|
+ u8 idx = 0;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ /* Convert the array into a 64-bit MAC */
|
|
|
|
+ mac = bcm_sf2_mac_to_u64(addr);
|
|
|
|
+
|
|
|
|
+ /* Perform a read for the given MAC and VID */
|
|
|
|
+ core_writeq(priv, mac, CORE_ARLA_MAC);
|
|
|
|
+ core_writel(priv, vid, CORE_ARLA_VID);
|
|
|
|
+
|
|
|
|
+ /* Issue a read operation for this MAC */
|
|
|
|
+ ret = bcm_sf2_arl_rw_op(priv, 1);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ ret = bcm_sf2_arl_read(priv, mac, vid, &ent, &idx, is_valid);
|
|
|
|
+ /* If this is a read, just finish now */
|
|
|
|
+ if (op)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ /* We could not find a matching MAC, so reset to a new entry */
|
|
|
|
+ if (ret) {
|
|
|
|
+ fwd_entry = 0;
|
|
|
|
+ idx = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ memset(&ent, 0, sizeof(ent));
|
|
|
|
+ ent.port = port;
|
|
|
|
+ ent.is_valid = is_valid;
|
|
|
|
+ ent.vid = vid;
|
|
|
|
+ ent.is_static = true;
|
|
|
|
+ memcpy(ent.mac, addr, ETH_ALEN);
|
|
|
|
+ bcm_sf2_arl_from_entry(&mac_vid, &fwd_entry, &ent);
|
|
|
|
+
|
|
|
|
+ core_writeq(priv, mac_vid, CORE_ARLA_MACVID_ENTRY(idx));
|
|
|
|
+ core_writel(priv, fwd_entry, CORE_ARLA_FWD_ENTRY(idx));
|
|
|
|
+
|
|
|
|
+ ret = bcm_sf2_arl_rw_op(priv, 0);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ /* Re-read the entry to check */
|
|
|
|
+ return bcm_sf2_arl_read(priv, mac, vid, &ent, &idx, is_valid);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int bcm_sf2_sw_fdb_prepare(struct dsa_switch *ds, int port,
|
|
|
|
+ const struct switchdev_obj_port_fdb *fdb,
|
|
|
|
+ struct switchdev_trans *trans)
|
|
|
|
+{
|
|
|
|
+ /* We do not need to do anything specific here yet */
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int bcm_sf2_sw_fdb_add(struct dsa_switch *ds, int port,
|
|
|
|
+ const struct switchdev_obj_port_fdb *fdb,
|
|
|
|
+ struct switchdev_trans *trans)
|
|
|
|
+{
|
|
|
|
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
|
|
|
|
+
|
|
|
|
+ return bcm_sf2_arl_op(priv, 0, port, fdb->addr, fdb->vid, true);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int bcm_sf2_sw_fdb_del(struct dsa_switch *ds, int port,
|
|
|
|
+ const struct switchdev_obj_port_fdb *fdb)
|
|
|
|
+{
|
|
|
|
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
|
|
|
|
+
|
|
|
|
+ return bcm_sf2_arl_op(priv, 0, port, fdb->addr, fdb->vid, false);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int bcm_sf2_arl_search_wait(struct bcm_sf2_priv *priv)
|
|
|
|
+{
|
|
|
|
+ unsigned timeout = 1000;
|
|
|
|
+ u32 reg;
|
|
|
|
+
|
|
|
|
+ do {
|
|
|
|
+ reg = core_readl(priv, CORE_ARLA_SRCH_CTL);
|
|
|
|
+ if (!(reg & ARLA_SRCH_STDN))
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ if (reg & ARLA_SRCH_VLID)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ usleep_range(1000, 2000);
|
|
|
|
+ } while (timeout--);
|
|
|
|
+
|
|
|
|
+ return -ETIMEDOUT;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void bcm_sf2_arl_search_rd(struct bcm_sf2_priv *priv, u8 idx,
|
|
|
|
+ struct bcm_sf2_arl_entry *ent)
|
|
|
|
+{
|
|
|
|
+ u64 mac_vid;
|
|
|
|
+ u32 fwd_entry;
|
|
|
|
+
|
|
|
|
+ mac_vid = core_readq(priv, CORE_ARLA_SRCH_RSLT_MACVID(idx));
|
|
|
|
+ fwd_entry = core_readl(priv, CORE_ARLA_SRCH_RSLT(idx));
|
|
|
|
+ bcm_sf2_arl_to_entry(ent, mac_vid, fwd_entry);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int bcm_sf2_sw_fdb_copy(struct net_device *dev, int port,
|
|
|
|
+ const struct bcm_sf2_arl_entry *ent,
|
|
|
|
+ struct switchdev_obj_port_fdb *fdb,
|
|
|
|
+ int (*cb)(struct switchdev_obj *obj))
|
|
|
|
+{
|
|
|
|
+ if (!ent->is_valid)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ if (port != ent->port)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ ether_addr_copy(fdb->addr, ent->mac);
|
|
|
|
+ fdb->vid = ent->vid;
|
|
|
|
+ fdb->ndm_state = ent->is_static ? NUD_NOARP : NUD_REACHABLE;
|
|
|
|
+
|
|
|
|
+ return cb(&fdb->obj);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int bcm_sf2_sw_fdb_dump(struct dsa_switch *ds, int port,
|
|
|
|
+ struct switchdev_obj_port_fdb *fdb,
|
|
|
|
+ int (*cb)(struct switchdev_obj *obj))
|
|
|
|
+{
|
|
|
|
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
|
|
|
|
+ struct net_device *dev = ds->ports[port];
|
|
|
|
+ struct bcm_sf2_arl_entry results[2];
|
|
|
|
+ unsigned int count = 0;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ /* Start search operation */
|
|
|
|
+ core_writel(priv, ARLA_SRCH_STDN, CORE_ARLA_SRCH_CTL);
|
|
|
|
+
|
|
|
|
+ do {
|
|
|
|
+ ret = bcm_sf2_arl_search_wait(priv);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ /* Read both entries, then return their values back */
|
|
|
|
+ bcm_sf2_arl_search_rd(priv, 0, &results[0]);
|
|
|
|
+ ret = bcm_sf2_sw_fdb_copy(dev, port, &results[0], fdb, cb);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ bcm_sf2_arl_search_rd(priv, 1, &results[1]);
|
|
|
|
+ ret = bcm_sf2_sw_fdb_copy(dev, port, &results[1], fdb, cb);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ if (!results[0].is_valid && !results[1].is_valid)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ } while (count++ < CORE_ARLA_NUM_ENTRIES);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
static irqreturn_t bcm_sf2_switch_0_isr(int irq, void *dev_id)
|
|
static irqreturn_t bcm_sf2_switch_0_isr(int irq, void *dev_id)
|
|
{
|
|
{
|
|
struct bcm_sf2_priv *priv = dev_id;
|
|
struct bcm_sf2_priv *priv = dev_id;
|
|
@@ -1076,6 +1308,10 @@ static struct dsa_switch_driver bcm_sf2_switch_driver = {
|
|
.port_join_bridge = bcm_sf2_sw_br_join,
|
|
.port_join_bridge = bcm_sf2_sw_br_join,
|
|
.port_leave_bridge = bcm_sf2_sw_br_leave,
|
|
.port_leave_bridge = bcm_sf2_sw_br_leave,
|
|
.port_stp_update = bcm_sf2_sw_br_set_stp_state,
|
|
.port_stp_update = bcm_sf2_sw_br_set_stp_state,
|
|
|
|
+ .port_fdb_prepare = bcm_sf2_sw_fdb_prepare,
|
|
|
|
+ .port_fdb_add = bcm_sf2_sw_fdb_add,
|
|
|
|
+ .port_fdb_del = bcm_sf2_sw_fdb_del,
|
|
|
|
+ .port_fdb_dump = bcm_sf2_sw_fdb_dump,
|
|
};
|
|
};
|
|
|
|
|
|
static int __init bcm_sf2_init(void)
|
|
static int __init bcm_sf2_init(void)
|