[PATCH v5 2/2] net: dsa: qca: ar9331: export stats64

From: Oleksij Rempel
Date: Fri Dec 11 2020 - 05:56:14 EST


Add stats support for the ar9331 switch.

Signed-off-by: Oleksij Rempel <o.rempel@xxxxxxxxxxxxxx>
---
drivers/net/dsa/qca/ar9331.c | 256 ++++++++++++++++++++++++++++++++++-
1 file changed, 255 insertions(+), 1 deletion(-)

diff --git a/drivers/net/dsa/qca/ar9331.c b/drivers/net/dsa/qca/ar9331.c
index 4d49c5f2b790..5baef0ec6410 100644
--- a/drivers/net/dsa/qca/ar9331.c
+++ b/drivers/net/dsa/qca/ar9331.c
@@ -101,6 +101,9 @@
AR9331_SW_PORT_STATUS_RX_FLOW_EN | AR9331_SW_PORT_STATUS_TX_FLOW_EN | \
AR9331_SW_PORT_STATUS_SPEED_M)

+/* MIB registers */
+#define AR9331_MIB_COUNTER(x) (0x20000 + ((x) * 0x100))
+
/* Phy bypass mode
* ------------------------------------------------------------------------
* Bit: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |10 |11 |12 |13 |14 |15 |
@@ -154,6 +157,111 @@
#define AR9331_SW_MDIO_POLL_SLEEP_US 1
#define AR9331_SW_MDIO_POLL_TIMEOUT_US 20

+/* The interval should be small enough to avoid overflow of 32bit MIBs */
+/*
+ * FIXME: as long as we can't read MIBs from stats64 call directly, we should
+ * poll stats more frequently then it is actually needed. In normal case
+ * 100 sec interval should be OK.
+ */
+#define STATS_INTERVAL_JIFFIES (3 * HZ)
+
+struct ar9331_sw_stats_raw {
+ u32 rxbroad; /* 0x00 */
+ u32 rxpause; /* 0x04 */
+ u32 rxmulti; /* 0x08 */
+ u32 rxfcserr; /* 0x0c */
+ u32 rxalignerr; /* 0x10 */
+ u32 rxrunt; /* 0x14 */
+ u32 rxfragment; /* 0x18 */
+ u32 rx64byte; /* 0x1c */
+ u32 rx128byte; /* 0x20 */
+ u32 rx256byte; /* 0x24 */
+ u32 rx512byte; /* 0x28 */
+ u32 rx1024byte; /* 0x2c */
+ u32 rx1518byte; /* 0x30 */
+ u32 rxmaxbyte; /* 0x34 */
+ u32 rxtoolong; /* 0x38 */
+ u32 rxgoodbyte; /* 0x3c */
+ u32 rxgoodbyte_hi;
+ u32 rxbadbyte; /* 0x44 */
+ u32 rxbadbyte_hi;
+ u32 rxoverflow; /* 0x4c */
+ u32 filtered; /* 0x50 */
+ u32 txbroad; /* 0x54 */
+ u32 txpause; /* 0x58 */
+ u32 txmulti; /* 0x5c */
+ u32 txunderrun; /* 0x60 */
+ u32 tx64byte; /* 0x64 */
+ u32 tx128byte; /* 0x68 */
+ u32 tx256byte; /* 0x6c */
+ u32 tx512byte; /* 0x70 */
+ u32 tx1024byte; /* 0x74 */
+ u32 tx1518byte; /* 0x78 */
+ u32 txmaxbyte; /* 0x7c */
+ u32 txoversize; /* 0x80 */
+ u32 txbyte; /* 0x84 */
+ u32 txbyte_hi;
+ u32 txcollision; /* 0x8c */
+ u32 txabortcol; /* 0x90 */
+ u32 txmulticol; /* 0x94 */
+ u32 txsinglecol; /* 0x98 */
+ u32 txexcdefer; /* 0x9c */
+ u32 txdefer; /* 0xa0 */
+ u32 txlatecol; /* 0xa4 */
+};
+
+struct ar9331_sw_stats {
+ u64_stats_t rxbroad;
+ u64_stats_t rxpause;
+ u64_stats_t rxmulti;
+ u64_stats_t rxfcserr;
+ u64_stats_t rxalignerr;
+ u64_stats_t rxrunt;
+ u64_stats_t rxfragment;
+ u64_stats_t rx64byte;
+ u64_stats_t rx128byte;
+ u64_stats_t rx256byte;
+ u64_stats_t rx512byte;
+ u64_stats_t rx1024byte;
+ u64_stats_t rx1518byte;
+ u64_stats_t rxmaxbyte;
+ u64_stats_t rxtoolong;
+ u64_stats_t rxgoodbyte;
+ u64_stats_t rxbadbyte;
+ u64_stats_t rxoverflow;
+ u64_stats_t filtered;
+ u64_stats_t txbroad;
+ u64_stats_t txpause;
+ u64_stats_t txmulti;
+ u64_stats_t txunderrun;
+ u64_stats_t tx64byte;
+ u64_stats_t tx128byte;
+ u64_stats_t tx256byte;
+ u64_stats_t tx512byte;
+ u64_stats_t tx1024byte;
+ u64_stats_t tx1518byte;
+ u64_stats_t txmaxbyte;
+ u64_stats_t txoversize;
+ u64_stats_t txbyte;
+ u64_stats_t txcollision;
+ u64_stats_t txabortcol;
+ u64_stats_t txmulticol;
+ u64_stats_t txsinglecol;
+ u64_stats_t txexcdefer;
+ u64_stats_t txdefer;
+ u64_stats_t txlatecol;
+
+ struct u64_stats_sync syncp;
+};
+
+struct ar9331_sw_priv;
+struct ar9331_sw_port {
+ int idx;
+ struct ar9331_sw_priv *priv;
+ struct delayed_work mib_read;
+ struct ar9331_sw_stats stats;
+};
+
struct ar9331_sw_priv {
struct device *dev;
struct dsa_switch ds;
@@ -165,6 +273,7 @@ struct ar9331_sw_priv {
struct mii_bus *sbus; /* mdio slave */
struct regmap *regmap;
struct reset_control *sw_reset;
+ struct ar9331_sw_port port[AR9331_SW_PORTS];
};

/* Warning: switch reset will reset last AR9331_SW_MDIO_PHY_MODE_PAGE request
@@ -424,6 +533,7 @@ static void ar9331_sw_phylink_mac_link_down(struct dsa_switch *ds, int port,
phy_interface_t interface)
{
struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv;
+ struct ar9331_sw_port *p = &priv->port[port];
struct regmap *regmap = priv->regmap;
int ret;

@@ -431,6 +541,8 @@ static void ar9331_sw_phylink_mac_link_down(struct dsa_switch *ds, int port,
AR9331_SW_PORT_STATUS_MAC_MASK, 0);
if (ret)
dev_err_ratelimited(priv->dev, "%s: %i\n", __func__, ret);
+
+ cancel_delayed_work_sync(&p->mib_read);
}

static void ar9331_sw_phylink_mac_link_up(struct dsa_switch *ds, int port,
@@ -441,10 +553,13 @@ static void ar9331_sw_phylink_mac_link_up(struct dsa_switch *ds, int port,
bool tx_pause, bool rx_pause)
{
struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv;
+ struct ar9331_sw_port *p = &priv->port[port];
struct regmap *regmap = priv->regmap;
u32 val;
int ret;

+ schedule_delayed_work(&p->mib_read, 0);
+
val = AR9331_SW_PORT_STATUS_MAC_MASK;
switch (speed) {
case SPEED_1000:
@@ -477,6 +592,128 @@ static void ar9331_sw_phylink_mac_link_up(struct dsa_switch *ds, int port,
dev_err_ratelimited(priv->dev, "%s: %i\n", __func__, ret);
}

+#define AR9331_STATS_ADD(_stats, _raw, _reg) \
+{ \
+ u64_stats_add(&_stats->_reg, _raw._reg); \
+}
+
+static void ar9331_read_stats(struct ar9331_sw_port *port)
+{
+ struct ar9331_sw_stats *stats = &port->stats;
+ struct ar9331_sw_priv *priv = port->priv;
+ struct ar9331_sw_stats_raw raw;
+ int ret;
+
+ /* Do the slowest part first, to avoid needles locking for long time */
+ ret = regmap_bulk_read(priv->regmap, AR9331_MIB_COUNTER(port->idx),
+ &raw, sizeof(raw) / sizeof(u32));
+ if (ret) {
+ dev_err_ratelimited(priv->dev, "%s: %i\n", __func__, ret);
+ return;
+ }
+ /* All MIB counters are cleared automatically on read */
+
+ u64_stats_update_begin(&stats->syncp);
+
+ AR9331_STATS_ADD(stats, raw, rxgoodbyte);
+ AR9331_STATS_ADD(stats, raw, rxbroad);
+ AR9331_STATS_ADD(stats, raw, rxpause);
+ AR9331_STATS_ADD(stats, raw, rxmulti);
+ AR9331_STATS_ADD(stats, raw, rxfcserr);
+ AR9331_STATS_ADD(stats, raw, rxalignerr);
+ AR9331_STATS_ADD(stats, raw, rxrunt);
+ AR9331_STATS_ADD(stats, raw, rxfragment);
+ AR9331_STATS_ADD(stats, raw, rx64byte);
+ AR9331_STATS_ADD(stats, raw, rx128byte);
+ AR9331_STATS_ADD(stats, raw, rx256byte);
+ AR9331_STATS_ADD(stats, raw, rx512byte);
+ AR9331_STATS_ADD(stats, raw, rx1024byte);
+ AR9331_STATS_ADD(stats, raw, rx1518byte);
+ AR9331_STATS_ADD(stats, raw, rxmaxbyte);
+ AR9331_STATS_ADD(stats, raw, rxtoolong);
+ AR9331_STATS_ADD(stats, raw, rxbadbyte);
+ AR9331_STATS_ADD(stats, raw, rxoverflow);
+ AR9331_STATS_ADD(stats, raw, filtered);
+ AR9331_STATS_ADD(stats, raw, txbroad);
+ AR9331_STATS_ADD(stats, raw, txpause);
+ AR9331_STATS_ADD(stats, raw, txmulti);
+ AR9331_STATS_ADD(stats, raw, txunderrun);
+ AR9331_STATS_ADD(stats, raw, tx64byte);
+ AR9331_STATS_ADD(stats, raw, tx128byte);
+ AR9331_STATS_ADD(stats, raw, tx256byte);
+ AR9331_STATS_ADD(stats, raw, tx512byte);
+ AR9331_STATS_ADD(stats, raw, tx1024byte);
+ AR9331_STATS_ADD(stats, raw, tx1518byte);
+ AR9331_STATS_ADD(stats, raw, txmaxbyte);
+ AR9331_STATS_ADD(stats, raw, txoversize);
+ AR9331_STATS_ADD(stats, raw, txbyte);
+ AR9331_STATS_ADD(stats, raw, txcollision);
+ AR9331_STATS_ADD(stats, raw, txabortcol);
+ AR9331_STATS_ADD(stats, raw, txmulticol);
+ AR9331_STATS_ADD(stats, raw, txsinglecol);
+ AR9331_STATS_ADD(stats, raw, txexcdefer);
+ AR9331_STATS_ADD(stats, raw, txdefer);
+ AR9331_STATS_ADD(stats, raw, txlatecol);
+
+ u64_stats_update_end(&stats->syncp);
+}
+
+static void ar9331_stats_update(struct ar9331_sw_port *port,
+ struct rtnl_link_stats64 *stats)
+{
+ struct ar9331_sw_stats *s = &port->stats;
+
+ stats->rx_packets = u64_stats_read(&s->rx64byte) +
+ u64_stats_read(&s->rx128byte) + u64_stats_read(&s->rx256byte) +
+ u64_stats_read(&s->rx512byte) + u64_stats_read(&s->rx1024byte) +
+ u64_stats_read(&s->rx1518byte) + u64_stats_read(&s->rxmaxbyte);
+ stats->tx_packets = u64_stats_read(&s->tx64byte) +
+ u64_stats_read(&s->tx128byte) + u64_stats_read(&s->tx256byte) +
+ u64_stats_read(&s->tx512byte) + u64_stats_read(&s->tx1024byte) +
+ u64_stats_read(&s->tx1518byte) + u64_stats_read(&s->txmaxbyte);
+ stats->rx_bytes = u64_stats_read(&s->rxgoodbyte);
+ stats->tx_bytes = u64_stats_read(&s->txbyte);
+ stats->rx_errors = u64_stats_read(&s->rxfcserr) +
+ u64_stats_read(&s->rxalignerr) + u64_stats_read(&s->rxrunt) +
+ u64_stats_read(&s->rxfragment) + u64_stats_read(&s->rxoverflow);
+ stats->tx_errors = u64_stats_read(&s->txoversize);
+ stats->multicast = u64_stats_read(&s->rxmulti);
+ stats->collisions = u64_stats_read(&s->txcollision);
+ stats->rx_length_errors = u64_stats_read(&s->rxrunt) +
+ u64_stats_read(&s->rxfragment) + u64_stats_read(&s->rxtoolong);
+ stats->rx_crc_errors = u64_stats_read(&s->rxfcserr) +
+ u64_stats_read(&s->rxalignerr) + u64_stats_read(&s->rxfragment);
+ stats->rx_frame_errors = u64_stats_read(&s->rxalignerr);
+ stats->rx_missed_errors = u64_stats_read(&s->rxoverflow);
+ stats->tx_aborted_errors = u64_stats_read(&s->txabortcol);
+ stats->tx_fifo_errors = u64_stats_read(&s->txunderrun);
+ stats->tx_window_errors = u64_stats_read(&s->txlatecol);
+ stats->rx_nohandler = u64_stats_read(&s->filtered);
+}
+
+static void ar9331_do_stats_poll(struct work_struct *work)
+{
+ struct ar9331_sw_port *port = container_of(work, struct ar9331_sw_port,
+ mib_read.work);
+
+ ar9331_read_stats(port);
+
+ schedule_delayed_work(&port->mib_read, STATS_INTERVAL_JIFFIES);
+}
+
+static void ar9331_get_stats64(struct dsa_switch *ds, int port,
+ struct rtnl_link_stats64 *s)
+{
+ struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv;
+ struct ar9331_sw_port *p = &priv->port[port];
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin(&p->stats.syncp);
+ ar9331_stats_update(p, s);
+ } while (u64_stats_fetch_retry(&p->stats.syncp, start));
+}
+
static const struct dsa_switch_ops ar9331_sw_ops = {
.get_tag_protocol = ar9331_sw_get_tag_protocol,
.setup = ar9331_sw_setup,
@@ -485,6 +722,7 @@ static const struct dsa_switch_ops ar9331_sw_ops = {
.phylink_mac_config = ar9331_sw_phylink_mac_config,
.phylink_mac_link_down = ar9331_sw_phylink_mac_link_down,
.phylink_mac_link_up = ar9331_sw_phylink_mac_link_up,
+ .get_stats64 = ar9331_get_stats64,
};

static irqreturn_t ar9331_sw_irq(int irq, void *data)
@@ -796,7 +1034,7 @@ static int ar9331_sw_probe(struct mdio_device *mdiodev)
{
struct ar9331_sw_priv *priv;
struct dsa_switch *ds;
- int ret;
+ int ret, i;

priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -831,6 +1069,15 @@ static int ar9331_sw_probe(struct mdio_device *mdiodev)
ds->ops = &priv->ops;
dev_set_drvdata(&mdiodev->dev, priv);

+ for (i = 0; i < ARRAY_SIZE(priv->port); i++) {
+ struct ar9331_sw_port *port = &priv->port[i];
+
+ port->idx = i;
+ port->priv = priv;
+ u64_stats_init(&port->stats.syncp);
+ INIT_DELAYED_WORK(&port->mib_read, ar9331_do_stats_poll);
+ }
+
ret = dsa_register_switch(ds);
if (ret)
goto err_remove_irq;
@@ -846,6 +1093,13 @@ static int ar9331_sw_probe(struct mdio_device *mdiodev)
static void ar9331_sw_remove(struct mdio_device *mdiodev)
{
struct ar9331_sw_priv *priv = dev_get_drvdata(&mdiodev->dev);
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(priv->port); i++) {
+ struct ar9331_sw_port *port = &priv->port[i];
+
+ cancel_delayed_work_sync(&port->mib_read);
+ }

irq_domain_remove(priv->irqdomain);
mdiobus_unregister(priv->mbus);
--
2.29.2