Commit 1f52247e authored by David S. Miller's avatar David S. Miller

Merge branch 'sja1105-H'

Vladimir Oltean says:

====================
NXP SJA1105 driver support for "H" switch topologies

Changes in v3:
Preserve the behavior of dsa_tree_setup_default_cpu() which is to pick
the first CPU port and not the last.

Changes in v2:
Send as non-RFC, drop the patches for discarding DSA-tagged packets on
user ports and DSA-untagged packets on DSA and CPU ports for now.

NXP builds boards like the Bluebox 3 where there are multiple SJA1110
switches connected to an LX2160A, but they are also connected to each
other. I call this topology an "H" tree because of the lateral
connection between switches. A piece extracted from a non-upstream
device tree looks like this:

&spi_bridge {
        /* SW1 */
        ethernet-switch@0 {
                compatible = "nxp,sja1110a";
                reg = <0>;
                dsa,member = <0 0>;

                ethernet-ports {
                        #address-cells = <1>;
                        #size-cells = <0>;

                        /* SW1_P1 */
                        port@1 {
                                reg = <1>;
                                label = "con_2x20";
                                phy-mode = "sgmii";

                                fixed-link {
                                        speed = <1000>;
                                        full-duplex;
                                };
                        };

                        port@2 {
                                reg = <2>;
                                ethernet = <&dpmac17>;
                                phy-mode = "rgmii-id";

                                fixed-link {
                                        speed = <1000>;
                                        full-duplex;
                                };
                        };

                        port@3 {
                                reg = <3>;
                                label = "1ge_p1";
                                phy-mode = "rgmii-id";
                                phy-handle = <&sw1_mii3_phy>;
                        };

                        sw1p4: port@4 {
                                reg = <4>;
                                link = <&sw2p1>;
                                phy-mode = "sgmii";

                                fixed-link {
                                        speed = <1000>;
                                        full-duplex;
                                };
                        };

                        port@5 {
                                reg = <5>;
                                label = "trx1";
                                phy-mode = "internal";
                                phy-handle = <&sw1_port5_base_t1_phy>;
                        };

                        port@6 {
                                reg = <6>;
                                label = "trx2";
                                phy-mode = "internal";
                                phy-handle = <&sw1_port6_base_t1_phy>;
                        };

                        port@7 {
                                reg = <7>;
                                label = "trx3";
                                phy-mode = "internal";
                                phy-handle = <&sw1_port7_base_t1_phy>;
                        };

                        port@8 {
                                reg = <8>;
                                label = "trx4";
                                phy-mode = "internal";
                                phy-handle = <&sw1_port8_base_t1_phy>;
                        };

                        port@9 {
                                reg = <9>;
                                label = "trx5";
                                phy-mode = "internal";
                                phy-handle = <&sw1_port9_base_t1_phy>;
                        };

                        port@a {
                                reg = <10>;
                                label = "trx6";
                                phy-mode = "internal";
                                phy-handle = <&sw1_port10_base_t1_phy>;
                        };
                };
        };

        /* SW2 */
        ethernet-switch@2 {
                compatible = "nxp,sja1110a";
                reg = <2>;
                dsa,member = <0 1>;

                ethernet-ports {
                        #address-cells = <1>;
                        #size-cells = <0>;

                        sw2p1: port@1 {
                                reg = <1>;
                                link = <&sw1p4>;
                                phy-mode = "sgmii";

                                fixed-link {
                                        speed = <1000>;
                                        full-duplex;
                                };
                        };

                        port@2 {
                                reg = <2>;
                                ethernet = <&dpmac18>;
                                phy-mode = "rgmii-id";

                                fixed-link {
                                        speed = <1000>;
                                        full-duplex;
                                };
                        };

                        port@3 {
                                reg = <3>;
                                label = "1ge_p2";
                                phy-mode = "rgmii-id";
                                phy-handle = <&sw2_mii3_phy>;
                        };

                        port@4 {
                                reg = <4>;
                                label = "to_sw3";
                                phy-mode = "2500base-x";

                                fixed-link {
                                        speed = <2500>;
                                        full-duplex;
                                };
                        };

                        port@5 {
                                reg = <5>;
                                label = "trx7";
                                phy-mode = "internal";
                                phy-handle = <&sw2_port5_base_t1_phy>;
                        };

                        port@6 {
                                reg = <6>;
                                label = "trx8";
                                phy-mode = "internal";
                                phy-handle = <&sw2_port6_base_t1_phy>;
                        };

                        port@7 {
                                reg = <7>;
                                label = "trx9";
                                phy-mode = "internal";
                                phy-handle = <&sw2_port7_base_t1_phy>;
                        };

                        port@8 {
                                reg = <8>;
                                label = "trx10";
                                phy-mode = "internal";
                                phy-handle = <&sw2_port8_base_t1_phy>;
                        };

                        port@9 {
                                reg = <9>;
                                label = "trx11";
                                phy-mode = "internal";
                                phy-handle = <&sw2_port9_base_t1_phy>;
                        };

                        port@a {
                                reg = <10>;
                                label = "trx12";
                                phy-mode = "internal";
                                phy-handle = <&sw2_port10_base_t1_phy>;
                        };
                };
        };
};

Basically it is a single DSA tree with 2 "ethernet" properties, i.e. a
multi-CPU-port system. There is also a DSA link between the switches,
but it is not a daisy chain topology, i.e. there is no "upstream" and
"downstream" switch, the DSA link is only to be used for the bridge data
plane (autonomous forwarding between switches, between the RJ-45 ports
and the automotive Ethernet ports), otherwise all traffic that should
reach the host should do so through the dedicated CPU port of the switch.

Of course, plain forwarding in this topology is bound to create packet
loops. I have thought long and hard about strategies to cut forwarding
in such a way as to prevent loops but also not impede normal operation
of the network on such a system, and I believe I have found a solution
that does work as expected. This relies heavily on DSA's recent ability
to perform RX filtering towards the host by installing MAC addresses as
static FDB entries. Since we have 2 distinct DSA masters, we have 2
distinct MAC addresses, and if the bridge is configured to have its own
MAC address that makes it 3 distinct MAC addresses. The bridge core,
plus the switchdev_handle_fdb_add_to_device() extension, handle each MAC
address by replicating it to each port of the DSA switch tree. So the
end result is that both switch 1 and switch 2 will have static FDB
entries towards their respective CPU ports for the 3 MAC addresses
corresponding to the DSA masters and to the bridge net device (and of
course, towards any station learned on a foreign interface).

So I think the basic design works, and it is basically just as fragile
as any other multi-CPU-port system is bound to be in terms of reliance
on static FDB entries towards the host (if hardware address learning on
the CPU port is to be used, MAC addresses would randomly bounce between
one CPU port and the other otherwise). In fact, I think it is even
better to start DSA's support of multi-CPU-port systems with something
small like the NXP Bluebox 3, because we allow some time for the code
paths like dsa_switch_host_address_match(), which were specifically
designed for it, to break in, and this board needs no user space
configuration of CPU ports, like static assignments between user and CPU
ports, or bonding between the CPU ports/DSA masters.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 0fd75f57 81d45898
......@@ -199,9 +199,13 @@ static int sja1105_init_mac_settings(struct sja1105_private *priv)
mac[i] = default_mac;
/* Let sja1105_bridge_stp_state_set() keep address learning
* enabled for the CPU port.
* enabled for the DSA ports. CPU ports use software-assisted
* learning to ensure that only FDB entries belonging to the
* bridge are learned, and that they are learned towards all
* CPU ports in a cross-chip topology if multiple CPU ports
* exist.
*/
if (dsa_is_cpu_port(ds, i))
if (dsa_is_dsa_port(ds, i))
priv->learn_ena |= BIT(i);
}
......@@ -460,7 +464,7 @@ static int sja1105_init_static_vlan(struct sja1105_private *priv)
pvid.vlan_bc |= BIT(port);
pvid.tag_port &= ~BIT(port);
if (dsa_is_cpu_port(ds, port)) {
if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)) {
priv->tag_8021q_pvid[port] = SJA1105_DEFAULT_VLAN;
priv->bridge_pvid[port] = SJA1105_DEFAULT_VLAN;
}
......@@ -474,8 +478,11 @@ static int sja1105_init_l2_forwarding(struct sja1105_private *priv)
{
struct sja1105_l2_forwarding_entry *l2fwd;
struct dsa_switch *ds = priv->ds;
struct dsa_switch_tree *dst;
struct sja1105_table *table;
int i, j;
struct dsa_link *dl;
int port, tc;
int from, to;
table = &priv->static_config.tables[BLK_IDX_L2_FORWARDING];
......@@ -493,47 +500,109 @@ static int sja1105_init_l2_forwarding(struct sja1105_private *priv)
l2fwd = table->entries;
/* First 5 entries define the forwarding rules */
for (i = 0; i < ds->num_ports; i++) {
unsigned int upstream = dsa_upstream_port(priv->ds, i);
/* First 5 entries in the L2 Forwarding Table define the forwarding
* rules and the VLAN PCP to ingress queue mapping.
* Set up the ingress queue mapping first.
*/
for (port = 0; port < ds->num_ports; port++) {
if (dsa_is_unused_port(ds, port))
continue;
if (dsa_is_unused_port(ds, i))
for (tc = 0; tc < SJA1105_NUM_TC; tc++)
l2fwd[port].vlan_pmap[tc] = tc;
}
/* Then manage the forwarding domain for user ports. These can forward
* only to the always-on domain (CPU port and DSA links)
*/
for (from = 0; from < ds->num_ports; from++) {
if (!dsa_is_user_port(ds, from))
continue;
for (j = 0; j < SJA1105_NUM_TC; j++)
l2fwd[i].vlan_pmap[j] = j;
for (to = 0; to < ds->num_ports; to++) {
if (!dsa_is_cpu_port(ds, to) &&
!dsa_is_dsa_port(ds, to))
continue;
/* All ports start up with egress flooding enabled,
* including the CPU port.
*/
priv->ucast_egress_floods |= BIT(i);
priv->bcast_egress_floods |= BIT(i);
l2fwd[from].bc_domain |= BIT(to);
l2fwd[from].fl_domain |= BIT(to);
if (i == upstream)
sja1105_port_allow_traffic(l2fwd, from, to, true);
}
}
/* Then manage the forwarding domain for DSA links and CPU ports (the
* always-on domain). These can send packets to any enabled port except
* themselves.
*/
for (from = 0; from < ds->num_ports; from++) {
if (!dsa_is_cpu_port(ds, from) && !dsa_is_dsa_port(ds, from))
continue;
sja1105_port_allow_traffic(l2fwd, i, upstream, true);
sja1105_port_allow_traffic(l2fwd, upstream, i, true);
for (to = 0; to < ds->num_ports; to++) {
if (dsa_is_unused_port(ds, to))
continue;
if (from == to)
continue;
l2fwd[i].bc_domain = BIT(upstream);
l2fwd[i].fl_domain = BIT(upstream);
l2fwd[from].bc_domain |= BIT(to);
l2fwd[from].fl_domain |= BIT(to);
l2fwd[upstream].bc_domain |= BIT(i);
l2fwd[upstream].fl_domain |= BIT(i);
sja1105_port_allow_traffic(l2fwd, from, to, true);
}
}
/* In odd topologies ("H" connections where there is a DSA link to
* another switch which also has its own CPU port), TX packets can loop
* back into the system (they are flooded from CPU port 1 to the DSA
* link, and from there to CPU port 2). Prevent this from happening by
* cutting RX from DSA links towards our CPU port, if the remote switch
* has its own CPU port and therefore doesn't need ours for network
* stack termination.
*/
dst = ds->dst;
list_for_each_entry(dl, &dst->rtable, list) {
if (dl->dp->ds != ds || dl->link_dp->cpu_dp == dl->dp->cpu_dp)
continue;
from = dl->dp->index;
to = dsa_upstream_port(ds, from);
dev_warn(ds->dev,
"H topology detected, cutting RX from DSA link %d to CPU port %d to prevent TX packet loops\n",
from, to);
sja1105_port_allow_traffic(l2fwd, from, to, false);
l2fwd[from].bc_domain &= ~BIT(to);
l2fwd[from].fl_domain &= ~BIT(to);
}
/* Finally, manage the egress flooding domain. All ports start up with
* flooding enabled, including the CPU port and DSA links.
*/
for (port = 0; port < ds->num_ports; port++) {
if (dsa_is_unused_port(ds, port))
continue;
priv->ucast_egress_floods |= BIT(port);
priv->bcast_egress_floods |= BIT(port);
}
/* Next 8 entries define VLAN PCP mapping from ingress to egress.
* Create a one-to-one mapping.
*/
for (i = 0; i < SJA1105_NUM_TC; i++) {
for (j = 0; j < ds->num_ports; j++) {
if (dsa_is_unused_port(ds, j))
for (tc = 0; tc < SJA1105_NUM_TC; tc++) {
for (port = 0; port < ds->num_ports; port++) {
if (dsa_is_unused_port(ds, port))
continue;
l2fwd[ds->num_ports + i].vlan_pmap[j] = i;
l2fwd[ds->num_ports + tc].vlan_pmap[port] = tc;
}
l2fwd[ds->num_ports + i].type_egrpcp2outputq = true;
l2fwd[ds->num_ports + tc].type_egrpcp2outputq = true;
}
return 0;
......@@ -688,6 +757,72 @@ static void sja1110_select_tdmaconfigidx(struct sja1105_private *priv)
general_params->tdmaconfigidx = tdmaconfigidx;
}
static int sja1105_init_topology(struct sja1105_private *priv,
struct sja1105_general_params_entry *general_params)
{
struct dsa_switch *ds = priv->ds;
int port;
/* The host port is the destination for traffic matching mac_fltres1
* and mac_fltres0 on all ports except itself. Default to an invalid
* value.
*/
general_params->host_port = ds->num_ports;
/* Link-local traffic received on casc_port will be forwarded
* to host_port without embedding the source port and device ID
* info in the destination MAC address, and no RX timestamps will be
* taken either (presumably because it is a cascaded port and a
* downstream SJA switch already did that).
* To disable the feature, we need to do different things depending on
* switch generation. On SJA1105 we need to set an invalid port, while
* on SJA1110 which support multiple cascaded ports, this field is a
* bitmask so it must be left zero.
*/
if (!priv->info->multiple_cascade_ports)
general_params->casc_port = ds->num_ports;
for (port = 0; port < ds->num_ports; port++) {
bool is_upstream = dsa_is_upstream_port(ds, port);
bool is_dsa_link = dsa_is_dsa_port(ds, port);
/* Upstream ports can be dedicated CPU ports or
* upstream-facing DSA links
*/
if (is_upstream) {
if (general_params->host_port == ds->num_ports) {
general_params->host_port = port;
} else {
dev_err(ds->dev,
"Port %llu is already a host port, configuring %d as one too is not supported\n",
general_params->host_port, port);
return -EINVAL;
}
}
/* Cascade ports are downstream-facing DSA links */
if (is_dsa_link && !is_upstream) {
if (priv->info->multiple_cascade_ports) {
general_params->casc_port |= BIT(port);
} else if (general_params->casc_port == ds->num_ports) {
general_params->casc_port = port;
} else {
dev_err(ds->dev,
"Port %llu is already a cascade port, configuring %d as one too is not supported\n",
general_params->casc_port, port);
return -EINVAL;
}
}
}
if (general_params->host_port == ds->num_ports) {
dev_err(ds->dev, "No host port configured\n");
return -EINVAL;
}
return 0;
}
static int sja1105_init_general_params(struct sja1105_private *priv)
{
struct sja1105_general_params_entry default_general_params = {
......@@ -706,12 +841,6 @@ static int sja1105_init_general_params(struct sja1105_private *priv)
.mac_flt0 = SJA1105_LINKLOCAL_FILTER_B_MASK,
.incl_srcpt0 = false,
.send_meta0 = false,
/* The destination for traffic matching mac_fltres1 and
* mac_fltres0 on all ports except host_port. Such traffic
* receieved on host_port itself would be dropped, except
* by installing a temporary 'management route'
*/
.host_port = priv->ds->num_ports,
/* Default to an invalid value */
.mirr_port = priv->ds->num_ports,
/* No TTEthernet */
......@@ -731,16 +860,12 @@ static int sja1105_init_general_params(struct sja1105_private *priv)
.header_type = ETH_P_SJA1110,
};
struct sja1105_general_params_entry *general_params;
struct dsa_switch *ds = priv->ds;
struct sja1105_table *table;
int port;
int rc;
for (port = 0; port < ds->num_ports; port++) {
if (dsa_is_cpu_port(ds, port)) {
default_general_params.host_port = port;
break;
}
}
rc = sja1105_init_topology(priv, &default_general_params);
if (rc)
return rc;
table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS];
......@@ -763,19 +888,6 @@ static int sja1105_init_general_params(struct sja1105_private *priv)
sja1110_select_tdmaconfigidx(priv);
/* Link-local traffic received on casc_port will be forwarded
* to host_port without embedding the source port and device ID
* info in the destination MAC address, and no RX timestamps will be
* taken either (presumably because it is a cascaded port and a
* downstream SJA switch already did that).
* To disable the feature, we need to do different things depending on
* switch generation. On SJA1105 we need to set an invalid port, while
* on SJA1110 which support multiple cascaded ports, this field is a
* bitmask so it must be left zero.
*/
if (!priv->info->multiple_cascade_ports)
general_params->casc_port = ds->num_ports;
return 0;
}
......@@ -903,7 +1015,7 @@ static int sja1105_init_l2_policing(struct sja1105_private *priv)
for (port = 0; port < ds->num_ports; port++) {
int mtu = VLAN_ETH_FRAME_LEN + ETH_FCS_LEN;
if (dsa_is_cpu_port(priv->ds, port))
if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port))
mtu += VLAN_HLEN;
policing[port].smax = 65535; /* Burst size in bytes */
......@@ -2231,8 +2343,8 @@ static int sja1105_bridge_vlan_add(struct dsa_switch *ds, int port,
return -EBUSY;
}
/* Always install bridge VLANs as egress-tagged on the CPU port. */
if (dsa_is_cpu_port(ds, port))
/* Always install bridge VLANs as egress-tagged on CPU and DSA ports */
if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port))
flags = 0;
rc = sja1105_vlan_add(priv, port, vlan->vid, flags);
......@@ -2401,6 +2513,7 @@ static int sja1105_setup(struct dsa_switch *ds)
ds->num_tx_queues = SJA1105_NUM_TC;
ds->mtu_enforcement_ingress = true;
ds->assisted_learning_on_cpu_port = true;
rc = sja1105_devlink_setup(ds);
if (rc < 0)
......@@ -2585,7 +2698,7 @@ static int sja1105_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
new_mtu += VLAN_ETH_HLEN + ETH_FCS_LEN;
if (dsa_is_cpu_port(ds, port))
if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port))
new_mtu += VLAN_HLEN;
policing = priv->static_config.tables[BLK_IDX_L2_POLICING].entries;
......
......@@ -311,6 +311,9 @@ static struct dsa_port *dsa_tree_find_first_cpu(struct dsa_switch_tree *dst)
return NULL;
}
/* Assign the default CPU port (the first one in the tree) to all ports of the
* fabric which don't already have one as part of their own switch.
*/
static int dsa_tree_setup_default_cpu(struct dsa_switch_tree *dst)
{
struct dsa_port *cpu_dp, *dp;
......@@ -321,15 +324,48 @@ static int dsa_tree_setup_default_cpu(struct dsa_switch_tree *dst)
return -EINVAL;
}
/* Assign the default CPU port to all ports of the fabric */
list_for_each_entry(dp, &dst->ports, list)
list_for_each_entry(dp, &dst->ports, list) {
if (dp->cpu_dp)
continue;
if (dsa_port_is_user(dp) || dsa_port_is_dsa(dp))
dp->cpu_dp = cpu_dp;
}
return 0;
}
static void dsa_tree_teardown_default_cpu(struct dsa_switch_tree *dst)
/* Perform initial assignment of CPU ports to user ports and DSA links in the
* fabric, giving preference to CPU ports local to each switch. Default to
* using the first CPU port in the switch tree if the port does not have a CPU
* port local to this switch.
*/
static int dsa_tree_setup_cpu_ports(struct dsa_switch_tree *dst)
{
struct dsa_port *cpu_dp, *dp;
list_for_each_entry(cpu_dp, &dst->ports, list) {
if (!dsa_port_is_cpu(cpu_dp))
continue;
list_for_each_entry(dp, &dst->ports, list) {
/* Prefer a local CPU port */
if (dp->ds != cpu_dp->ds)
continue;
/* Prefer the first local CPU port found */
if (dp->cpu_dp)
continue;
if (dsa_port_is_user(dp) || dsa_port_is_dsa(dp))
dp->cpu_dp = cpu_dp;
}
}
return dsa_tree_setup_default_cpu(dst);
}
static void dsa_tree_teardown_cpu_ports(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
......@@ -921,13 +957,13 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)
if (!complete)
return 0;
err = dsa_tree_setup_default_cpu(dst);
err = dsa_tree_setup_cpu_ports(dst);
if (err)
return err;
err = dsa_tree_setup_switches(dst);
if (err)
goto teardown_default_cpu;
goto teardown_cpu_ports;
err = dsa_tree_setup_master(dst);
if (err)
......@@ -947,8 +983,8 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)
dsa_tree_teardown_master(dst);
teardown_switches:
dsa_tree_teardown_switches(dst);
teardown_default_cpu:
dsa_tree_teardown_default_cpu(dst);
teardown_cpu_ports:
dsa_tree_teardown_cpu_ports(dst);
return err;
}
......@@ -966,7 +1002,7 @@ static void dsa_tree_teardown(struct dsa_switch_tree *dst)
dsa_tree_teardown_switches(dst);
dsa_tree_teardown_default_cpu(dst);
dsa_tree_teardown_cpu_ports(dst);
list_for_each_entry_safe(dl, next, &dst->rtable, list) {
list_del(&dl->list);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment