net: dsa: Provide CPU port statistics to master netdev
authorFlorian Fainelli <f.fainelli@gmail.com>
Wed, 27 Apr 2016 18:45:14 +0000 (11:45 -0700)
committerDavid S. Miller <davem@davemloft.net>
Thu, 28 Apr 2016 21:16:17 +0000 (17:16 -0400)
This patch overloads the DSA master netdev, aka CPU Ethernet MAC to also
include switch-side statistics, which is useful for debugging purposes,
when the switch is not properly connected to the Ethernet MAC (duplex
mismatch, (RG)MII electrical issues etc.).

We accomplish this by retaining the original copy of the master netdev's
ethtool_ops, and just overload the 3 operations we care about:
get_sset_count, get_strings and get_ethtool_stats so as to intercept
these calls and call into the original master_netdev ethtool_ops, plus
our own.

We take this approach as opposed to providing a set of DSA helper
functions that would retrive the CPU port's statistics, because the
entire purpose of DSA is to allow unmodified Ethernet MAC drivers to be
used as CPU conduit interfaces, therefore, statistics overlay in such
drivers would simply not scale.

The new ethtool -S <iface> output would therefore look like this now:
<iface> statistics
p<2 digits cpu port number>_<switch MIB counter names>

Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/dsa.h
net/dsa/slave.c

index 2d280aba97e25c0043fc6af1023e975cf4a7f077..8e86af87c84fe812efb9a7cfbb5d9e6d17d8e222 100644 (file)
@@ -110,6 +110,11 @@ struct dsa_switch_tree {
                                       struct net_device *orig_dev);
        enum dsa_tag_protocol   tag_protocol;
 
+       /*
+        * Original copy of the master netdev ethtool_ops
+        */
+       struct ethtool_ops      master_ethtool_ops;
+
        /*
         * The switch and port to which the CPU is attached.
         */
index 3b6750f5e68b012235bbd3d96ec2d76ecedac7c0..5ea8a40c8d33da8a2964939eff38fbdbad98ea17 100644 (file)
@@ -666,6 +666,78 @@ static void dsa_slave_get_strings(struct net_device *dev,
        }
 }
 
+static void dsa_cpu_port_get_ethtool_stats(struct net_device *dev,
+                                          struct ethtool_stats *stats,
+                                          uint64_t *data)
+{
+       struct dsa_switch_tree *dst = dev->dsa_ptr;
+       struct dsa_switch *ds = dst->ds[0];
+       s8 cpu_port = dst->cpu_port;
+       int count = 0;
+
+       if (dst->master_ethtool_ops.get_sset_count) {
+               count = dst->master_ethtool_ops.get_sset_count(dev,
+                                                              ETH_SS_STATS);
+               dst->master_ethtool_ops.get_ethtool_stats(dev, stats, data);
+       }
+
+       if (ds->drv->get_ethtool_stats)
+               ds->drv->get_ethtool_stats(ds, cpu_port, data + count);
+}
+
+static int dsa_cpu_port_get_sset_count(struct net_device *dev, int sset)
+{
+       struct dsa_switch_tree *dst = dev->dsa_ptr;
+       struct dsa_switch *ds = dst->ds[0];
+       int count = 0;
+
+       if (dst->master_ethtool_ops.get_sset_count)
+               count += dst->master_ethtool_ops.get_sset_count(dev, sset);
+
+       if (sset == ETH_SS_STATS && ds->drv->get_sset_count)
+               count += ds->drv->get_sset_count(ds);
+
+       return count;
+}
+
+static void dsa_cpu_port_get_strings(struct net_device *dev,
+                                    uint32_t stringset, uint8_t *data)
+{
+       struct dsa_switch_tree *dst = dev->dsa_ptr;
+       struct dsa_switch *ds = dst->ds[0];
+       s8 cpu_port = dst->cpu_port;
+       int len = ETH_GSTRING_LEN;
+       int mcount = 0, count;
+       unsigned int i;
+       uint8_t pfx[4];
+       uint8_t *ndata;
+
+       snprintf(pfx, sizeof(pfx), "p%.2d", cpu_port);
+       /* We do not want to be NULL-terminated, since this is a prefix */
+       pfx[sizeof(pfx) - 1] = '_';
+
+       if (dst->master_ethtool_ops.get_sset_count) {
+               mcount = dst->master_ethtool_ops.get_sset_count(dev,
+                                                               ETH_SS_STATS);
+               dst->master_ethtool_ops.get_strings(dev, stringset, data);
+       }
+
+       if (stringset == ETH_SS_STATS && ds->drv->get_strings) {
+               ndata = data + mcount * len;
+               /* This function copies ETH_GSTRINGS_LEN bytes, we will mangle
+                * the output after to prepend our CPU port prefix we
+                * constructed earlier
+                */
+               ds->drv->get_strings(ds, cpu_port, ndata);
+               count = ds->drv->get_sset_count(ds);
+               for (i = 0; i < count; i++) {
+                       memmove(ndata + (i * len + sizeof(pfx)),
+                               ndata + i * len, len - sizeof(pfx));
+                       memcpy(ndata + i * len, pfx, sizeof(pfx));
+               }
+       }
+}
+
 static void dsa_slave_get_ethtool_stats(struct net_device *dev,
                                        struct ethtool_stats *stats,
                                        uint64_t *data)
@@ -821,6 +893,8 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = {
        .get_eee                = dsa_slave_get_eee,
 };
 
+static struct ethtool_ops dsa_cpu_port_ethtool_ops;
+
 static const struct net_device_ops dsa_slave_netdev_ops = {
        .ndo_open               = dsa_slave_open,
        .ndo_stop               = dsa_slave_close,
@@ -1038,6 +1112,7 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
                     int port, char *name)
 {
        struct net_device *master = ds->dst->master_netdev;
+       struct dsa_switch_tree *dst = ds->dst;
        struct net_device *slave_dev;
        struct dsa_slave_priv *p;
        int ret;
@@ -1049,6 +1124,19 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
 
        slave_dev->features = master->vlan_features;
        slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;
+       if (master->ethtool_ops != &dsa_cpu_port_ethtool_ops) {
+               memcpy(&dst->master_ethtool_ops, master->ethtool_ops,
+                      sizeof(struct ethtool_ops));
+               memcpy(&dsa_cpu_port_ethtool_ops, &dst->master_ethtool_ops,
+                      sizeof(struct ethtool_ops));
+               dsa_cpu_port_ethtool_ops.get_sset_count =
+                                       dsa_cpu_port_get_sset_count;
+               dsa_cpu_port_ethtool_ops.get_ethtool_stats =
+                                       dsa_cpu_port_get_ethtool_stats;
+               dsa_cpu_port_ethtool_ops.get_strings =
+                                       dsa_cpu_port_get_strings;
+               master->ethtool_ops = &dsa_cpu_port_ethtool_ops;
+       }
        eth_hw_addr_inherit(slave_dev, master);
        slave_dev->priv_flags |= IFF_NO_QUEUE;
        slave_dev->netdev_ops = &dsa_slave_netdev_ops;