aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2019-05-05 21:52:42 -0700
committerDavid S. Miller <davem@davemloft.net>2019-05-05 21:52:42 -0700
commit0e5ef5a22ab77f881803d11b855b5e2dabd38278 (patch)
treea66289f11be8b2681942da737766ca7944a092c2 /drivers/net
parentnet: dsa: mv88e6xxx: refine SMI support (diff)
parentDocumentation: net: dsa: sja1105: Add info about supported traffic modes (diff)
downloadlinux-dev-0e5ef5a22ab77f881803d11b855b5e2dabd38278.tar.xz
linux-dev-0e5ef5a22ab77f881803d11b855b5e2dabd38278.zip
Merge branch 'Traffic-support-for-SJA1105-DSA-driver'
Vladimir Oltean says: ==================== Traffic support for SJA1105 DSA driver This patch set is a continuation of the "NXP SJA1105 DSA driver" v3 series, which was split in multiple pieces for easier review. Supporting a fully-featured (traffic-capable) driver for this switch requires some rework in DSA and also leaves behind a more generic infrastructure for other dumb switches that rely on 802.1Q pseudo-switch tagging for port separation. Among the DSA changes required are: * Generic xmit and rcv functions for pushing/popping 802.1Q tags on skb's. These are modeled as a tagging protocol of its own but which must be customized by drivers to fit their own hardware possibilities. * Permitting the .setup callback to invoke switchdev operations that will loop back into the driver through the switchdev notifier chain. The SJA1105 driver then proceeds to extend this 8021q switch tagging protocol while adding its own (tag_sja1105). This is done because the driver actually implements a "dual tagger": * For normal traffic it uses 802.1Q tags * For management (multicast DMAC) frames the switch has native support for recognizing and annotating these with source port and switch id information. Because this is a "dual tagger", decoding of management frames should still function when regular traffic can't (under a bridge with VLAN filtering). There was intervention in the DSA receive hotpath, where a new filtering function called from eth_type_trans() is needed. This is useful in the general sense for switches that might actually have some limited means of source port decoding, such as only for management traffic, but not for everything. In order for the 802.1Q tagging protocol (which cannot be enabled under all conditions, unlike the management traffic decoding) to not be an all-or-nothing choice, the filtering function matches everything that can be decoded, and everything else is left to pass to the master netdevice. Lastly, DSA core support was added for drivers to request skb deferral. SJA1105 needs this for SPI intervention during transmission of link-local traffic. This is not done from within the tagger. Some patches were carried over unchanged from the previous patchset (01/09). Others were slightly reworked while adapting to the recent changes in "Make DSA tag drivers kernel modules" (02/09). The introduction of some structures (DSA_SKB_CB, dp->priv) may seem a little premature at this point and the new structures under-utilized. The reason is that traffic support has been rewritten with PTP timestamping in mind, and then I removed the timestamping code from the current submission (1. it is a different topic, 2. it does not work very well yet). On demand I can provide the timestamping patchset as a RFC though. "NXP SJA1105 DSA driver" v3 patchset can be found at: https://lkml.org/lkml/2019/4/12/978 v1 patchset can be found at: https://lkml.org/lkml/2019/5/3/877 Changes in v2: * Made the deferred xmit workqueue also be drained on the netdev suspend callback, not just on ndo_stop. * Added clarification about how other netdevices may be bridged with the switch ports. v2 patchset can be found at: https://www.spinics.net/lists/netdev/msg568818.html Changes in v3: * Exported the dsa_port_vid_add and dsa_port_vid_del symbols to fix an error reported by the kbuild test robot * Fixed the following checkpatch warnings in 05/10: Macro argument reuse 'skb' - possible side-effects? Macro argument reuse 'clone' - possible side-effects? * Added a commit description to the documentation patch (10/10) ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/dsa/sja1105/Kconfig1
-rw-r--r--drivers/net/dsa/sja1105/sja1105.h6
-rw-r--r--drivers/net/dsa/sja1105/sja1105_main.c246
3 files changed, 238 insertions, 15 deletions
diff --git a/drivers/net/dsa/sja1105/Kconfig b/drivers/net/dsa/sja1105/Kconfig
index 038685bb9d57..757751a89819 100644
--- a/drivers/net/dsa/sja1105/Kconfig
+++ b/drivers/net/dsa/sja1105/Kconfig
@@ -1,6 +1,7 @@
config NET_DSA_SJA1105
tristate "NXP SJA1105 Ethernet switch family support"
depends on NET_DSA && SPI
+ select NET_DSA_TAG_SJA1105
select PACKING
select CRC32
help
diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
index b0a155b57e17..b043bfc408f2 100644
--- a/drivers/net/dsa/sja1105/sja1105.h
+++ b/drivers/net/dsa/sja1105/sja1105.h
@@ -7,6 +7,7 @@
#include <linux/dsa/sja1105.h>
#include <net/dsa.h>
+#include <linux/mutex.h>
#include "sja1105_static_config.h"
#define SJA1105_NUM_PORTS 5
@@ -65,6 +66,11 @@ struct sja1105_private {
struct gpio_desc *reset_gpio;
struct spi_device *spidev;
struct dsa_switch *ds;
+ struct sja1105_port ports[SJA1105_NUM_PORTS];
+ /* Serializes transmission of management frames so that
+ * the switch doesn't confuse them with one another.
+ */
+ struct mutex mgmt_lock;
};
#include "sja1105_dynamic_config.h"
diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index 74f8ff9e17e0..50ff625c85d6 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -20,6 +20,7 @@
#include <linux/netdevice.h>
#include <linux/if_bridge.h>
#include <linux/if_ether.h>
+#include <linux/dsa/8021q.h>
#include "sja1105.h"
static void sja1105_hw_reset(struct gpio_desc *gpio, unsigned int pulse_len,
@@ -91,8 +92,10 @@ static int sja1105_init_mac_settings(struct sja1105_private *priv)
.drpuntag = false,
/* Don't retag 802.1p (VID 0) traffic with the pvid */
.retag = false,
- /* Enable learning and I/O on user ports by default. */
- .dyn_learn = true,
+ /* Disable learning and I/O on user ports by default -
+ * STP will enable it.
+ */
+ .dyn_learn = false,
.egress = false,
.ingress = false,
};
@@ -118,8 +121,17 @@ static int sja1105_init_mac_settings(struct sja1105_private *priv)
mac = table->entries;
- for (i = 0; i < SJA1105_NUM_PORTS; i++)
+ for (i = 0; i < SJA1105_NUM_PORTS; i++) {
mac[i] = default_mac;
+ if (i == dsa_upstream_port(priv->ds, i)) {
+ /* STP doesn't get called for CPU port, so we need to
+ * set the I/O parameters statically.
+ */
+ mac[i].dyn_learn = true;
+ mac[i].ingress = true;
+ mac[i].egress = true;
+ }
+ }
return 0;
}
@@ -406,11 +418,14 @@ static int sja1105_init_general_params(struct sja1105_private *priv)
.tpid2 = ETH_P_SJA1105,
};
struct sja1105_table *table;
- int i;
+ int i, k = 0;
- for (i = 0; i < SJA1105_NUM_PORTS; i++)
+ for (i = 0; i < SJA1105_NUM_PORTS; i++) {
if (dsa_is_dsa_port(priv->ds, i))
default_general_params.casc_port = i;
+ else if (dsa_is_user_port(priv->ds, i))
+ priv->ports[i].mgmt_slot = k++;
+ }
table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS];
@@ -651,12 +666,14 @@ static sja1105_speed_t sja1105_get_speed_cfg(unsigned int speed_mbps)
* for a specific port.
*
* @speed_mbps: If 0, leave the speed unchanged, else adapt MAC to PHY speed.
- * @enabled: Manage Rx and Tx settings for this port. Overrides the static
- * configuration settings.
+ * @enabled: Manage Rx and Tx settings for this port. If false, overrides the
+ * settings from the STP state, but not persistently (does not
+ * overwrite the static MAC info for this port).
*/
static int sja1105_adjust_port_config(struct sja1105_private *priv, int port,
int speed_mbps, bool enabled)
{
+ struct sja1105_mac_config_entry dyn_mac;
struct sja1105_xmii_params_entry *mii;
struct sja1105_mac_config_entry *mac;
struct device *dev = priv->ds->dev;
@@ -689,12 +706,13 @@ static int sja1105_adjust_port_config(struct sja1105_private *priv, int port,
* the code common, we'll use the static configuration tables as a
* reasonable approximation for both E/T and P/Q/R/S.
*/
- mac[port].ingress = enabled;
- mac[port].egress = enabled;
+ dyn_mac = mac[port];
+ dyn_mac.ingress = enabled && mac[port].ingress;
+ dyn_mac.egress = enabled && mac[port].egress;
/* Write to the dynamic reconfiguration tables */
rc = sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG,
- port, &mac[port], true);
+ port, &dyn_mac, true);
if (rc < 0) {
dev_err(dev, "Failed to write MAC config: %d\n", rc);
return rc;
@@ -982,6 +1000,50 @@ static int sja1105_bridge_member(struct dsa_switch *ds, int port,
port, &l2_fwd[port], true);
}
+static void sja1105_bridge_stp_state_set(struct dsa_switch *ds, int port,
+ u8 state)
+{
+ struct sja1105_private *priv = ds->priv;
+ struct sja1105_mac_config_entry *mac;
+
+ mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries;
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ case BR_STATE_BLOCKING:
+ /* From UM10944 description of DRPDTAG (why put this there?):
+ * "Management traffic flows to the port regardless of the state
+ * of the INGRESS flag". So BPDUs are still be allowed to pass.
+ * At the moment no difference between DISABLED and BLOCKING.
+ */
+ mac[port].ingress = false;
+ mac[port].egress = false;
+ mac[port].dyn_learn = false;
+ break;
+ case BR_STATE_LISTENING:
+ mac[port].ingress = true;
+ mac[port].egress = false;
+ mac[port].dyn_learn = false;
+ break;
+ case BR_STATE_LEARNING:
+ mac[port].ingress = true;
+ mac[port].egress = false;
+ mac[port].dyn_learn = true;
+ break;
+ case BR_STATE_FORWARDING:
+ mac[port].ingress = true;
+ mac[port].egress = true;
+ mac[port].dyn_learn = true;
+ break;
+ default:
+ dev_err(ds->dev, "invalid STP state: %d\n", state);
+ return;
+ }
+
+ sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG, port,
+ &mac[port], true);
+}
+
static int sja1105_bridge_join(struct dsa_switch *ds, int port,
struct net_device *br)
{
@@ -994,6 +1056,23 @@ static void sja1105_bridge_leave(struct dsa_switch *ds, int port,
sja1105_bridge_member(ds, port, br, false);
}
+static u8 sja1105_stp_state_get(struct sja1105_private *priv, int port)
+{
+ struct sja1105_mac_config_entry *mac;
+
+ mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries;
+
+ if (!mac[port].ingress && !mac[port].egress && !mac[port].dyn_learn)
+ return BR_STATE_BLOCKING;
+ if (mac[port].ingress && !mac[port].egress && !mac[port].dyn_learn)
+ return BR_STATE_LISTENING;
+ if (mac[port].ingress && !mac[port].egress && mac[port].dyn_learn)
+ return BR_STATE_LEARNING;
+ if (mac[port].ingress && mac[port].egress && mac[port].dyn_learn)
+ return BR_STATE_FORWARDING;
+ return -EINVAL;
+}
+
/* For situations where we need to change a setting at runtime that is only
* available through the static configuration, resetting the switch in order
* to upload the new static config is unavoidable. Back up the settings we
@@ -1004,16 +1083,27 @@ static int sja1105_static_config_reload(struct sja1105_private *priv)
{
struct sja1105_mac_config_entry *mac;
int speed_mbps[SJA1105_NUM_PORTS];
+ u8 stp_state[SJA1105_NUM_PORTS];
int rc, i;
mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries;
/* Back up settings changed by sja1105_adjust_port_config and
- * and restore their defaults.
+ * sja1105_bridge_stp_state_set and restore their defaults.
*/
for (i = 0; i < SJA1105_NUM_PORTS; i++) {
speed_mbps[i] = sja1105_speed[mac[i].speed];
mac[i].speed = SJA1105_SPEED_AUTO;
+ if (i == dsa_upstream_port(priv->ds, i)) {
+ mac[i].ingress = true;
+ mac[i].egress = true;
+ mac[i].dyn_learn = true;
+ } else {
+ stp_state[i] = sja1105_stp_state_get(priv, i);
+ mac[i].ingress = false;
+ mac[i].egress = false;
+ mac[i].dyn_learn = false;
+ }
}
/* Reset switch and send updated static configuration */
@@ -1032,6 +1122,9 @@ static int sja1105_static_config_reload(struct sja1105_private *priv)
for (i = 0; i < SJA1105_NUM_PORTS; i++) {
bool enabled = (speed_mbps[i] != 0);
+ if (i != dsa_upstream_port(priv->ds, i))
+ sja1105_bridge_stp_state_set(priv->ds, i, stp_state[i]);
+
rc = sja1105_adjust_port_config(priv, i, speed_mbps[i],
enabled);
if (rc < 0)
@@ -1146,10 +1239,27 @@ static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid,
return 0;
}
+static int sja1105_setup_8021q_tagging(struct dsa_switch *ds, bool enabled)
+{
+ int rc, i;
+
+ for (i = 0; i < SJA1105_NUM_PORTS; i++) {
+ rc = dsa_port_setup_8021q_tagging(ds, i, enabled);
+ if (rc < 0) {
+ dev_err(ds->dev, "Failed to setup VLAN tagging for port %d: %d\n",
+ i, rc);
+ return rc;
+ }
+ }
+ dev_info(ds->dev, "%s switch tagging\n",
+ enabled ? "Enabled" : "Disabled");
+ return 0;
+}
+
static enum dsa_tag_protocol
sja1105_get_tag_protocol(struct dsa_switch *ds, int port)
{
- return DSA_TAG_PROTO_NONE;
+ return DSA_TAG_PROTO_SJA1105;
}
/* This callback needs to be present */
@@ -1173,7 +1283,11 @@ static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled)
if (rc)
dev_err(ds->dev, "Failed to change VLAN Ethertype\n");
- return rc;
+ /* Switch port identification based on 802.1Q is only passable
+ * if we are not under a vlan_filtering bridge. So make sure
+ * the two configurations are mutually exclusive.
+ */
+ return sja1105_setup_8021q_tagging(ds, !enabled);
}
static void sja1105_vlan_add(struct dsa_switch *ds, int port,
@@ -1276,7 +1390,98 @@ static int sja1105_setup(struct dsa_switch *ds)
*/
ds->vlan_filtering_is_global = true;
- return 0;
+ /* The DSA/switchdev model brings up switch ports in standalone mode by
+ * default, and that means vlan_filtering is 0 since they're not under
+ * a bridge, so it's safe to set up switch tagging at this time.
+ */
+ return sja1105_setup_8021q_tagging(ds, true);
+}
+
+static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot,
+ struct sk_buff *skb)
+{
+ struct sja1105_mgmt_entry mgmt_route = {0};
+ struct sja1105_private *priv = ds->priv;
+ struct ethhdr *hdr;
+ int timeout = 10;
+ int rc;
+
+ hdr = eth_hdr(skb);
+
+ mgmt_route.macaddr = ether_addr_to_u64(hdr->h_dest);
+ mgmt_route.destports = BIT(port);
+ mgmt_route.enfport = 1;
+
+ rc = sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE,
+ slot, &mgmt_route, true);
+ if (rc < 0) {
+ kfree_skb(skb);
+ return rc;
+ }
+
+ /* Transfer skb to the host port. */
+ dsa_enqueue_skb(skb, ds->ports[port].slave);
+
+ /* Wait until the switch has processed the frame */
+ do {
+ rc = sja1105_dynamic_config_read(priv, BLK_IDX_MGMT_ROUTE,
+ slot, &mgmt_route);
+ if (rc < 0) {
+ dev_err_ratelimited(priv->ds->dev,
+ "failed to poll for mgmt route\n");
+ continue;
+ }
+
+ /* UM10944: The ENFPORT flag of the respective entry is
+ * cleared when a match is found. The host can use this
+ * flag as an acknowledgment.
+ */
+ cpu_relax();
+ } while (mgmt_route.enfport && --timeout);
+
+ if (!timeout) {
+ /* Clean up the management route so that a follow-up
+ * frame may not match on it by mistake.
+ */
+ sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE,
+ slot, &mgmt_route, false);
+ dev_err_ratelimited(priv->ds->dev, "xmit timed out\n");
+ }
+
+ return NETDEV_TX_OK;
+}
+
+/* Deferred work is unfortunately necessary because setting up the management
+ * route cannot be done from atomit context (SPI transfer takes a sleepable
+ * lock on the bus)
+ */
+static netdev_tx_t sja1105_port_deferred_xmit(struct dsa_switch *ds, int port,
+ struct sk_buff *skb)
+{
+ struct sja1105_private *priv = ds->priv;
+ struct sja1105_port *sp = &priv->ports[port];
+ int slot = sp->mgmt_slot;
+
+ /* The tragic fact about the switch having 4x2 slots for installing
+ * management routes is that all of them except one are actually
+ * useless.
+ * If 2 slots are simultaneously configured for two BPDUs sent to the
+ * same (multicast) DMAC but on different egress ports, the switch
+ * would confuse them and redirect first frame it receives on the CPU
+ * port towards the port configured on the numerically first slot
+ * (therefore wrong port), then second received frame on second slot
+ * (also wrong port).
+ * So for all practical purposes, there needs to be a lock that
+ * prevents that from happening. The slot used here is utterly useless
+ * (could have simply been 0 just as fine), but we are doing it
+ * nonetheless, in case a smarter idea ever comes up in the future.
+ */
+ mutex_lock(&priv->mgmt_lock);
+
+ sja1105_mgmt_xmit(ds, port, slot, skb);
+
+ mutex_unlock(&priv->mgmt_lock);
+ return NETDEV_TX_OK;
}
/* The MAXAGE setting belongs to the L2 Forwarding Parameters table,
@@ -1317,6 +1522,7 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
.port_fdb_del = sja1105_fdb_del,
.port_bridge_join = sja1105_bridge_join,
.port_bridge_leave = sja1105_bridge_leave,
+ .port_stp_state_set = sja1105_bridge_stp_state_set,
.port_vlan_prepare = sja1105_vlan_prepare,
.port_vlan_filtering = sja1105_vlan_filtering,
.port_vlan_add = sja1105_vlan_add,
@@ -1324,6 +1530,7 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
.port_mdb_prepare = sja1105_mdb_prepare,
.port_mdb_add = sja1105_mdb_add,
.port_mdb_del = sja1105_mdb_del,
+ .port_deferred_xmit = sja1105_port_deferred_xmit,
};
static int sja1105_check_device_id(struct sja1105_private *priv)
@@ -1367,7 +1574,7 @@ static int sja1105_probe(struct spi_device *spi)
struct device *dev = &spi->dev;
struct sja1105_private *priv;
struct dsa_switch *ds;
- int rc;
+ int rc, i;
if (!dev->of_node) {
dev_err(dev, "No DTS bindings for SJA1105 driver\n");
@@ -1418,6 +1625,15 @@ static int sja1105_probe(struct spi_device *spi)
ds->priv = priv;
priv->ds = ds;
+ /* Connections between dsa_port and sja1105_port */
+ for (i = 0; i < SJA1105_NUM_PORTS; i++) {
+ struct sja1105_port *sp = &priv->ports[i];
+
+ ds->ports[i].priv = sp;
+ sp->dp = &ds->ports[i];
+ }
+ mutex_init(&priv->mgmt_lock);
+
return dsa_register_switch(priv->ds);
}