summaryrefslogtreecommitdiffstats
path: root/drivers/net/dsa/bcm_sf2.c
diff options
context:
space:
mode:
authorFlorian Fainelli2015-02-24 22:15:34 +0100
committerDavid S. Miller2015-02-25 23:03:38 +0100
commit12f460f23423e81d6dd3efeb78906ae634ad8fc9 (patch)
treed0a46201df407c4719abade5cd44bcb86041b195 /drivers/net/dsa/bcm_sf2.c
parentnet: dsa: integrate with SWITCHDEV for HW bridging (diff)
downloadkernel-qcow2-linux-12f460f23423e81d6dd3efeb78906ae634ad8fc9.tar.gz
kernel-qcow2-linux-12f460f23423e81d6dd3efeb78906ae634ad8fc9.tar.xz
kernel-qcow2-linux-12f460f23423e81d6dd3efeb78906ae634ad8fc9.zip
net: dsa: bcm_sf2: add HW bridging support
Implement the bridge join, leave and set_stp callbacks by making that we do the following: - when a port joins the bridge, all existing ports in the bridge get their VLAN control register updated with that joining port - the joining port is including all existing bridge ports in its own VLAN control register The leave operation is fairly similar, special care must be taken to make sure that port leaving the bridging is not removing itself from its own VLAN control register. Since the various BR_* states apply directly to our HW semantics, we just need to translate these constants into their corresponding HW settings, and voila! We make sure to trigger a fast-ageing process for ports that are joining/leaving the bridge and transition from incompatible states, this is equivalent to triggering an ARL flush for that port. Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/dsa/bcm_sf2.c')
-rw-r--r--drivers/net/dsa/bcm_sf2.c155
1 files changed, 154 insertions, 1 deletions
diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c
index 4daffb284931..cedb572bf25a 100644
--- a/drivers/net/dsa/bcm_sf2.c
+++ b/drivers/net/dsa/bcm_sf2.c
@@ -23,6 +23,7 @@
#include <linux/of_address.h>
#include <net/dsa.h>
#include <linux/ethtool.h>
+#include <linux/if_bridge.h>
#include "bcm_sf2.h"
#include "bcm_sf2_regs.h"
@@ -299,10 +300,14 @@ static int bcm_sf2_port_setup(struct dsa_switch *ds, int port,
if (port == 7)
intrl2_1_mask_clear(priv, P_IRQ_MASK(P7_IRQ_OFF));
- /* Set this port, and only this one to be in the default VLAN */
+ /* Set this port, and only this one to be in the default VLAN,
+ * if member of a bridge, restore its membership prior to
+ * bringing down this port.
+ */
reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(port));
reg &= ~PORT_VLAN_CTRL_MASK;
reg |= (1 << port);
+ reg |= priv->port_sts[port].vlan_ctl_mask;
core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(port));
bcm_sf2_imp_vlan_setup(ds, cpu_port);
@@ -400,6 +405,151 @@ static int bcm_sf2_sw_set_eee(struct dsa_switch *ds, int port,
return 0;
}
+/* Fast-ageing of ARL entries for a given port, equivalent to an ARL
+ * flush for that port.
+ */
+static int bcm_sf2_sw_fast_age_port(struct dsa_switch *ds, int port)
+{
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+ unsigned int timeout = 1000;
+ u32 reg;
+
+ core_writel(priv, port, CORE_FAST_AGE_PORT);
+
+ reg = core_readl(priv, CORE_FAST_AGE_CTRL);
+ reg |= EN_AGE_PORT | FAST_AGE_STR_DONE;
+ core_writel(priv, reg, CORE_FAST_AGE_CTRL);
+
+ do {
+ reg = core_readl(priv, CORE_FAST_AGE_CTRL);
+ if (!(reg & FAST_AGE_STR_DONE))
+ break;
+
+ cpu_relax();
+ } while (timeout--);
+
+ if (!timeout)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int bcm_sf2_sw_br_join(struct dsa_switch *ds, int port,
+ u32 br_port_mask)
+{
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+ unsigned int i;
+ u32 reg, p_ctl;
+
+ p_ctl = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(port));
+
+ for (i = 0; i < priv->hw_params.num_ports; i++) {
+ if (!((1 << i) & br_port_mask))
+ continue;
+
+ /* Add this local port to the remote port VLAN control
+ * membership and update the remote port bitmask
+ */
+ reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(i));
+ reg |= 1 << port;
+ core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(i));
+ priv->port_sts[i].vlan_ctl_mask = reg;
+
+ p_ctl |= 1 << i;
+ }
+
+ /* Configure the local port VLAN control membership to include
+ * remote ports and update the local port bitmask
+ */
+ core_writel(priv, p_ctl, CORE_PORT_VLAN_CTL_PORT(port));
+ priv->port_sts[port].vlan_ctl_mask = p_ctl;
+
+ return 0;
+}
+
+static int bcm_sf2_sw_br_leave(struct dsa_switch *ds, int port,
+ u32 br_port_mask)
+{
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+ unsigned int i;
+ u32 reg, p_ctl;
+
+ p_ctl = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(port));
+
+ for (i = 0; i < priv->hw_params.num_ports; i++) {
+ /* Don't touch the remaining ports */
+ if (!((1 << i) & br_port_mask))
+ continue;
+
+ reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(i));
+ reg &= ~(1 << port);
+ core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(i));
+ priv->port_sts[port].vlan_ctl_mask = reg;
+
+ /* Prevent self removal to preserve isolation */
+ if (port != i)
+ p_ctl &= ~(1 << i);
+ }
+
+ core_writel(priv, p_ctl, CORE_PORT_VLAN_CTL_PORT(port));
+ priv->port_sts[port].vlan_ctl_mask = p_ctl;
+
+ return 0;
+}
+
+static int bcm_sf2_sw_br_set_stp_state(struct dsa_switch *ds, int port,
+ u8 state)
+{
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+ u8 hw_state, cur_hw_state;
+ int ret = 0;
+ u32 reg;
+
+ reg = core_readl(priv, CORE_G_PCTL_PORT(port));
+ cur_hw_state = reg >> G_MISTP_STATE_SHIFT;
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ hw_state = G_MISTP_DIS_STATE;
+ break;
+ case BR_STATE_LISTENING:
+ hw_state = G_MISTP_LISTEN_STATE;
+ break;
+ case BR_STATE_LEARNING:
+ hw_state = G_MISTP_LEARN_STATE;
+ break;
+ case BR_STATE_FORWARDING:
+ hw_state = G_MISTP_FWD_STATE;
+ break;
+ case BR_STATE_BLOCKING:
+ hw_state = G_MISTP_BLOCK_STATE;
+ break;
+ default:
+ pr_err("%s: invalid STP state: %d\n", __func__, state);
+ return -EINVAL;
+ }
+
+ /* Fast-age ARL entries if we are moving a port from Learning or
+ * Forwarding state to Disabled, Blocking or Listening state
+ */
+ if (cur_hw_state != hw_state) {
+ if (cur_hw_state & 4 && !(hw_state & 4)) {
+ ret = bcm_sf2_sw_fast_age_port(ds, port);
+ if (ret) {
+ pr_err("%s: fast-ageing failed\n", __func__);
+ return ret;
+ }
+ }
+ }
+
+ reg = core_readl(priv, CORE_G_PCTL_PORT(port));
+ reg &= ~(G_MISTP_STATE_MASK << G_MISTP_STATE_SHIFT);
+ reg |= hw_state;
+ core_writel(priv, reg, CORE_G_PCTL_PORT(port));
+
+ return 0;
+}
+
static irqreturn_t bcm_sf2_switch_0_isr(int irq, void *dev_id)
{
struct bcm_sf2_priv *priv = dev_id;
@@ -916,6 +1066,9 @@ static struct dsa_switch_driver bcm_sf2_switch_driver = {
.port_disable = bcm_sf2_port_disable,
.get_eee = bcm_sf2_sw_get_eee,
.set_eee = bcm_sf2_sw_set_eee,
+ .port_join_bridge = bcm_sf2_sw_br_join,
+ .port_leave_bridge = bcm_sf2_sw_br_leave,
+ .port_stp_update = bcm_sf2_sw_br_set_stp_state,
};
static int __init bcm_sf2_init(void)