net: dsa: microchip: add MIB counter reading support
authorTristram Ha <Tristram.Ha@microchip.com>
Sat, 23 Feb 2019 00:36:48 +0000 (16:36 -0800)
committerDavid S. Miller <davem@davemloft.net>
Mon, 25 Feb 2019 01:49:59 +0000 (17:49 -0800)
Add background MIB counter reading support.

Port MIB counters should only be read when there is link.  Otherwise it is
a waste of time as hardware never increases those counters.  There are
exceptions as some switches keep track of dropped counts no matter what.

Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/microchip/ksz9477.c
drivers/net/dsa/microchip/ksz_common.c
drivers/net/dsa/microchip/ksz_common.h
drivers/net/dsa/microchip/ksz_priv.h

index 4573b6ed2c4f434b0a33820c3068aed11f28988f..94cd38515ab82f44f59bab82604990fa771256db 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/gpio.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/iopoll.h>
 #include <linux/platform_data/microchip-ksz.h>
 #include <linux/phy.h>
 #include <linux/etherdevice.h>
@@ -18,8 +19,8 @@
 #include <net/switchdev.h>
 
 #include "ksz_priv.h"
-#include "ksz_common.h"
 #include "ksz9477_reg.h"
+#include "ksz_common.h"
 
 static const struct {
        int index;
@@ -259,6 +260,75 @@ static int ksz9477_reset_switch(struct ksz_device *dev)
        return 0;
 }
 
+static void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr,
+                             u64 *cnt)
+{
+       struct ksz_poll_ctx ctx = {
+               .dev = dev,
+               .port = port,
+               .offset = REG_PORT_MIB_CTRL_STAT__4,
+       };
+       struct ksz_port *p = &dev->ports[port];
+       u32 data;
+       int ret;
+
+       /* retain the flush/freeze bit */
+       data = p->freeze ? MIB_COUNTER_FLUSH_FREEZE : 0;
+       data |= MIB_COUNTER_READ;
+       data |= (addr << MIB_COUNTER_INDEX_S);
+       ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data);
+
+       ret = readx_poll_timeout(ksz_pread32_poll, &ctx, data,
+                                !(data & MIB_COUNTER_READ), 10, 1000);
+
+       /* failed to read MIB. get out of loop */
+       if (ret < 0) {
+               dev_dbg(dev->dev, "Failed to get MIB\n");
+               return;
+       }
+
+       /* count resets upon read */
+       ksz_pread32(dev, port, REG_PORT_MIB_DATA, &data);
+       *cnt += data;
+}
+
+static void ksz9477_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
+                             u64 *dropped, u64 *cnt)
+{
+       addr = ksz9477_mib_names[addr].index;
+       ksz9477_r_mib_cnt(dev, port, addr, cnt);
+}
+
+static void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze)
+{
+       u32 val = freeze ? MIB_COUNTER_FLUSH_FREEZE : 0;
+       struct ksz_port *p = &dev->ports[port];
+
+       /* enable/disable the port for flush/freeze function */
+       mutex_lock(&p->mib.cnt_mutex);
+       ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, val);
+
+       /* used by MIB counter reading code to know freeze is enabled */
+       p->freeze = freeze;
+       mutex_unlock(&p->mib.cnt_mutex);
+}
+
+static void ksz9477_port_init_cnt(struct ksz_device *dev, int port)
+{
+       struct ksz_port_mib *mib = &dev->ports[port].mib;
+
+       /* flush all enabled port MIB counters */
+       mutex_lock(&mib->cnt_mutex);
+       ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4,
+                    MIB_COUNTER_FLUSH_FREEZE);
+       ksz_write8(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FLUSH);
+       ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, 0);
+       mutex_unlock(&mib->cnt_mutex);
+
+       mib->cnt_ptr = 0;
+       memset(mib->counters, 0, dev->mib_cnt * sizeof(u64));
+}
+
 static enum dsa_tag_protocol ksz9477_get_tag_protocol(struct dsa_switch *ds,
                                                      int port)
 {
@@ -342,47 +412,6 @@ static void ksz9477_get_strings(struct dsa_switch *ds, int port,
        }
 }
 
-static void ksz_get_ethtool_stats(struct dsa_switch *ds, int port,
-                                 uint64_t *buf)
-{
-       struct ksz_device *dev = ds->priv;
-       int i;
-       u32 data;
-       int timeout;
-
-       mutex_lock(&dev->stats_mutex);
-
-       for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
-               data = MIB_COUNTER_READ;
-               data |= ((ksz9477_mib_names[i].index & 0xFF) <<
-                       MIB_COUNTER_INDEX_S);
-               ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data);
-
-               timeout = 1000;
-               do {
-                       ksz_pread32(dev, port, REG_PORT_MIB_CTRL_STAT__4,
-                                   &data);
-                       usleep_range(1, 10);
-                       if (!(data & MIB_COUNTER_READ))
-                               break;
-               } while (timeout-- > 0);
-
-               /* failed to read MIB. get out of loop */
-               if (!timeout) {
-                       dev_dbg(dev->dev, "Failed to get MIB\n");
-                       break;
-               }
-
-               /* count resets upon read */
-               ksz_pread32(dev, port, REG_PORT_MIB_DATA, &data);
-
-               dev->mib_value[i] += (uint64_t)data;
-               buf[i] = dev->mib_value[i];
-       }
-
-       mutex_unlock(&dev->stats_mutex);
-}
-
 static void ksz9477_cfg_port_member(struct ksz_device *dev, int port,
                                    u8 member)
 {
@@ -1151,9 +1180,14 @@ static int ksz9477_setup(struct dsa_switch *ds)
        /* queue based egress rate limit */
        ksz_cfg(dev, REG_SW_MAC_CTRL_5, SW_OUT_RATE_LIMIT_QUEUE_BASED, true);
 
+       /* enable global MIB counter freeze function */
+       ksz_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, true);
+
        /* start switch */
        ksz_cfg(dev, REG_SW_OPERATION, SW_START, true);
 
+       ksz_init_mib_timer(dev);
+
        return 0;
 }
 
@@ -1287,6 +1321,7 @@ static int ksz9477_switch_init(struct ksz_device *dev)
        if (!dev->ports)
                return -ENOMEM;
        for (i = 0; i < dev->mib_port_cnt; i++) {
+               mutex_init(&dev->ports[i].mib.cnt_mutex);
                dev->ports[i].mib.counters =
                        devm_kzalloc(dev->dev,
                                     sizeof(u64) *
@@ -1311,6 +1346,10 @@ static const struct ksz_dev_ops ksz9477_dev_ops = {
        .flush_dyn_mac_table = ksz9477_flush_dyn_mac_table,
        .phy_setup = ksz9477_phy_setup,
        .port_setup = ksz9477_port_setup,
+       .r_mib_cnt = ksz9477_r_mib_cnt,
+       .r_mib_pkt = ksz9477_r_mib_pkt,
+       .freeze_mib = ksz9477_freeze_mib,
+       .port_init_cnt = ksz9477_port_init_cnt,
        .shutdown = ksz9477_reset_switch,
        .detect = ksz9477_switch_detect,
        .init = ksz9477_switch_init,
index 6f728429eec693f6429ce4b9cd2e4956c5eab22d..eecfbf30cdc2bfad83569d92c5a52366443824f3 100644 (file)
@@ -40,6 +40,85 @@ void ksz_update_port_member(struct ksz_device *dev, int port)
 }
 EXPORT_SYMBOL_GPL(ksz_update_port_member);
 
+static void port_r_cnt(struct ksz_device *dev, int port)
+{
+       struct ksz_port_mib *mib = &dev->ports[port].mib;
+       u64 *dropped;
+
+       /* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */
+       while (mib->cnt_ptr < dev->reg_mib_cnt) {
+               dev->dev_ops->r_mib_cnt(dev, port, mib->cnt_ptr,
+                                       &mib->counters[mib->cnt_ptr]);
+               ++mib->cnt_ptr;
+       }
+
+       /* last one in storage */
+       dropped = &mib->counters[dev->mib_cnt];
+
+       /* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */
+       while (mib->cnt_ptr < dev->mib_cnt) {
+               dev->dev_ops->r_mib_pkt(dev, port, mib->cnt_ptr,
+                                       dropped, &mib->counters[mib->cnt_ptr]);
+               ++mib->cnt_ptr;
+       }
+       mib->cnt_ptr = 0;
+}
+
+static void ksz_mib_read_work(struct work_struct *work)
+{
+       struct ksz_device *dev = container_of(work, struct ksz_device,
+                                             mib_read);
+       struct ksz_port_mib *mib;
+       struct ksz_port *p;
+       int i;
+
+       for (i = 0; i < dev->mib_port_cnt; i++) {
+               p = &dev->ports[i];
+               mib = &p->mib;
+               mutex_lock(&mib->cnt_mutex);
+
+               /* Only read MIB counters when the port is told to do.
+                * If not, read only dropped counters when link is not up.
+                */
+               if (!p->read) {
+                       const struct dsa_port *dp = dsa_to_port(dev->ds, i);
+
+                       if (!netif_carrier_ok(dp->slave))
+                               mib->cnt_ptr = dev->reg_mib_cnt;
+               }
+               port_r_cnt(dev, i);
+               p->read = false;
+               mutex_unlock(&mib->cnt_mutex);
+       }
+}
+
+static void mib_monitor(struct timer_list *t)
+{
+       struct ksz_device *dev = from_timer(dev, t, mib_read_timer);
+
+       mod_timer(&dev->mib_read_timer, jiffies + dev->mib_read_interval);
+       schedule_work(&dev->mib_read);
+}
+
+void ksz_init_mib_timer(struct ksz_device *dev)
+{
+       int i;
+
+       /* Read MIB counters every 30 seconds to avoid overflow. */
+       dev->mib_read_interval = msecs_to_jiffies(30000);
+
+       INIT_WORK(&dev->mib_read, ksz_mib_read_work);
+       timer_setup(&dev->mib_read_timer, mib_monitor, 0);
+
+       for (i = 0; i < dev->mib_port_cnt; i++)
+               dev->dev_ops->port_init_cnt(dev, i);
+
+       /* Start the timer 2 seconds later. */
+       dev->mib_read_timer.expires = jiffies + msecs_to_jiffies(2000);
+       add_timer(&dev->mib_read_timer);
+}
+EXPORT_SYMBOL_GPL(ksz_init_mib_timer);
+
 int ksz_phy_read16(struct dsa_switch *ds, int addr, int reg)
 {
        struct ksz_device *dev = ds->priv;
@@ -72,6 +151,24 @@ int ksz_sset_count(struct dsa_switch *ds, int port, int sset)
 }
 EXPORT_SYMBOL_GPL(ksz_sset_count);
 
+void ksz_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *buf)
+{
+       const struct dsa_port *dp = dsa_to_port(ds, port);
+       struct ksz_device *dev = ds->priv;
+       struct ksz_port_mib *mib;
+
+       mib = &dev->ports[port].mib;
+       mutex_lock(&mib->cnt_mutex);
+
+       /* Only read dropped counters if no link. */
+       if (!netif_carrier_ok(dp->slave))
+               mib->cnt_ptr = dev->reg_mib_cnt;
+       port_r_cnt(dev, port);
+       memcpy(buf, mib->counters, dev->mib_cnt * sizeof(u64));
+       mutex_unlock(&mib->cnt_mutex);
+}
+EXPORT_SYMBOL_GPL(ksz_get_ethtool_stats);
+
 int ksz_port_bridge_join(struct dsa_switch *ds, int port,
                         struct net_device *br)
 {
@@ -339,6 +436,12 @@ EXPORT_SYMBOL(ksz_switch_register);
 
 void ksz_switch_remove(struct ksz_device *dev)
 {
+       /* timer started */
+       if (dev->mib_read_timer.expires) {
+               del_timer_sync(&dev->mib_read_timer);
+               flush_work(&dev->mib_read);
+       }
+
        dev->dev_ops->exit(dev);
        dsa_unregister_switch(dev->ds);
 
index 2dd832de0d526d55cd9512ba963219d12cf8bfe6..f45a55369e4b9b75989955db81bf28695cda4d90 100644 (file)
@@ -1,19 +1,21 @@
 /* SPDX-License-Identifier: GPL-2.0
  * Microchip switch driver common header
  *
- * Copyright (C) 2017-2018 Microchip Technology Inc.
+ * Copyright (C) 2017-2019 Microchip Technology Inc.
  */
 
 #ifndef __KSZ_COMMON_H
 #define __KSZ_COMMON_H
 
 void ksz_update_port_member(struct ksz_device *dev, int port);
+void ksz_init_mib_timer(struct ksz_device *dev);
 
 /* Common DSA access functions */
 
 int ksz_phy_read16(struct dsa_switch *ds, int addr, int reg);
 int ksz_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val);
 int ksz_sset_count(struct dsa_switch *ds, int port, int sset);
+void ksz_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *buf);
 int ksz_port_bridge_join(struct dsa_switch *ds, int port,
                         struct net_device *br);
 void ksz_port_bridge_leave(struct dsa_switch *ds, int port,
@@ -211,4 +213,18 @@ static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits,
        ksz_write8(dev, addr, data);
 }
 
+struct ksz_poll_ctx {
+       struct ksz_device *dev;
+       int port;
+       int offset;
+};
+
+static inline u32 ksz_pread32_poll(struct ksz_poll_ctx *ctx)
+{
+       u32 data;
+
+       ksz_pread32(ctx->dev, ctx->port, ctx->offset, &data);
+       return data;
+}
+
 #endif
index 0fdc58bdd503c0e20ac07886c7dd70aab91c03eb..1d2d98f35bb020a037e1d28d75c71e2b00e524e7 100644 (file)
@@ -14,8 +14,6 @@
 #include <linux/etherdevice.h>
 #include <net/dsa.h>
 
-#include "ksz9477_reg.h"
-
 struct ksz_io_ops;
 
 struct vlan_table {
@@ -23,6 +21,7 @@ struct vlan_table {
 };
 
 struct ksz_port_mib {
+       struct mutex cnt_mutex;         /* structure access */
        u8 cnt_ptr;
        u64 *counters;
 };
@@ -38,7 +37,8 @@ struct ksz_port {
        u32 fiber:1;                    /* port is fiber */
        u32 sgmii:1;                    /* port is SGMII */
        u32 force:1;
-       u32 link_just_down:1;           /* link just goes down */
+       u32 read:1;                     /* read MIB counters in background */
+       u32 freeze:1;                   /* MIB counter freeze is enabled */
 
        struct ksz_port_mib mib;
 };
@@ -79,8 +79,6 @@ struct ksz_device {
 
        struct vlan_table *vlan_cache;
 
-       u64 mib_value[TOTAL_SWITCH_COUNTER_NUM];
-
        u8 *txbuf;
 
        struct ksz_port *ports;
@@ -153,6 +151,7 @@ struct ksz_dev_ops {
                          u64 *cnt);
        void (*r_mib_pkt)(struct ksz_device *dev, int port, u16 addr,
                          u64 *dropped, u64 *cnt);
+       void (*freeze_mib)(struct ksz_device *dev, int port, bool freeze);
        void (*port_init_cnt)(struct ksz_device *dev, int port);
        int (*shutdown)(struct ksz_device *dev);
        int (*detect)(struct ksz_device *dev);