aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/net/dsa/ocelot/felix.c
diff options
context:
space:
mode:
authorVladimir Oltean <vladimir.oltean@nxp.com>2022-05-11 12:50:20 +0300
committerJakub Kicinski <kuba@kernel.org>2022-05-12 16:38:55 -0700
commit7a29d220f4c0745a6d435dbd53c659fbde4998b6 (patch)
tree68c67256c9709d5468357c86c3e232e145167b23 /drivers/net/dsa/ocelot/felix.c
parentnet: dsa: felix: dynamically determine tag_8021q CPU port for traps (diff)
downloadwireguard-linux-7a29d220f4c0745a6d435dbd53c659fbde4998b6.tar.xz
wireguard-linux-7a29d220f4c0745a6d435dbd53c659fbde4998b6.zip
net: dsa: felix: reimplement tagging protocol change with function pointers
The error handling for the current tagging protocol change procedure is a bit brittle (we dismantle the previous tagging protocol entirely before setting up the new one). By identifying which parts of a tagging protocol are unique to itself and which parts are shared with the other, we can implement a protocol change procedure where error handling is a bit more robust, because we start setting up the new protocol first, and tear down the old one only after the setup of the specific and shared parts succeeded. The protocol change is a bit too open-coded too, in the area of migrating host flood settings and MDBs. By identifying what differs between tagging protocols (the forwarding masks for host flooding) we can implement a more straightforward migration procedure which is handled in the shared portion of the protocol change, rather than individually by each protocol. Therefore, a more structured approach calls for the introduction of a structure of function pointers per tagging protocol. This covers setup, teardown and the host forwarding mask. In the future it will also cover how to prepare for a new DSA master. The initial tagging protocol setup (at driver probe time) and the final teardown (at driver removal time) are also adapted to call into the structured methods of the specific protocol in current use. This is especially relevant for teardown, where we previously called felix_del_tag_protocol() only for the first CPU port. But by not specifying which CPU port this is for, we gain more flexibility to support multiple CPU ports in the future. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'drivers/net/dsa/ocelot/felix.c')
-rw-r--r--drivers/net/dsa/ocelot/felix.c399
1 files changed, 202 insertions, 197 deletions
diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c
index e76a5d434626..beac90bc642c 100644
--- a/drivers/net/dsa/ocelot/felix.c
+++ b/drivers/net/dsa/ocelot/felix.c
@@ -42,43 +42,6 @@ static struct net_device *felix_classify_db(struct dsa_db db)
}
}
-static void felix_migrate_pgid_bit(struct dsa_switch *ds, int from, int to,
- int pgid)
-{
- struct ocelot *ocelot = ds->priv;
- bool on;
- u32 val;
-
- val = ocelot_read_rix(ocelot, ANA_PGID_PGID, pgid);
- on = !!(val & BIT(from));
- val &= ~BIT(from);
- if (on)
- val |= BIT(to);
- else
- val &= ~BIT(to);
-
- ocelot_write_rix(ocelot, val, ANA_PGID_PGID, pgid);
-}
-
-static void felix_migrate_flood_to_npi_port(struct dsa_switch *ds, int port)
-{
- struct ocelot *ocelot = ds->priv;
-
- felix_migrate_pgid_bit(ds, port, ocelot->num_phys_ports, PGID_UC);
- felix_migrate_pgid_bit(ds, port, ocelot->num_phys_ports, PGID_MC);
- felix_migrate_pgid_bit(ds, port, ocelot->num_phys_ports, PGID_BC);
-}
-
-static void
-felix_migrate_flood_to_tag_8021q_port(struct dsa_switch *ds, int port)
-{
- struct ocelot *ocelot = ds->priv;
-
- felix_migrate_pgid_bit(ds, ocelot->num_phys_ports, port, PGID_UC);
- felix_migrate_pgid_bit(ds, ocelot->num_phys_ports, port, PGID_MC);
- felix_migrate_pgid_bit(ds, ocelot->num_phys_ports, port, PGID_BC);
-}
-
/* Set up VCAP ES0 rules for pushing a tag_8021q VLAN towards the CPU such that
* the tagger can perform RX source port identification.
*/
@@ -392,13 +355,107 @@ static int felix_update_trapping_destinations(struct dsa_switch *ds,
return 0;
}
-static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu)
+/* The CPU port module is connected to the Node Processor Interface (NPI). This
+ * is the mode through which frames can be injected from and extracted to an
+ * external CPU, over Ethernet. In NXP SoCs, the "external CPU" is the ARM CPU
+ * running Linux, and this forms a DSA setup together with the enetc or fman
+ * DSA master.
+ */
+static void felix_npi_port_init(struct ocelot *ocelot, int port)
+{
+ ocelot->npi = port;
+
+ ocelot_write(ocelot, QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK_M |
+ QSYS_EXT_CPU_CFG_EXT_CPU_PORT(port),
+ QSYS_EXT_CPU_CFG);
+
+ /* NPI port Injection/Extraction configuration */
+ ocelot_fields_write(ocelot, port, SYS_PORT_MODE_INCL_XTR_HDR,
+ ocelot->npi_xtr_prefix);
+ ocelot_fields_write(ocelot, port, SYS_PORT_MODE_INCL_INJ_HDR,
+ ocelot->npi_inj_prefix);
+
+ /* Disable transmission of pause frames */
+ ocelot_fields_write(ocelot, port, SYS_PAUSE_CFG_PAUSE_ENA, 0);
+}
+
+static void felix_npi_port_deinit(struct ocelot *ocelot, int port)
+{
+ /* Restore hardware defaults */
+ int unused_port = ocelot->num_phys_ports + 2;
+
+ ocelot->npi = -1;
+
+ ocelot_write(ocelot, QSYS_EXT_CPU_CFG_EXT_CPU_PORT(unused_port),
+ QSYS_EXT_CPU_CFG);
+
+ ocelot_fields_write(ocelot, port, SYS_PORT_MODE_INCL_XTR_HDR,
+ OCELOT_TAG_PREFIX_DISABLED);
+ ocelot_fields_write(ocelot, port, SYS_PORT_MODE_INCL_INJ_HDR,
+ OCELOT_TAG_PREFIX_DISABLED);
+
+ /* Enable transmission of pause frames */
+ ocelot_fields_write(ocelot, port, SYS_PAUSE_CFG_PAUSE_ENA, 1);
+}
+
+static int felix_tag_npi_setup(struct dsa_switch *ds)
+{
+ struct dsa_port *dp, *first_cpu_dp = NULL;
+ struct ocelot *ocelot = ds->priv;
+
+ dsa_switch_for_each_user_port(dp, ds) {
+ if (first_cpu_dp && dp->cpu_dp != first_cpu_dp) {
+ dev_err(ds->dev, "Multiple NPI ports not supported\n");
+ return -EINVAL;
+ }
+
+ first_cpu_dp = dp->cpu_dp;
+ }
+
+ if (!first_cpu_dp)
+ return -EINVAL;
+
+ felix_npi_port_init(ocelot, first_cpu_dp->index);
+
+ return 0;
+}
+
+static void felix_tag_npi_teardown(struct dsa_switch *ds)
{
struct ocelot *ocelot = ds->priv;
- struct dsa_port *dp;
+
+ felix_npi_port_deinit(ocelot, ocelot->npi);
+}
+
+static unsigned long felix_tag_npi_get_host_fwd_mask(struct dsa_switch *ds)
+{
+ struct ocelot *ocelot = ds->priv;
+
+ return BIT(ocelot->num_phys_ports);
+}
+
+static const struct felix_tag_proto_ops felix_tag_npi_proto_ops = {
+ .setup = felix_tag_npi_setup,
+ .teardown = felix_tag_npi_teardown,
+ .get_host_fwd_mask = felix_tag_npi_get_host_fwd_mask,
+};
+
+static int felix_tag_8021q_setup(struct dsa_switch *ds)
+{
+ struct ocelot *ocelot = ds->priv;
+ struct dsa_port *dp, *cpu_dp;
int err;
- felix_8021q_cpu_port_init(ocelot, cpu);
+ err = dsa_tag_8021q_register(ds, htons(ETH_P_8021AD));
+ if (err)
+ return err;
+
+ dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+ felix_8021q_cpu_port_init(ocelot, cpu_dp->index);
+
+ /* TODO we could support multiple CPU ports in tag_8021q mode */
+ break;
+ }
dsa_switch_for_each_available_port(dp, ds) {
/* This overwrites ocelot_init():
@@ -416,21 +473,6 @@ static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu)
ANA_PORT_CPU_FWD_BPDU_CFG, dp->index);
}
- err = dsa_tag_8021q_register(ds, htons(ETH_P_8021AD));
- if (err)
- return err;
-
- err = ocelot_migrate_mdbs(ocelot, BIT(ocelot->num_phys_ports),
- BIT(cpu));
- if (err)
- goto out_tag_8021q_unregister;
-
- felix_migrate_flood_to_tag_8021q_port(ds, cpu);
-
- err = felix_update_trapping_destinations(ds, true);
- if (err)
- goto out_migrate_flood;
-
/* The ownership of the CPU port module's queues might have just been
* transferred to the tag_8021q tagger from the NPI-based tagger.
* So there might still be all sorts of crap in the queues. On the
@@ -441,27 +483,12 @@ static int felix_setup_tag_8021q(struct dsa_switch *ds, int cpu)
ocelot_drain_cpu_queue(ocelot, 0);
return 0;
-
-out_migrate_flood:
- felix_migrate_flood_to_npi_port(ds, cpu);
- ocelot_migrate_mdbs(ocelot, BIT(cpu), BIT(ocelot->num_phys_ports));
-out_tag_8021q_unregister:
- dsa_tag_8021q_unregister(ds);
- return err;
}
-static void felix_teardown_tag_8021q(struct dsa_switch *ds, int cpu)
+static void felix_tag_8021q_teardown(struct dsa_switch *ds)
{
struct ocelot *ocelot = ds->priv;
- struct dsa_port *dp;
- int err;
-
- err = felix_update_trapping_destinations(ds, false);
- if (err)
- dev_err(ds->dev, "felix_teardown_mmio_filtering returned %d",
- err);
-
- dsa_tag_8021q_unregister(ds);
+ struct dsa_port *dp, *cpu_dp;
dsa_switch_for_each_available_port(dp, ds) {
/* Restore the logic from ocelot_init:
@@ -473,110 +500,99 @@ static void felix_teardown_tag_8021q(struct dsa_switch *ds, int cpu)
dp->index);
}
- felix_8021q_cpu_port_deinit(ocelot, cpu);
-}
-
-/* The CPU port module is connected to the Node Processor Interface (NPI). This
- * is the mode through which frames can be injected from and extracted to an
- * external CPU, over Ethernet. In NXP SoCs, the "external CPU" is the ARM CPU
- * running Linux, and this forms a DSA setup together with the enetc or fman
- * DSA master.
- */
-static void felix_npi_port_init(struct ocelot *ocelot, int port)
-{
- ocelot->npi = port;
-
- ocelot_write(ocelot, QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK_M |
- QSYS_EXT_CPU_CFG_EXT_CPU_PORT(port),
- QSYS_EXT_CPU_CFG);
+ dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+ felix_8021q_cpu_port_deinit(ocelot, cpu_dp->index);
- /* NPI port Injection/Extraction configuration */
- ocelot_fields_write(ocelot, port, SYS_PORT_MODE_INCL_XTR_HDR,
- ocelot->npi_xtr_prefix);
- ocelot_fields_write(ocelot, port, SYS_PORT_MODE_INCL_INJ_HDR,
- ocelot->npi_inj_prefix);
+ /* TODO we could support multiple CPU ports in tag_8021q mode */
+ break;
+ }
- /* Disable transmission of pause frames */
- ocelot_fields_write(ocelot, port, SYS_PAUSE_CFG_PAUSE_ENA, 0);
+ dsa_tag_8021q_unregister(ds);
}
-static void felix_npi_port_deinit(struct ocelot *ocelot, int port)
+static unsigned long felix_tag_8021q_get_host_fwd_mask(struct dsa_switch *ds)
{
- /* Restore hardware defaults */
- int unused_port = ocelot->num_phys_ports + 2;
+ return dsa_cpu_ports(ds);
+}
- ocelot->npi = -1;
+static const struct felix_tag_proto_ops felix_tag_8021q_proto_ops = {
+ .setup = felix_tag_8021q_setup,
+ .teardown = felix_tag_8021q_teardown,
+ .get_host_fwd_mask = felix_tag_8021q_get_host_fwd_mask,
+};
- ocelot_write(ocelot, QSYS_EXT_CPU_CFG_EXT_CPU_PORT(unused_port),
- QSYS_EXT_CPU_CFG);
+static void felix_set_host_flood(struct dsa_switch *ds, unsigned long mask,
+ bool uc, bool mc, bool bc)
+{
+ struct ocelot *ocelot = ds->priv;
+ unsigned long val;
- ocelot_fields_write(ocelot, port, SYS_PORT_MODE_INCL_XTR_HDR,
- OCELOT_TAG_PREFIX_DISABLED);
- ocelot_fields_write(ocelot, port, SYS_PORT_MODE_INCL_INJ_HDR,
- OCELOT_TAG_PREFIX_DISABLED);
+ val = uc ? mask : 0;
+ ocelot_rmw_rix(ocelot, val, mask, ANA_PGID_PGID, PGID_UC);
- /* Enable transmission of pause frames */
- ocelot_fields_write(ocelot, port, SYS_PAUSE_CFG_PAUSE_ENA, 1);
+ val = mc ? mask : 0;
+ ocelot_rmw_rix(ocelot, val, mask, ANA_PGID_PGID, PGID_MC);
+ ocelot_rmw_rix(ocelot, val, mask, ANA_PGID_PGID, PGID_MCIPV4);
+ ocelot_rmw_rix(ocelot, val, mask, ANA_PGID_PGID, PGID_MCIPV6);
}
-static int felix_setup_tag_npi(struct dsa_switch *ds, int cpu)
+static void
+felix_migrate_host_flood(struct dsa_switch *ds,
+ const struct felix_tag_proto_ops *proto_ops,
+ const struct felix_tag_proto_ops *old_proto_ops)
{
struct ocelot *ocelot = ds->priv;
- int err;
-
- err = ocelot_migrate_mdbs(ocelot, BIT(cpu),
- BIT(ocelot->num_phys_ports));
- if (err)
- return err;
-
- felix_migrate_flood_to_npi_port(ds, cpu);
+ struct felix *felix = ocelot_to_felix(ocelot);
+ unsigned long mask;
- felix_npi_port_init(ocelot, cpu);
+ if (old_proto_ops) {
+ mask = old_proto_ops->get_host_fwd_mask(ds);
+ felix_set_host_flood(ds, mask, false, false, false);
+ }
- return 0;
+ mask = proto_ops->get_host_fwd_mask(ds);
+ felix_set_host_flood(ds, mask, !!felix->host_flood_uc_mask,
+ !!felix->host_flood_mc_mask, true);
}
-static void felix_teardown_tag_npi(struct dsa_switch *ds, int cpu)
+static int felix_migrate_mdbs(struct dsa_switch *ds,
+ const struct felix_tag_proto_ops *proto_ops,
+ const struct felix_tag_proto_ops *old_proto_ops)
{
struct ocelot *ocelot = ds->priv;
+ unsigned long from, to;
+
+ if (!old_proto_ops)
+ return 0;
+
+ from = old_proto_ops->get_host_fwd_mask(ds);
+ to = proto_ops->get_host_fwd_mask(ds);
- felix_npi_port_deinit(ocelot, cpu);
+ return ocelot_migrate_mdbs(ocelot, from, to);
}
-static int felix_set_tag_protocol(struct dsa_switch *ds, int cpu,
- enum dsa_tag_protocol proto)
+/* Configure the shared hardware resources for a transition between
+ * @old_proto_ops and @proto_ops.
+ * Manual migration is needed because as far as DSA is concerned, no change of
+ * the CPU port is taking place here, just of the tagging protocol.
+ */
+static int
+felix_tag_proto_setup_shared(struct dsa_switch *ds,
+ const struct felix_tag_proto_ops *proto_ops,
+ const struct felix_tag_proto_ops *old_proto_ops)
{
+ bool using_tag_8021q = (proto_ops == &felix_tag_8021q_proto_ops);
int err;
- switch (proto) {
- case DSA_TAG_PROTO_SEVILLE:
- case DSA_TAG_PROTO_OCELOT:
- err = felix_setup_tag_npi(ds, cpu);
- break;
- case DSA_TAG_PROTO_OCELOT_8021Q:
- err = felix_setup_tag_8021q(ds, cpu);
- break;
- default:
- err = -EPROTONOSUPPORT;
- }
+ err = felix_migrate_mdbs(ds, proto_ops, old_proto_ops);
+ if (err)
+ return err;
- return err;
-}
+ felix_update_trapping_destinations(ds, using_tag_8021q);
-static void felix_del_tag_protocol(struct dsa_switch *ds, int cpu,
- enum dsa_tag_protocol proto)
-{
- switch (proto) {
- case DSA_TAG_PROTO_SEVILLE:
- case DSA_TAG_PROTO_OCELOT:
- felix_teardown_tag_npi(ds, cpu);
- break;
- case DSA_TAG_PROTO_OCELOT_8021Q:
- felix_teardown_tag_8021q(ds, cpu);
- break;
- default:
- break;
- }
+ felix_migrate_host_flood(ds, proto_ops, old_proto_ops);
+
+ return 0;
}
/* This always leaves the switch in a consistent state, because although the
@@ -586,33 +602,45 @@ static void felix_del_tag_protocol(struct dsa_switch *ds, int cpu,
static int felix_change_tag_protocol(struct dsa_switch *ds,
enum dsa_tag_protocol proto)
{
+ const struct felix_tag_proto_ops *old_proto_ops, *proto_ops;
struct ocelot *ocelot = ds->priv;
struct felix *felix = ocelot_to_felix(ocelot);
- enum dsa_tag_protocol old_proto = felix->tag_proto;
- struct dsa_port *cpu_dp;
int err;
- if (proto != DSA_TAG_PROTO_SEVILLE &&
- proto != DSA_TAG_PROTO_OCELOT &&
- proto != DSA_TAG_PROTO_OCELOT_8021Q)
+ switch (proto) {
+ case DSA_TAG_PROTO_SEVILLE:
+ case DSA_TAG_PROTO_OCELOT:
+ proto_ops = &felix_tag_npi_proto_ops;
+ break;
+ case DSA_TAG_PROTO_OCELOT_8021Q:
+ proto_ops = &felix_tag_8021q_proto_ops;
+ break;
+ default:
return -EPROTONOSUPPORT;
+ }
- dsa_switch_for_each_cpu_port(cpu_dp, ds) {
- felix_del_tag_protocol(ds, cpu_dp->index, old_proto);
+ old_proto_ops = felix->tag_proto_ops;
- err = felix_set_tag_protocol(ds, cpu_dp->index, proto);
- if (err) {
- felix_set_tag_protocol(ds, cpu_dp->index, old_proto);
- return err;
- }
+ err = proto_ops->setup(ds);
+ if (err)
+ goto setup_failed;
- /* Stop at first CPU port */
- break;
- }
+ err = felix_tag_proto_setup_shared(ds, proto_ops, old_proto_ops);
+ if (err)
+ goto setup_shared_failed;
+ if (old_proto_ops)
+ old_proto_ops->teardown(ds);
+
+ felix->tag_proto_ops = proto_ops;
felix->tag_proto = proto;
return 0;
+
+setup_shared_failed:
+ proto_ops->teardown(ds);
+setup_failed:
+ return err;
}
static enum dsa_tag_protocol felix_get_tag_protocol(struct dsa_switch *ds,
@@ -630,7 +658,7 @@ static void felix_port_set_host_flood(struct dsa_switch *ds, int port,
{
struct ocelot *ocelot = ds->priv;
struct felix *felix = ocelot_to_felix(ocelot);
- unsigned long mask, val;
+ unsigned long mask;
if (uc)
felix->host_flood_uc_mask |= BIT(port);
@@ -642,18 +670,9 @@ static void felix_port_set_host_flood(struct dsa_switch *ds, int port,
else
felix->host_flood_mc_mask &= ~BIT(port);
- if (felix->tag_proto == DSA_TAG_PROTO_OCELOT_8021Q)
- mask = dsa_cpu_ports(ds);
- else
- mask = BIT(ocelot->num_phys_ports);
-
- val = (felix->host_flood_uc_mask) ? mask : 0;
- ocelot_rmw_rix(ocelot, val, mask, ANA_PGID_PGID, PGID_UC);
-
- val = (felix->host_flood_mc_mask) ? mask : 0;
- ocelot_rmw_rix(ocelot, val, mask, ANA_PGID_PGID, PGID_MC);
- ocelot_rmw_rix(ocelot, val, mask, ANA_PGID_PGID, PGID_MCIPV4);
- ocelot_rmw_rix(ocelot, val, mask, ANA_PGID_PGID, PGID_MCIPV6);
+ mask = felix->tag_proto_ops->get_host_fwd_mask(ds);
+ felix_set_host_flood(ds, mask, !!felix->host_flood_uc_mask,
+ !!felix->host_flood_mc_mask, true);
}
static int felix_set_ageing_time(struct dsa_switch *ds,
@@ -1332,7 +1351,6 @@ static int felix_setup(struct dsa_switch *ds)
{
struct ocelot *ocelot = ds->priv;
struct felix *felix = ocelot_to_felix(ocelot);
- unsigned long cpu_flood;
struct dsa_port *dp;
int err;
@@ -1366,21 +1384,10 @@ static int felix_setup(struct dsa_switch *ds)
if (err)
goto out_deinit_ports;
- dsa_switch_for_each_cpu_port(dp, ds) {
- /* The initial tag protocol is NPI which always returns 0, so
- * there's no real point in checking for errors.
- */
- felix_set_tag_protocol(ds, dp->index, felix->tag_proto);
-
- /* Start off with flooding disabled towards the NPI port
- * (actually CPU port module).
- */
- cpu_flood = ANA_PGID_PGID_PGID(BIT(ocelot->num_phys_ports));
- ocelot_rmw_rix(ocelot, 0, cpu_flood, ANA_PGID_PGID, PGID_UC);
- ocelot_rmw_rix(ocelot, 0, cpu_flood, ANA_PGID_PGID, PGID_MC);
-
- break;
- }
+ /* The initial tag protocol is NPI which won't fail during initial
+ * setup, there's no real point in checking for errors.
+ */
+ felix_change_tag_protocol(ds, felix->tag_proto);
ds->mtu_enforcement_ingress = true;
ds->assisted_learning_on_cpu_port = true;
@@ -1409,10 +1416,8 @@ static void felix_teardown(struct dsa_switch *ds)
struct felix *felix = ocelot_to_felix(ocelot);
struct dsa_port *dp;
- dsa_switch_for_each_cpu_port(dp, ds) {
- felix_del_tag_protocol(ds, dp->index, felix->tag_proto);
- break;
- }
+ if (felix->tag_proto_ops)
+ felix->tag_proto_ops->teardown(ds);
dsa_switch_for_each_available_port(dp, ds)
ocelot_deinit_port(ocelot, dp->index);