From: Vladimir Oltean <vladimir.olt...@nxp.com>

DSA supports cross-chip setups, and this means that when an operation is
performed on a port, the other switches in the fabric are also notified,
to get a chance to implement something similar which might be needed
when sending/receiving a packet to/from that port.

However we find that the DSA notifier match functions are written up
pretty much randomly, so let's add a test that is easy to run even
without cross-chip hardware, in order to better visualize the ports on
which the notifier will match.

[.../selftests/net/dsa] $ make
gcc     test_dsa_notifier_match.c  -o test_dsa_notifier_match
[.../selftests/net/dsa] $ ./test_dsa_notifier_match
Heat map for test notifier emitted on sw2p1:

   sw0p0     sw0p1     sw0p2     sw0p3     sw0p4
[  cpu  ] [  user ] [  user ] [  user ] [  dsa  ]
[   x   ] [       ] [       ] [       ] [   x   ]

   sw1p0     sw1p1     sw1p2     sw1p3     sw1p4
[  user ] [  user ] [  user ] [  user ] [  dsa  ]
[       ] [       ] [       ] [       ] [   x   ]

   sw2p0     sw2p1     sw2p2     sw2p3     sw2p4
[  user ] [  user ] [  user ] [  user ] [  dsa  ]
[       ] [   x   ] [       ] [       ] [       ]

Heat map for test notifier emitted on sw0p0:

   sw0p0     sw0p1     sw0p2     sw0p3     sw0p4
[  cpu  ] [  user ] [  user ] [  user ] [  dsa  ]
[   x   ] [       ] [       ] [       ] [       ]

   sw1p0     sw1p1     sw1p2     sw1p3     sw1p4
[  user ] [  user ] [  user ] [  user ] [  dsa  ]
[       ] [       ] [       ] [       ] [   x   ]

   sw2p0     sw2p1     sw2p2     sw2p3     sw2p4
[  user ] [  user ] [  user ] [  user ] [  dsa  ]
[       ] [       ] [       ] [       ] [   x   ]

Signed-off-by: Vladimir Oltean <vladimir.olt...@nxp.com>
---
 MAINTAINERS                                   |   1 +
 tools/testing/selftests/net/dsa/Makefile      |   6 +
 .../net/dsa/test_dsa_notifier_match.c         | 446 ++++++++++++++++++
 3 files changed, 453 insertions(+)
 create mode 100644 tools/testing/selftests/net/dsa/Makefile
 create mode 100644 tools/testing/selftests/net/dsa/test_dsa_notifier_match.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 986a8eef8633..0b501750065a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12397,6 +12397,7 @@ F:      include/linux/dsa/
 F:     include/linux/platform_data/dsa.h
 F:     include/net/dsa.h
 F:     net/dsa/
+F:     tools/testing/selftests/net/dsa/
 
 NETWORKING [GENERAL]
 M:     "David S. Miller" <da...@davemloft.net>
diff --git a/tools/testing/selftests/net/dsa/Makefile 
b/tools/testing/selftests/net/dsa/Makefile
new file mode 100644
index 000000000000..650d99bd6dab
--- /dev/null
+++ b/tools/testing/selftests/net/dsa/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_PROGS := test_dsa_notifier_match
+TEST_GEN_FILES := test_dsa_notifier_match
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/net/dsa/test_dsa_notifier_match.c 
b/tools/testing/selftests/net/dsa/test_dsa_notifier_match.c
new file mode 100644
index 000000000000..8118e97c6294
--- /dev/null
+++ b/tools/testing/selftests/net/dsa/test_dsa_notifier_match.c
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (c) 2008-2009 Marvell Semiconductor
+ * Copyright (c) 2013 Florian Fainelli <flor...@openwrt.org>
+ * Copyright (c) 2016 Andrew Lunn <and...@lunn.ch>
+ * Copyright (c) 2017 Savoir-faire Linux Inc.
+ *     Vivien Didelot <vivien.dide...@savoirfairelinux.com>
+ * Copyright (c) 2021 Vladimir Oltean <vladimir.olt...@nxp.com>
+ */
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+
+struct dsa_port {
+       enum {
+               DSA_PORT_TYPE_UNUSED = 0,
+               DSA_PORT_TYPE_CPU,
+               DSA_PORT_TYPE_DSA,
+               DSA_PORT_TYPE_USER,
+       } type;
+
+       struct dsa_switch       *ds;
+       unsigned int            index;
+       struct dsa_port         *cpu_dp;
+
+       STAILQ_ENTRY(dsa_port)  list;
+};
+
+/* TODO: ideally DSA ports would have a single dp->link_dp member,
+ * and no dst->rtable nor this struct dsa_link would be needed,
+ * but this would require some more complex tree walking,
+ * so keep it stupid at the moment and list them all.
+ */
+struct dsa_link {
+       struct dsa_port         *dp;
+       struct dsa_port         *link_dp;
+       STAILQ_ENTRY(dsa_link)  list;
+};
+
+struct dsa_switch_tree {
+       /* List of switch ports */
+       STAILQ_HEAD(ports_head, dsa_port) ports;
+
+       /* List of switches (for notifiers) */
+       STAILQ_HEAD(switches_head, dsa_switch) switches;
+
+       /* List of DSA links composing the routing table */
+       STAILQ_HEAD(rtable_head, dsa_link) rtable;
+};
+
+struct dsa_switch {
+       /*
+        * Parent switch tree, and switch index.
+        */
+       struct dsa_switch_tree          *dst;
+       unsigned int                    index;
+
+       STAILQ_ENTRY(dsa_switch)        list;
+
+       bool                            *heat_map;
+
+       size_t                          num_ports;
+};
+
+static const char dsa_port_type[][16] = {
+       [DSA_PORT_TYPE_UNUSED]  = "unused ",
+       [DSA_PORT_TYPE_CPU]     = "  cpu  ",
+       [DSA_PORT_TYPE_DSA]     = "  dsa  ",
+       [DSA_PORT_TYPE_USER]    = "  user ",
+};
+
+static struct dsa_port *dsa_to_port(struct dsa_switch *ds, int p)
+{
+       struct dsa_switch_tree *dst = ds->dst;
+       struct dsa_port *dp;
+
+       STAILQ_FOREACH(dp, &dst->ports, list)
+               if (dp->ds == ds && dp->index == p)
+                       return dp;
+
+       return NULL;
+}
+
+static bool dsa_is_unused_port(struct dsa_switch *ds, int p)
+{
+       return dsa_to_port(ds, p)->type == DSA_PORT_TYPE_UNUSED;
+}
+
+static bool dsa_is_cpu_port(struct dsa_switch *ds, int p)
+{
+       return dsa_to_port(ds, p)->type == DSA_PORT_TYPE_CPU;
+}
+
+static bool dsa_is_dsa_port(struct dsa_switch *ds, int p)
+{
+       return dsa_to_port(ds, p)->type == DSA_PORT_TYPE_DSA;
+}
+
+static bool dsa_is_user_port(struct dsa_switch *ds, int p)
+{
+       return dsa_to_port(ds, p)->type == DSA_PORT_TYPE_USER;
+}
+
+static int dsa_register_switch(struct dsa_switch_tree *dst, int index,
+                              int num_ports)
+{
+       struct dsa_switch *ds;
+       struct dsa_port *dp;
+       int err, port;
+
+       ds = calloc(1, sizeof(*ds));
+       if (!ds)
+               return -ENOMEM;
+
+       ds->heat_map = calloc(num_ports, sizeof(bool));
+       if (!ds->heat_map) {
+               free(ds);
+               return -ENOMEM;
+       }
+
+       ds->num_ports = num_ports;
+       ds->index = index;
+       ds->dst = dst;
+
+       for (port = 0; port < num_ports; port++) {
+               dp = calloc(1, sizeof(*dp));
+               if (!dp) {
+                       err = -ENOMEM;
+                       goto out_free_ports;
+               }
+
+               dp->ds = ds;
+               dp->index = port;
+               STAILQ_INSERT_TAIL(&dst->ports, dp, list);
+       }
+
+       STAILQ_INSERT_TAIL(&dst->switches, ds, list);
+
+       return 0;
+
+out_free_ports:
+       while ((dp = STAILQ_FIRST(&dst->ports))) {
+               STAILQ_REMOVE_HEAD(&dst->ports, list);
+               free(dp);
+       }
+
+       free(ds);
+       return err;
+}
+
+static void dsa_tree_teardown(struct dsa_switch_tree *dst)
+{
+       struct dsa_switch *ds;
+       struct dsa_port *dp;
+       struct dsa_link *dl;
+
+       while (dl = STAILQ_FIRST(&dst->rtable)) {
+               STAILQ_REMOVE_HEAD(&dst->rtable, list);
+               free(dl);
+       }
+
+       while (dp = STAILQ_FIRST(&dst->ports)) {
+               STAILQ_REMOVE_HEAD(&dst->ports, list);
+               free(dp);
+       }
+
+       while (ds = STAILQ_FIRST(&dst->switches)) {
+               STAILQ_REMOVE_HEAD(&dst->switches, list);
+               free(ds->heat_map);
+               free(ds);
+       }
+}
+
+static struct dsa_port *dsa_tree_find_port_by_index(struct dsa_switch_tree 
*dst,
+                                                   int sw_index, int port)
+{
+       struct dsa_port *dp;
+
+       STAILQ_FOREACH(dp, &dst->ports, list)
+               if (dp->ds->index == sw_index && dp->index == port)
+                       return dp;
+
+       return NULL;
+}
+
+static int dsa_setup_link(struct dsa_switch_tree *dst, int from_sw_index,
+                         int from_port, int to_sw_index, int to_port)
+{
+       struct dsa_port *dp, *link_dp;
+       struct dsa_link *dl;
+
+       dp = dsa_tree_find_port_by_index(dst, from_sw_index, from_port);
+       if (!dp) {
+               fprintf(stderr, "failed to find sw%dp%d\n", from_sw_index,
+                       from_port);
+               return -ENODEV;
+       }
+
+       link_dp = dsa_tree_find_port_by_index(dst, to_sw_index, to_port);
+       if (!link_dp) {
+               fprintf(stderr, "failed to find sw%dp%d\n", to_sw_index,
+                       to_port);
+               return -ENODEV;
+       }
+
+       dl = calloc(1, sizeof(*dl));
+       if (!dl)
+               return -ENOMEM;
+
+       STAILQ_INSERT_HEAD(&dst->rtable, dl, list);
+       dp->type = DSA_PORT_TYPE_DSA;
+       link_dp->type = DSA_PORT_TYPE_DSA;
+
+       return 0;
+}
+
+static int dsa_tree_setup_default_cpu(struct dsa_switch_tree *dst,
+                                     int sw_index, int port)
+{
+       struct dsa_port *dp, *cpu_dp;
+
+       cpu_dp = dsa_tree_find_port_by_index(dst, sw_index, port);
+       if (!cpu_dp) {
+               fprintf(stderr, "failed to find sw%dp%d\n", sw_index, port);
+               return -ENODEV;
+       }
+
+       cpu_dp->type = DSA_PORT_TYPE_CPU;
+
+       STAILQ_FOREACH(dp, &dst->ports, list)
+               if (dsa_is_user_port(dp->ds, dp->index) ||
+                   dsa_is_dsa_port(dp->ds, dp->index))
+                       dp->cpu_dp = cpu_dp;
+
+       return 0;
+}
+
+static void
+dsa_tree_convert_all_unused_ports_to_user(struct dsa_switch_tree *dst)
+{
+       struct dsa_port *dp;
+
+       STAILQ_FOREACH(dp, &dst->ports, list)
+               if (dsa_is_unused_port(dp->ds, dp->index))
+                       dp->type = DSA_PORT_TYPE_USER;
+}
+
+/*  CPU
+ *   |
+ * sw0p0 sw0p1 sw0p2 sw0p3 sw0p4
+ *                           | DSA link
+ * sw1p0 sw1p1 sw1p2 sw1p3 sw1p4
+ *                           | DSA link
+ * sw2p0 sw2p1 sw2p2 sw2p3 sw2p4
+ */
+static int dsa_setup_tree(struct dsa_switch_tree *dst)
+{
+       int err;
+
+       STAILQ_INIT(&dst->ports);
+       STAILQ_INIT(&dst->switches);
+       STAILQ_INIT(&dst->rtable);
+
+       err = dsa_register_switch(dst, 0, 5);
+       if (err)
+               return err;
+
+       err = dsa_register_switch(dst, 1, 5);
+       if (err)
+               return err;
+
+       err = dsa_register_switch(dst, 2, 5);
+       if (err)
+               return err;
+
+       /* sw0p4 to sw1p4 */
+       err = dsa_setup_link(dst, 0, 4, 1, 4);
+       if (err)
+               return err;
+
+       /* sw0p4 to sw2p4 */
+       err = dsa_setup_link(dst, 0, 4, 2, 4);
+       if (err)
+               return err;
+
+       /* sw1p4 to sw0p4 */
+       err = dsa_setup_link(dst, 1, 4, 0, 4);
+       if (err)
+               return err;
+
+       /* sw1p4 to sw2p4 */
+       err = dsa_setup_link(dst, 1, 4, 2, 4);
+       if (err)
+               return err;
+
+       /* sw2p4 to sw0p4 */
+       err = dsa_setup_link(dst, 2, 4, 0, 4);
+       if (err)
+               return err;
+
+       /* sw2p4 to sw1p4 */
+       err = dsa_setup_link(dst, 2, 4, 1, 4);
+       if (err)
+               return err;
+
+       dsa_tree_convert_all_unused_ports_to_user(dst);
+
+       err = dsa_tree_setup_default_cpu(dst, 0, 0);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+enum {
+       DSA_NOTIFIER_TEST,
+};
+
+struct dsa_notifier_test_info {
+       int sw_index;
+       int port;
+};
+
+static int dsa_switch_test_match(struct dsa_switch *ds, int port,
+                                struct dsa_notifier_test_info *info)
+{
+       if (ds->index == info->sw_index)
+               return port == info->port;
+
+       return dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port);
+}
+
+static int dsa_switch_test(struct dsa_switch *ds,
+                          struct dsa_notifier_test_info *info)
+{
+       int port;
+
+       for (port = 0; port < ds->num_ports; port++)
+               if (dsa_switch_test_match(ds, port, info))
+                       ds->heat_map[port] = true;
+
+       return 0;
+}
+
+static int dsa_switch_event(struct dsa_switch *ds, unsigned long event,
+                           void *info)
+{
+       int err;
+
+       switch (event) {
+       case DSA_NOTIFIER_TEST:
+               err = dsa_switch_test(ds, info);
+               break;
+       default:
+               err = -EOPNOTSUPP;
+               break;
+       }
+
+       return err;
+}
+
+static int dsa_tree_notify(struct dsa_switch_tree *dst, unsigned long e, void 
*v)
+{
+       struct dsa_switch *ds;
+       int err;
+
+       STAILQ_FOREACH(ds, &dst->switches, list) {
+               err = dsa_switch_event(ds, e, v);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
+static int dsa_port_notify(const struct dsa_port *dp, unsigned long e, void *v)
+{
+       return dsa_tree_notify(dp->ds->dst, e, v);
+}
+
+static int dsa_test_notify(struct dsa_switch_tree *dst, int sw_index, int port)
+{
+       struct dsa_notifier_test_info info = {
+               .sw_index = sw_index,
+               .port = port,
+       };
+       struct dsa_switch *ds;
+       struct dsa_port *dp;
+       int err;
+
+       dp = dsa_tree_find_port_by_index(dst, sw_index, port);
+       if (!dp) {
+               fprintf(stderr, "failed to find sw%dp%d\n", sw_index, port);
+               return -ENODEV;
+       }
+
+       err = dsa_port_notify(dp, DSA_NOTIFIER_TEST, &info);
+       if (err)
+               return err;
+
+       printf("Heat map for test notifier emitted on sw%dp%d:\n\n",
+              sw_index, port);
+
+       STAILQ_FOREACH(ds, &dst->switches, list) {
+               for (port = 0; port < ds->num_ports; port++)
+                       printf("   sw%dp%d  ", ds->index, port);
+               printf("\n");
+               for (port = 0; port < ds->num_ports; port++)
+                       printf("[%s] ", dsa_port_type[dsa_to_port(ds, 
port)->type]);
+               printf("\n");
+               for (port = 0; port < ds->num_ports; port++) {
+                       if (ds->heat_map[port])
+                               printf("[   x   ] ");
+                       else
+                               printf("[       ] ");
+               }
+               printf("\n\n");
+               memset(ds->heat_map, 0, sizeof(bool) * ds->num_ports);
+       }
+}
+
+int main(void)
+{
+       struct dsa_switch_tree dst = {0};
+       struct dsa_port *dp;
+       int err;
+
+       err = dsa_setup_tree(&dst);
+       if (err)
+               goto out;
+
+       err = dsa_test_notify(&dst, 2, 1);
+       if (err)
+               goto out;
+
+       err = dsa_test_notify(&dst, 0, 0);
+       if (err)
+               goto out;
+
+out:
+       dsa_tree_teardown(&dst);
+
+       return err;
+}
-- 
2.25.1

Reply via email to