linux/drivers/net/dsa/sja1105/sja1105_ethtool.c
Vladimir Oltean 039b167d68 net: dsa: sja1105: don't use burst SPI reads for port statistics
The current internal sja1105 driver API is optimized for retrieving many
statistics counters at once. But the switch does not do atomic snapshotting
for them anyway.

In case we start reporting the hardware port counters through
ndo_get_stats64 as well, not just ethtool, it would be good to be able
to read individual port counters and not all of them.

Additionally, since Arnd Bergmann's commit ae1804de93 ("dsa: sja1105:
dynamically allocate stats structure"), sja1105_get_ethtool_stats
allocates memory dynamically, since struct sja1105_port_status was
deemed to consume too much stack memory. That is not ideal.
The large structure is only needed because of the burst read.
If we read statistics one by one, we can consume less memory, and
we can avoid dynamic allocation.

Additionally, latency-sensitive interfaces such as PTP operations (for
phc2sys) might suffer if the SPI mutex is being held for too long, which
happens in the case of SPI burst reads. By reading counters one by one,
we give a chance for higher priority processes to preempt and take the
SPI bus mutex for accessing the PTP clock.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-21 14:01:41 -07:00

630 lines
10 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
*/
#include "sja1105.h"
enum sja1105_counter_index {
__SJA1105_COUNTER_UNUSED,
/* MAC */
N_RUNT,
N_SOFERR,
N_ALIGNERR,
N_MIIERR,
TYPEERR,
SIZEERR,
TCTIMEOUT,
PRIORERR,
NOMASTER,
MEMOV,
MEMERR,
INVTYP,
INTCYOV,
DOMERR,
PCFBAGDROP,
SPCPRIOR,
AGEPRIOR,
PORTDROP,
LENDROP,
BAGDROP,
POLICEERR,
DRPNONA664ERR,
SPCERR,
AGEDRP,
/* HL1 */
N_N664ERR,
N_VLANERR,
N_UNRELEASED,
N_SIZEERR,
N_CRCERR,
N_VLNOTFOUND,
N_CTPOLERR,
N_POLERR,
N_RXFRM,
N_RXBYTE,
N_TXFRM,
N_TXBYTE,
/* HL2 */
N_QFULL,
N_PART_DROP,
N_EGR_DISABLED,
N_NOT_REACH,
__MAX_SJA1105ET_PORT_COUNTER,
/* P/Q/R/S only */
/* ETHER */
N_DROPS_NOLEARN = __MAX_SJA1105ET_PORT_COUNTER,
N_DROPS_NOROUTE,
N_DROPS_ILL_DTAG,
N_DROPS_DTAG,
N_DROPS_SOTAG,
N_DROPS_SITAG,
N_DROPS_UTAG,
N_TX_BYTES_1024_2047,
N_TX_BYTES_512_1023,
N_TX_BYTES_256_511,
N_TX_BYTES_128_255,
N_TX_BYTES_65_127,
N_TX_BYTES_64,
N_TX_MCAST,
N_TX_BCAST,
N_RX_BYTES_1024_2047,
N_RX_BYTES_512_1023,
N_RX_BYTES_256_511,
N_RX_BYTES_128_255,
N_RX_BYTES_65_127,
N_RX_BYTES_64,
N_RX_MCAST,
N_RX_BCAST,
__MAX_SJA1105PQRS_PORT_COUNTER,
};
struct sja1105_port_counter {
enum sja1105_stats_area area;
const char name[ETH_GSTRING_LEN];
int offset;
int start;
int end;
bool is_64bit;
};
static const struct sja1105_port_counter sja1105_port_counters[] = {
/* MAC-Level Diagnostic Counters */
[N_RUNT] = {
.area = MAC,
.name = "n_runt",
.offset = 0,
.start = 31,
.end = 24,
},
[N_SOFERR] = {
.area = MAC,
.name = "n_soferr",
.offset = 0x0,
.start = 23,
.end = 16,
},
[N_ALIGNERR] = {
.area = MAC,
.name = "n_alignerr",
.offset = 0x0,
.start = 15,
.end = 8,
},
[N_MIIERR] = {
.area = MAC,
.name = "n_miierr",
.offset = 0x0,
.start = 7,
.end = 0,
},
/* MAC-Level Diagnostic Flags */
[TYPEERR] = {
.area = MAC,
.name = "typeerr",
.offset = 0x1,
.start = 27,
.end = 27,
},
[SIZEERR] = {
.area = MAC,
.name = "sizeerr",
.offset = 0x1,
.start = 26,
.end = 26,
},
[TCTIMEOUT] = {
.area = MAC,
.name = "tctimeout",
.offset = 0x1,
.start = 25,
.end = 25,
},
[PRIORERR] = {
.area = MAC,
.name = "priorerr",
.offset = 0x1,
.start = 24,
.end = 24,
},
[NOMASTER] = {
.area = MAC,
.name = "nomaster",
.offset = 0x1,
.start = 23,
.end = 23,
},
[MEMOV] = {
.area = MAC,
.name = "memov",
.offset = 0x1,
.start = 22,
.end = 22,
},
[MEMERR] = {
.area = MAC,
.name = "memerr",
.offset = 0x1,
.start = 21,
.end = 21,
},
[INVTYP] = {
.area = MAC,
.name = "invtyp",
.offset = 0x1,
.start = 19,
.end = 19,
},
[INTCYOV] = {
.area = MAC,
.name = "intcyov",
.offset = 0x1,
.start = 18,
.end = 18,
},
[DOMERR] = {
.area = MAC,
.name = "domerr",
.offset = 0x1,
.start = 17,
.end = 17,
},
[PCFBAGDROP] = {
.area = MAC,
.name = "pcfbagdrop",
.offset = 0x1,
.start = 16,
.end = 16,
},
[SPCPRIOR] = {
.area = MAC,
.name = "spcprior",
.offset = 0x1,
.start = 15,
.end = 12,
},
[AGEPRIOR] = {
.area = MAC,
.name = "ageprior",
.offset = 0x1,
.start = 11,
.end = 8,
},
[PORTDROP] = {
.area = MAC,
.name = "portdrop",
.offset = 0x1,
.start = 6,
.end = 6,
},
[LENDROP] = {
.area = MAC,
.name = "lendrop",
.offset = 0x1,
.start = 5,
.end = 5,
},
[BAGDROP] = {
.area = MAC,
.name = "bagdrop",
.offset = 0x1,
.start = 4,
.end = 4,
},
[POLICEERR] = {
.area = MAC,
.name = "policeerr",
.offset = 0x1,
.start = 3,
.end = 3,
},
[DRPNONA664ERR] = {
.area = MAC,
.name = "drpnona664err",
.offset = 0x1,
.start = 2,
.end = 2,
},
[SPCERR] = {
.area = MAC,
.name = "spcerr",
.offset = 0x1,
.start = 1,
.end = 1,
},
[AGEDRP] = {
.area = MAC,
.name = "agedrp",
.offset = 0x1,
.start = 0,
.end = 0,
},
/* High-Level Diagnostic Counters */
[N_N664ERR] = {
.area = HL1,
.name = "n_n664err",
.offset = 0xF,
.start = 31,
.end = 0,
},
[N_VLANERR] = {
.area = HL1,
.name = "n_vlanerr",
.offset = 0xE,
.start = 31,
.end = 0,
},
[N_UNRELEASED] = {
.area = HL1,
.name = "n_unreleased",
.offset = 0xD,
.start = 31,
.end = 0,
},
[N_SIZEERR] = {
.area = HL1,
.name = "n_sizeerr",
.offset = 0xC,
.start = 31,
.end = 0,
},
[N_CRCERR] = {
.area = HL1,
.name = "n_crcerr",
.offset = 0xB,
.start = 31,
.end = 0,
},
[N_VLNOTFOUND] = {
.area = HL1,
.name = "n_vlnotfound",
.offset = 0xA,
.start = 31,
.end = 0,
},
[N_CTPOLERR] = {
.area = HL1,
.name = "n_ctpolerr",
.offset = 0x9,
.start = 31,
.end = 0,
},
[N_POLERR] = {
.area = HL1,
.name = "n_polerr",
.offset = 0x8,
.start = 31,
.end = 0,
},
[N_RXFRM] = {
.area = HL1,
.name = "n_rxfrm",
.offset = 0x6,
.start = 31,
.end = 0,
.is_64bit = true,
},
[N_RXBYTE] = {
.area = HL1,
.name = "n_rxbyte",
.offset = 0x4,
.start = 31,
.end = 0,
.is_64bit = true,
},
[N_TXFRM] = {
.area = HL1,
.name = "n_txfrm",
.offset = 0x2,
.start = 31,
.end = 0,
.is_64bit = true,
},
[N_TXBYTE] = {
.area = HL1,
.name = "n_txbyte",
.offset = 0x0,
.start = 31,
.end = 0,
.is_64bit = true,
},
[N_QFULL] = {
.area = HL2,
.name = "n_qfull",
.offset = 0x3,
.start = 31,
.end = 0,
},
[N_PART_DROP] = {
.area = HL2,
.name = "n_part_drop",
.offset = 0x2,
.start = 31,
.end = 0,
},
[N_EGR_DISABLED] = {
.area = HL2,
.name = "n_egr_disabled",
.offset = 0x1,
.start = 31,
.end = 0,
},
[N_NOT_REACH] = {
.area = HL2,
.name = "n_not_reach",
.offset = 0x0,
.start = 31,
.end = 0,
},
/* Ether Stats */
[N_DROPS_NOLEARN] = {
.area = ETHER,
.name = "n_drops_nolearn",
.offset = 0x16,
.start = 31,
.end = 0,
},
[N_DROPS_NOROUTE] = {
.area = ETHER,
.name = "n_drops_noroute",
.offset = 0x15,
.start = 31,
.end = 0,
},
[N_DROPS_ILL_DTAG] = {
.area = ETHER,
.name = "n_drops_ill_dtag",
.offset = 0x14,
.start = 31,
.end = 0,
},
[N_DROPS_DTAG] = {
.area = ETHER,
.name = "n_drops_dtag",
.offset = 0x13,
.start = 31,
.end = 0,
},
[N_DROPS_SOTAG] = {
.area = ETHER,
.name = "n_drops_sotag",
.offset = 0x12,
.start = 31,
.end = 0,
},
[N_DROPS_SITAG] = {
.area = ETHER,
.name = "n_drops_sitag",
.offset = 0x11,
.start = 31,
.end = 0,
},
[N_DROPS_UTAG] = {
.area = ETHER,
.name = "n_drops_utag",
.offset = 0x10,
.start = 31,
.end = 0,
},
[N_TX_BYTES_1024_2047] = {
.area = ETHER,
.name = "n_tx_bytes_1024_2047",
.offset = 0x0F,
.start = 31,
.end = 0,
},
[N_TX_BYTES_512_1023] = {
.area = ETHER,
.name = "n_tx_bytes_512_1023",
.offset = 0x0E,
.start = 31,
.end = 0,
},
[N_TX_BYTES_256_511] = {
.area = ETHER,
.name = "n_tx_bytes_256_511",
.offset = 0x0D,
.start = 31,
.end = 0,
},
[N_TX_BYTES_128_255] = {
.area = ETHER,
.name = "n_tx_bytes_128_255",
.offset = 0x0C,
.start = 31,
.end = 0,
},
[N_TX_BYTES_65_127] = {
.area = ETHER,
.name = "n_tx_bytes_65_127",
.offset = 0x0B,
.start = 31,
.end = 0,
},
[N_TX_BYTES_64] = {
.area = ETHER,
.name = "n_tx_bytes_64",
.offset = 0x0A,
.start = 31,
.end = 0,
},
[N_TX_MCAST] = {
.area = ETHER,
.name = "n_tx_mcast",
.offset = 0x09,
.start = 31,
.end = 0,
},
[N_TX_BCAST] = {
.area = ETHER,
.name = "n_tx_bcast",
.offset = 0x08,
.start = 31,
.end = 0,
},
[N_RX_BYTES_1024_2047] = {
.area = ETHER,
.name = "n_rx_bytes_1024_2047",
.offset = 0x07,
.start = 31,
.end = 0,
},
[N_RX_BYTES_512_1023] = {
.area = ETHER,
.name = "n_rx_bytes_512_1023",
.offset = 0x06,
.start = 31,
.end = 0,
},
[N_RX_BYTES_256_511] = {
.area = ETHER,
.name = "n_rx_bytes_256_511",
.offset = 0x05,
.start = 31,
.end = 0,
},
[N_RX_BYTES_128_255] = {
.area = ETHER,
.name = "n_rx_bytes_128_255",
.offset = 0x04,
.start = 31,
.end = 0,
},
[N_RX_BYTES_65_127] = {
.area = ETHER,
.name = "n_rx_bytes_65_127",
.offset = 0x03,
.start = 31,
.end = 0,
},
[N_RX_BYTES_64] = {
.area = ETHER,
.name = "n_rx_bytes_64",
.offset = 0x02,
.start = 31,
.end = 0,
},
[N_RX_MCAST] = {
.area = ETHER,
.name = "n_rx_mcast",
.offset = 0x01,
.start = 31,
.end = 0,
},
[N_RX_BCAST] = {
.area = ETHER,
.name = "n_rx_bcast",
.offset = 0x00,
.start = 31,
.end = 0,
},
};
static int sja1105_port_counter_read(struct sja1105_private *priv, int port,
enum sja1105_counter_index idx, u64 *ctr)
{
const struct sja1105_port_counter *c = &sja1105_port_counters[idx];
size_t size = c->is_64bit ? 8 : 4;
u8 buf[8] = {0};
u64 regs;
int rc;
regs = priv->info->regs->stats[c->area][port];
rc = sja1105_xfer_buf(priv, SPI_READ, regs + c->offset, buf, size);
if (rc)
return rc;
sja1105_unpack(buf, ctr, c->start, c->end, size);
return 0;
}
void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data)
{
struct sja1105_private *priv = ds->priv;
enum sja1105_counter_index max_ctr, i;
int rc, k = 0;
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
max_ctr = __MAX_SJA1105ET_PORT_COUNTER;
else
max_ctr = __MAX_SJA1105PQRS_PORT_COUNTER;
for (i = 0; i < max_ctr; i++) {
rc = sja1105_port_counter_read(priv, port, i, &data[k++]);
if (rc) {
dev_err(ds->dev,
"Failed to read port %d counters: %d\n",
port, rc);
break;
}
}
}
void sja1105_get_strings(struct dsa_switch *ds, int port,
u32 stringset, u8 *data)
{
struct sja1105_private *priv = ds->priv;
enum sja1105_counter_index max_ctr, i;
char *p = data;
if (stringset != ETH_SS_STATS)
return;
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
max_ctr = __MAX_SJA1105ET_PORT_COUNTER;
else
max_ctr = __MAX_SJA1105PQRS_PORT_COUNTER;
for (i = 0; i < max_ctr; i++) {
strscpy(p, sja1105_port_counters[i].name, ETH_GSTRING_LEN);
p += ETH_GSTRING_LEN;
}
}
int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
struct sja1105_private *priv = ds->priv;
enum sja1105_counter_index max_ctr, i;
int sset_count = 0;
if (sset != ETH_SS_STATS)
return -EOPNOTSUPP;
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
max_ctr = __MAX_SJA1105ET_PORT_COUNTER;
else
max_ctr = __MAX_SJA1105PQRS_PORT_COUNTER;
for (i = 0; i < max_ctr; i++) {
if (!strlen(sja1105_port_counters[i].name))
continue;
sset_count++;
}
return sset_count;
}