| // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
| /* Statistics for Ocelot switch family |
| * |
| * Copyright (c) 2017 Microsemi Corporation |
| * Copyright 2022 NXP |
| */ |
| #include <linux/spinlock.h> |
| #include <linux/mutex.h> |
| #include <linux/workqueue.h> |
| #include "ocelot.h" |
| |
| /* Read the counters from hardware and keep them in region->buf. |
| * Caller must hold &ocelot->stat_view_lock. |
| */ |
| static int ocelot_port_update_stats(struct ocelot *ocelot, int port) |
| { |
| struct ocelot_stats_region *region; |
| int err; |
| |
| /* Configure the port to read the stats from */ |
| ocelot_write(ocelot, SYS_STAT_CFG_STAT_VIEW(port), SYS_STAT_CFG); |
| |
| list_for_each_entry(region, &ocelot->stats_regions, node) { |
| err = ocelot_bulk_read(ocelot, region->base, region->buf, |
| region->count); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| /* Transfer the counters from region->buf to ocelot->stats. |
| * Caller must hold &ocelot->stat_view_lock and &ocelot->stats_lock. |
| */ |
| static void ocelot_port_transfer_stats(struct ocelot *ocelot, int port) |
| { |
| unsigned int idx = port * OCELOT_NUM_STATS; |
| struct ocelot_stats_region *region; |
| int j; |
| |
| list_for_each_entry(region, &ocelot->stats_regions, node) { |
| for (j = 0; j < region->count; j++) { |
| u64 *stat = &ocelot->stats[idx + j]; |
| u64 val = region->buf[j]; |
| |
| if (val < (*stat & U32_MAX)) |
| *stat += (u64)1 << 32; |
| |
| *stat = (*stat & ~(u64)U32_MAX) + val; |
| } |
| |
| idx += region->count; |
| } |
| } |
| |
| static void ocelot_check_stats_work(struct work_struct *work) |
| { |
| struct delayed_work *del_work = to_delayed_work(work); |
| struct ocelot *ocelot = container_of(del_work, struct ocelot, |
| stats_work); |
| int port, err; |
| |
| mutex_lock(&ocelot->stat_view_lock); |
| |
| for (port = 0; port < ocelot->num_phys_ports; port++) { |
| err = ocelot_port_update_stats(ocelot, port); |
| if (err) |
| break; |
| |
| spin_lock(&ocelot->stats_lock); |
| ocelot_port_transfer_stats(ocelot, port); |
| spin_unlock(&ocelot->stats_lock); |
| } |
| |
| if (!err && ocelot->ops->update_stats) |
| ocelot->ops->update_stats(ocelot); |
| |
| mutex_unlock(&ocelot->stat_view_lock); |
| |
| if (err) |
| dev_err(ocelot->dev, "Error %d updating ethtool stats\n", err); |
| |
| queue_delayed_work(ocelot->stats_queue, &ocelot->stats_work, |
| OCELOT_STATS_CHECK_DELAY); |
| } |
| |
| void ocelot_get_strings(struct ocelot *ocelot, int port, u32 sset, u8 *data) |
| { |
| int i; |
| |
| if (sset != ETH_SS_STATS) |
| return; |
| |
| for (i = 0; i < OCELOT_NUM_STATS; i++) { |
| if (ocelot->stats_layout[i].name[0] == '\0') |
| continue; |
| |
| memcpy(data + i * ETH_GSTRING_LEN, ocelot->stats_layout[i].name, |
| ETH_GSTRING_LEN); |
| } |
| } |
| EXPORT_SYMBOL(ocelot_get_strings); |
| |
| /* Update ocelot->stats for the given port and run the given callback */ |
| static void ocelot_port_stats_run(struct ocelot *ocelot, int port, void *priv, |
| void (*cb)(struct ocelot *ocelot, int port, |
| void *priv)) |
| { |
| int err; |
| |
| mutex_lock(&ocelot->stat_view_lock); |
| |
| err = ocelot_port_update_stats(ocelot, port); |
| if (err) { |
| dev_err(ocelot->dev, "Failed to update port %d stats: %pe\n", |
| port, ERR_PTR(err)); |
| goto out_unlock; |
| } |
| |
| spin_lock(&ocelot->stats_lock); |
| |
| ocelot_port_transfer_stats(ocelot, port); |
| cb(ocelot, port, priv); |
| |
| spin_unlock(&ocelot->stats_lock); |
| |
| out_unlock: |
| mutex_unlock(&ocelot->stat_view_lock); |
| } |
| |
| int ocelot_get_sset_count(struct ocelot *ocelot, int port, int sset) |
| { |
| int i, num_stats = 0; |
| |
| if (sset != ETH_SS_STATS) |
| return -EOPNOTSUPP; |
| |
| for (i = 0; i < OCELOT_NUM_STATS; i++) |
| if (ocelot->stats_layout[i].name[0] != '\0') |
| num_stats++; |
| |
| return num_stats; |
| } |
| EXPORT_SYMBOL(ocelot_get_sset_count); |
| |
| static void ocelot_port_ethtool_stats_cb(struct ocelot *ocelot, int port, |
| void *priv) |
| { |
| u64 *data = priv; |
| int i; |
| |
| /* Copy all supported counters */ |
| for (i = 0; i < OCELOT_NUM_STATS; i++) { |
| int index = port * OCELOT_NUM_STATS + i; |
| |
| if (ocelot->stats_layout[i].name[0] == '\0') |
| continue; |
| |
| *data++ = ocelot->stats[index]; |
| } |
| } |
| |
| void ocelot_get_ethtool_stats(struct ocelot *ocelot, int port, u64 *data) |
| { |
| ocelot_port_stats_run(ocelot, port, data, ocelot_port_ethtool_stats_cb); |
| } |
| EXPORT_SYMBOL(ocelot_get_ethtool_stats); |
| |
| static void ocelot_port_pause_stats_cb(struct ocelot *ocelot, int port, void *priv) |
| { |
| u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS]; |
| struct ethtool_pause_stats *pause_stats = priv; |
| |
| pause_stats->tx_pause_frames = s[OCELOT_STAT_TX_PAUSE]; |
| pause_stats->rx_pause_frames = s[OCELOT_STAT_RX_PAUSE]; |
| } |
| |
| void ocelot_port_get_pause_stats(struct ocelot *ocelot, int port, |
| struct ethtool_pause_stats *pause_stats) |
| { |
| ocelot_port_stats_run(ocelot, port, pause_stats, |
| ocelot_port_pause_stats_cb); |
| } |
| EXPORT_SYMBOL_GPL(ocelot_port_get_pause_stats); |
| |
| static const struct ethtool_rmon_hist_range ocelot_rmon_ranges[] = { |
| { 64, 64 }, |
| { 65, 127 }, |
| { 128, 255 }, |
| { 256, 511 }, |
| { 512, 1023 }, |
| { 1024, 1526 }, |
| { 1527, 65535 }, |
| {}, |
| }; |
| |
| static void ocelot_port_rmon_stats_cb(struct ocelot *ocelot, int port, void *priv) |
| { |
| u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS]; |
| struct ethtool_rmon_stats *rmon_stats = priv; |
| |
| rmon_stats->undersize_pkts = s[OCELOT_STAT_RX_SHORTS]; |
| rmon_stats->oversize_pkts = s[OCELOT_STAT_RX_LONGS]; |
| rmon_stats->fragments = s[OCELOT_STAT_RX_FRAGMENTS]; |
| rmon_stats->jabbers = s[OCELOT_STAT_RX_JABBERS]; |
| |
| rmon_stats->hist[0] = s[OCELOT_STAT_RX_64]; |
| rmon_stats->hist[1] = s[OCELOT_STAT_RX_65_127]; |
| rmon_stats->hist[2] = s[OCELOT_STAT_RX_128_255]; |
| rmon_stats->hist[3] = s[OCELOT_STAT_RX_256_511]; |
| rmon_stats->hist[4] = s[OCELOT_STAT_RX_512_1023]; |
| rmon_stats->hist[5] = s[OCELOT_STAT_RX_1024_1526]; |
| rmon_stats->hist[6] = s[OCELOT_STAT_RX_1527_MAX]; |
| |
| rmon_stats->hist_tx[0] = s[OCELOT_STAT_TX_64]; |
| rmon_stats->hist_tx[1] = s[OCELOT_STAT_TX_65_127]; |
| rmon_stats->hist_tx[2] = s[OCELOT_STAT_TX_128_255]; |
| rmon_stats->hist_tx[3] = s[OCELOT_STAT_TX_128_255]; |
| rmon_stats->hist_tx[4] = s[OCELOT_STAT_TX_256_511]; |
| rmon_stats->hist_tx[5] = s[OCELOT_STAT_TX_512_1023]; |
| rmon_stats->hist_tx[6] = s[OCELOT_STAT_TX_1024_1526]; |
| } |
| |
| void ocelot_port_get_rmon_stats(struct ocelot *ocelot, int port, |
| struct ethtool_rmon_stats *rmon_stats, |
| const struct ethtool_rmon_hist_range **ranges) |
| { |
| *ranges = ocelot_rmon_ranges; |
| |
| ocelot_port_stats_run(ocelot, port, rmon_stats, |
| ocelot_port_rmon_stats_cb); |
| } |
| EXPORT_SYMBOL_GPL(ocelot_port_get_rmon_stats); |
| |
| static void ocelot_port_ctrl_stats_cb(struct ocelot *ocelot, int port, void *priv) |
| { |
| u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS]; |
| struct ethtool_eth_ctrl_stats *ctrl_stats = priv; |
| |
| ctrl_stats->MACControlFramesReceived = s[OCELOT_STAT_RX_CONTROL]; |
| } |
| |
| void ocelot_port_get_eth_ctrl_stats(struct ocelot *ocelot, int port, |
| struct ethtool_eth_ctrl_stats *ctrl_stats) |
| { |
| ocelot_port_stats_run(ocelot, port, ctrl_stats, |
| ocelot_port_ctrl_stats_cb); |
| } |
| EXPORT_SYMBOL_GPL(ocelot_port_get_eth_ctrl_stats); |
| |
| static void ocelot_port_mac_stats_cb(struct ocelot *ocelot, int port, void *priv) |
| { |
| u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS]; |
| struct ethtool_eth_mac_stats *mac_stats = priv; |
| |
| mac_stats->OctetsTransmittedOK = s[OCELOT_STAT_TX_OCTETS]; |
| mac_stats->FramesTransmittedOK = s[OCELOT_STAT_TX_64] + |
| s[OCELOT_STAT_TX_65_127] + |
| s[OCELOT_STAT_TX_128_255] + |
| s[OCELOT_STAT_TX_256_511] + |
| s[OCELOT_STAT_TX_512_1023] + |
| s[OCELOT_STAT_TX_1024_1526] + |
| s[OCELOT_STAT_TX_1527_MAX]; |
| mac_stats->OctetsReceivedOK = s[OCELOT_STAT_RX_OCTETS]; |
| mac_stats->FramesReceivedOK = s[OCELOT_STAT_RX_GREEN_PRIO_0] + |
| s[OCELOT_STAT_RX_GREEN_PRIO_1] + |
| s[OCELOT_STAT_RX_GREEN_PRIO_2] + |
| s[OCELOT_STAT_RX_GREEN_PRIO_3] + |
| s[OCELOT_STAT_RX_GREEN_PRIO_4] + |
| s[OCELOT_STAT_RX_GREEN_PRIO_5] + |
| s[OCELOT_STAT_RX_GREEN_PRIO_6] + |
| s[OCELOT_STAT_RX_GREEN_PRIO_7] + |
| s[OCELOT_STAT_RX_YELLOW_PRIO_0] + |
| s[OCELOT_STAT_RX_YELLOW_PRIO_1] + |
| s[OCELOT_STAT_RX_YELLOW_PRIO_2] + |
| s[OCELOT_STAT_RX_YELLOW_PRIO_3] + |
| s[OCELOT_STAT_RX_YELLOW_PRIO_4] + |
| s[OCELOT_STAT_RX_YELLOW_PRIO_5] + |
| s[OCELOT_STAT_RX_YELLOW_PRIO_6] + |
| s[OCELOT_STAT_RX_YELLOW_PRIO_7]; |
| mac_stats->MulticastFramesXmittedOK = s[OCELOT_STAT_TX_MULTICAST]; |
| mac_stats->BroadcastFramesXmittedOK = s[OCELOT_STAT_TX_BROADCAST]; |
| mac_stats->MulticastFramesReceivedOK = s[OCELOT_STAT_RX_MULTICAST]; |
| mac_stats->BroadcastFramesReceivedOK = s[OCELOT_STAT_RX_BROADCAST]; |
| mac_stats->FrameTooLongErrors = s[OCELOT_STAT_RX_LONGS]; |
| /* Sadly, C_RX_CRC is the sum of FCS and alignment errors, they are not |
| * counted individually. |
| */ |
| mac_stats->FrameCheckSequenceErrors = s[OCELOT_STAT_RX_CRC_ALIGN_ERRS]; |
| mac_stats->AlignmentErrors = s[OCELOT_STAT_RX_CRC_ALIGN_ERRS]; |
| } |
| |
| void ocelot_port_get_eth_mac_stats(struct ocelot *ocelot, int port, |
| struct ethtool_eth_mac_stats *mac_stats) |
| { |
| ocelot_port_stats_run(ocelot, port, mac_stats, |
| ocelot_port_mac_stats_cb); |
| } |
| EXPORT_SYMBOL_GPL(ocelot_port_get_eth_mac_stats); |
| |
| static void ocelot_port_phy_stats_cb(struct ocelot *ocelot, int port, void *priv) |
| { |
| u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS]; |
| struct ethtool_eth_phy_stats *phy_stats = priv; |
| |
| phy_stats->SymbolErrorDuringCarrier = s[OCELOT_STAT_RX_SYM_ERRS]; |
| } |
| |
| void ocelot_port_get_eth_phy_stats(struct ocelot *ocelot, int port, |
| struct ethtool_eth_phy_stats *phy_stats) |
| { |
| ocelot_port_stats_run(ocelot, port, phy_stats, |
| ocelot_port_phy_stats_cb); |
| } |
| EXPORT_SYMBOL_GPL(ocelot_port_get_eth_phy_stats); |
| |
| void ocelot_port_get_stats64(struct ocelot *ocelot, int port, |
| struct rtnl_link_stats64 *stats) |
| { |
| u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS]; |
| |
| spin_lock(&ocelot->stats_lock); |
| |
| /* Get Rx stats */ |
| stats->rx_bytes = s[OCELOT_STAT_RX_OCTETS]; |
| stats->rx_packets = s[OCELOT_STAT_RX_SHORTS] + |
| s[OCELOT_STAT_RX_FRAGMENTS] + |
| s[OCELOT_STAT_RX_JABBERS] + |
| s[OCELOT_STAT_RX_LONGS] + |
| s[OCELOT_STAT_RX_64] + |
| s[OCELOT_STAT_RX_65_127] + |
| s[OCELOT_STAT_RX_128_255] + |
| s[OCELOT_STAT_RX_256_511] + |
| s[OCELOT_STAT_RX_512_1023] + |
| s[OCELOT_STAT_RX_1024_1526] + |
| s[OCELOT_STAT_RX_1527_MAX]; |
| stats->multicast = s[OCELOT_STAT_RX_MULTICAST]; |
| stats->rx_missed_errors = s[OCELOT_STAT_DROP_TAIL]; |
| stats->rx_dropped = s[OCELOT_STAT_RX_RED_PRIO_0] + |
| s[OCELOT_STAT_RX_RED_PRIO_1] + |
| s[OCELOT_STAT_RX_RED_PRIO_2] + |
| s[OCELOT_STAT_RX_RED_PRIO_3] + |
| s[OCELOT_STAT_RX_RED_PRIO_4] + |
| s[OCELOT_STAT_RX_RED_PRIO_5] + |
| s[OCELOT_STAT_RX_RED_PRIO_6] + |
| s[OCELOT_STAT_RX_RED_PRIO_7] + |
| s[OCELOT_STAT_DROP_LOCAL] + |
| s[OCELOT_STAT_DROP_YELLOW_PRIO_0] + |
| s[OCELOT_STAT_DROP_YELLOW_PRIO_1] + |
| s[OCELOT_STAT_DROP_YELLOW_PRIO_2] + |
| s[OCELOT_STAT_DROP_YELLOW_PRIO_3] + |
| s[OCELOT_STAT_DROP_YELLOW_PRIO_4] + |
| s[OCELOT_STAT_DROP_YELLOW_PRIO_5] + |
| s[OCELOT_STAT_DROP_YELLOW_PRIO_6] + |
| s[OCELOT_STAT_DROP_YELLOW_PRIO_7] + |
| s[OCELOT_STAT_DROP_GREEN_PRIO_0] + |
| s[OCELOT_STAT_DROP_GREEN_PRIO_1] + |
| s[OCELOT_STAT_DROP_GREEN_PRIO_2] + |
| s[OCELOT_STAT_DROP_GREEN_PRIO_3] + |
| s[OCELOT_STAT_DROP_GREEN_PRIO_4] + |
| s[OCELOT_STAT_DROP_GREEN_PRIO_5] + |
| s[OCELOT_STAT_DROP_GREEN_PRIO_6] + |
| s[OCELOT_STAT_DROP_GREEN_PRIO_7]; |
| |
| /* Get Tx stats */ |
| stats->tx_bytes = s[OCELOT_STAT_TX_OCTETS]; |
| stats->tx_packets = s[OCELOT_STAT_TX_64] + |
| s[OCELOT_STAT_TX_65_127] + |
| s[OCELOT_STAT_TX_128_255] + |
| s[OCELOT_STAT_TX_256_511] + |
| s[OCELOT_STAT_TX_512_1023] + |
| s[OCELOT_STAT_TX_1024_1526] + |
| s[OCELOT_STAT_TX_1527_MAX]; |
| stats->tx_dropped = s[OCELOT_STAT_TX_DROPS] + |
| s[OCELOT_STAT_TX_AGED]; |
| stats->collisions = s[OCELOT_STAT_TX_COLLISION]; |
| |
| spin_unlock(&ocelot->stats_lock); |
| } |
| EXPORT_SYMBOL(ocelot_port_get_stats64); |
| |
| static int ocelot_prepare_stats_regions(struct ocelot *ocelot) |
| { |
| struct ocelot_stats_region *region = NULL; |
| unsigned int last; |
| int i; |
| |
| INIT_LIST_HEAD(&ocelot->stats_regions); |
| |
| for (i = 0; i < OCELOT_NUM_STATS; i++) { |
| if (!ocelot->stats_layout[i].reg) |
| continue; |
| |
| if (region && ocelot->stats_layout[i].reg == last + 4) { |
| region->count++; |
| } else { |
| region = devm_kzalloc(ocelot->dev, sizeof(*region), |
| GFP_KERNEL); |
| if (!region) |
| return -ENOMEM; |
| |
| region->base = ocelot->stats_layout[i].reg; |
| region->count = 1; |
| list_add_tail(®ion->node, &ocelot->stats_regions); |
| } |
| |
| last = ocelot->stats_layout[i].reg; |
| } |
| |
| list_for_each_entry(region, &ocelot->stats_regions, node) { |
| region->buf = devm_kcalloc(ocelot->dev, region->count, |
| sizeof(*region->buf), GFP_KERNEL); |
| if (!region->buf) |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| int ocelot_stats_init(struct ocelot *ocelot) |
| { |
| char queue_name[32]; |
| int ret; |
| |
| ocelot->stats = devm_kcalloc(ocelot->dev, |
| ocelot->num_phys_ports * OCELOT_NUM_STATS, |
| sizeof(u64), GFP_KERNEL); |
| if (!ocelot->stats) |
| return -ENOMEM; |
| |
| snprintf(queue_name, sizeof(queue_name), "%s-stats", |
| dev_name(ocelot->dev)); |
| ocelot->stats_queue = create_singlethread_workqueue(queue_name); |
| if (!ocelot->stats_queue) |
| return -ENOMEM; |
| |
| spin_lock_init(&ocelot->stats_lock); |
| mutex_init(&ocelot->stat_view_lock); |
| |
| ret = ocelot_prepare_stats_regions(ocelot); |
| if (ret) { |
| destroy_workqueue(ocelot->stats_queue); |
| return ret; |
| } |
| |
| INIT_DELAYED_WORK(&ocelot->stats_work, ocelot_check_stats_work); |
| queue_delayed_work(ocelot->stats_queue, &ocelot->stats_work, |
| OCELOT_STATS_CHECK_DELAY); |
| |
| return 0; |
| } |
| |
| void ocelot_stats_deinit(struct ocelot *ocelot) |
| { |
| cancel_delayed_work(&ocelot->stats_work); |
| destroy_workqueue(ocelot->stats_queue); |
| } |