Skip to content

Commit

Permalink
net: dsa: microchip: add MIB counter reading support
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Tristram Ha authored and David S. Miller committed Feb 25, 2019
1 parent 42fc6a4 commit 7c6ff47
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 48 deletions.
123 changes: 81 additions & 42 deletions drivers/net/dsa/microchip/ksz9477.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) *
Expand All @@ -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,
Expand Down
103 changes: 103 additions & 0 deletions drivers/net/dsa/microchip/ksz_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);

Expand Down
18 changes: 17 additions & 1 deletion drivers/net/dsa/microchip/ksz_common.h
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
9 changes: 4 additions & 5 deletions drivers/net/dsa/microchip/ksz_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@
#include <linux/etherdevice.h>
#include <net/dsa.h>

#include "ksz9477_reg.h"

struct ksz_io_ops;

struct vlan_table {
u32 table[3];
};

struct ksz_port_mib {
struct mutex cnt_mutex; /* structure access */
u8 cnt_ptr;
u64 *counters;
};
Expand All @@ -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;
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 7c6ff47

Please sign in to comment.