From 7678e69079c10b2fb10977f28f44ddb22971ea5b Mon Sep 17 00:00:00 2001 From: Brett Creeley Date: Sun, 14 Jun 2026 13:52:59 -0700 Subject: [PATCH 1/4] ionic: Fix check in ionic_get_link_ext_stats The current check will fail if SR-IOV is not initialized for the physical function; this is because is_physfn is 0 if sriov_init() isn't run or fails. Change the check that prevents getting the link down count to use is_virtfn instead so that VFs don't get this functionality, which was the original intent. Fixes: 132b4ebfa090 ("ionic: add support for ethtool extended stat link_down_count") Signed-off-by: Brett Creeley Signed-off-by: Eric Joyner Link: https://patch.msgid.link/20260614205303.48088-2-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/pensando/ionic/ionic_ethtool.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c index 78a802eb159f..6069fa460913 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c @@ -116,8 +116,15 @@ static void ionic_get_link_ext_stats(struct net_device *netdev, { struct ionic_lif *lif = netdev_priv(netdev); - if (lif->ionic->pdev->is_physfn) - stats->link_down_events = lif->link_down_count; + if (lif->ionic->pdev->is_virtfn) + return; + + if (!lif->ionic->idev.port_info) { + netdev_err_once(netdev, "port_info not initialized\n"); + return; + } + + stats->link_down_events = lif->link_down_count; } static int ionic_get_link_ksettings(struct net_device *netdev, From 433bc500814916133db94e55f096ccc01e5faa30 Mon Sep 17 00:00:00 2001 From: Eric Joyner Date: Sun, 14 Jun 2026 13:53:00 -0700 Subject: [PATCH 2/4] ionic: Update ionic_if.h with new extra port stats Add a new structure to report additional statistics from the firmware to struct ionic_port_info. This new struct currently only contains FEC related statistics, but any new port-level statistics collected by the firmware would go into it. The new structure is located in the same area as the unused ionic_port_pb_stats structure, so this patch also removes that and its supporting enumerations since they was never used in this driver. Finally, to indicate firmware support for the new structure, introduce a new device capability that the driver can use to see if the attached device supports reporting these extra stats. Signed-off-by: Eric Joyner Link: https://patch.msgid.link/20260614205303.48088-3-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/pensando/ionic/ionic_if.h | 64 ++++--------------- 1 file changed, 11 insertions(+), 53 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_if.h b/drivers/net/ethernet/pensando/ionic/ionic_if.h index 23d6e2b4791e..0a201422d0c5 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_if.h +++ b/drivers/net/ethernet/pensando/ionic/ionic_if.h @@ -273,10 +273,12 @@ union ionic_drv_identity { * enum ionic_dev_capability - Device capabilities * @IONIC_DEV_CAP_VF_CTRL: Device supports VF ctrl operations * @IONIC_DEV_CAP_DISC_CMB: Device supports CMB discovery operations + * @IONIC_DEV_CAP_EXTRA_STATS: Device supports extra stats schema */ enum ionic_dev_capability { IONIC_DEV_CAP_VF_CTRL = BIT(0), IONIC_DEV_CAP_DISC_CMB = BIT(1), + IONIC_DEV_CAP_EXTRA_STATS = BIT(4), }; /** @@ -2329,7 +2331,7 @@ struct ionic_qos_identify_comp { /* Capri max supported, should be renamed. */ #define IONIC_QOS_CLASS_MAX 7 #define IONIC_QOS_PCP_MAX 8 -#define IONIC_QOS_CLASS_NAME_SZ 32 +#define IONIC_QOS_CLASS_NAME_SZ 32 #define IONIC_QOS_DSCP_MAX 64 #define IONIC_QOS_ALL_PCP 0xFF #define IONIC_DSCP_BLOCK_SIZE 8 @@ -2855,54 +2857,12 @@ struct ionic_mgmt_port_stats { __le64 frames_tx_pause; }; -enum ionic_pb_buffer_drop_stats { - IONIC_BUFFER_INTRINSIC_DROP = 0, - IONIC_BUFFER_DISCARDED, - IONIC_BUFFER_ADMITTED, - IONIC_BUFFER_OUT_OF_CELLS_DROP, - IONIC_BUFFER_OUT_OF_CELLS_DROP_2, - IONIC_BUFFER_OUT_OF_CREDIT_DROP, - IONIC_BUFFER_TRUNCATION_DROP, - IONIC_BUFFER_PORT_DISABLED_DROP, - IONIC_BUFFER_COPY_TO_CPU_TAIL_DROP, - IONIC_BUFFER_SPAN_TAIL_DROP, - IONIC_BUFFER_MIN_SIZE_VIOLATION_DROP, - IONIC_BUFFER_ENQUEUE_ERROR_DROP, - IONIC_BUFFER_INVALID_PORT_DROP, - IONIC_BUFFER_INVALID_OUTPUT_QUEUE_DROP, - IONIC_BUFFER_DROP_MAX, -}; - -enum ionic_oflow_drop_stats { - IONIC_OFLOW_OCCUPANCY_DROP, - IONIC_OFLOW_EMERGENCY_STOP_DROP, - IONIC_OFLOW_WRITE_BUFFER_ACK_FILL_UP_DROP, - IONIC_OFLOW_WRITE_BUFFER_ACK_FULL_DROP, - IONIC_OFLOW_WRITE_BUFFER_FULL_DROP, - IONIC_OFLOW_CONTROL_FIFO_FULL_DROP, - IONIC_OFLOW_DROP_MAX, -}; - -/* struct ionic_port_pb_stats - packet buffers system stats - * uses ionic_pb_buffer_drop_stats for drop_counts[] - */ -struct ionic_port_pb_stats { - __le64 sop_count_in; - __le64 eop_count_in; - __le64 sop_count_out; - __le64 eop_count_out; - __le64 drop_counts[IONIC_BUFFER_DROP_MAX]; - __le64 input_queue_buffer_occupancy[IONIC_QOS_TC_MAX]; - __le64 input_queue_port_monitor[IONIC_QOS_TC_MAX]; - __le64 output_queue_port_monitor[IONIC_QOS_TC_MAX]; - __le64 oflow_drop_counts[IONIC_OFLOW_DROP_MAX]; - __le64 input_queue_good_pkts_in[IONIC_QOS_TC_MAX]; - __le64 input_queue_good_pkts_out[IONIC_QOS_TC_MAX]; - __le64 input_queue_err_pkts_in[IONIC_QOS_TC_MAX]; - __le64 input_queue_fifo_depth[IONIC_QOS_TC_MAX]; - __le64 input_queue_max_fifo_depth[IONIC_QOS_TC_MAX]; - __le64 input_queue_peak_occupancy[IONIC_QOS_TC_MAX]; - __le64 output_queue_buffer_occupancy[IONIC_QOS_TC_MAX]; +struct ionic_port_extra_stats { + __le64 rsfec_correctable_blocks; + __le64 rsfec_uncorrectable_blocks; + __le64 fec_corrected_bits_total; + __le64 rx_bits_phy; + __le64 fec_codeword_error_bin[16]; }; /** @@ -2950,7 +2910,7 @@ union ionic_port_identity { * @sprom_page2: Extended Transceiver sprom, page 2 * @sprom_page17: Extended Transceiver sprom, page 17 * @rsvd: reserved byte(s) - * @pb_stats: uplink pb drop stats + * @extra_stats: Extra port statistics data */ struct ionic_port_info { union ionic_port_config config; @@ -2968,9 +2928,7 @@ struct ionic_port_info { }; }; u8 rsvd[376]; - - /* pb_stats must start at 2k offset */ - struct ionic_port_pb_stats pb_stats; + struct ionic_port_extra_stats extra_stats; }; /* From cb683ff6ee07ab92701cc45ae21373c2b0c8d481 Mon Sep 17 00:00:00 2001 From: Eric Joyner Date: Sun, 14 Jun 2026 13:53:01 -0700 Subject: [PATCH 3/4] ionic: Report "rx_bits_phy" stat to ethtool This stat contains the number of total bits that the PHY has received; it's useful for BER calculations. Add it to the ethtool stats output. However, since this is one of the new "extra port stats", it's reported in a different manner than the existing port stats and only conditionally added to the ethtool stats output list: both the DEV_CAP_EXTRA_STATS capability must be supported by the firmware, and the firmware must set the value of the statistic to something other than IONIC_STAT_INVALID. To help support this scheme, the extra port stats region is initialized to 0xff's/IONIC_STAT_INVALID by the driver, to ensure the statistics that the driver knows about but the firmware does not are still invalid to the driver. Signed-off-by: Eric Joyner Link: https://patch.msgid.link/20260614205303.48088-4-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/pensando/ionic/ionic_dev.h | 1 + .../net/ethernet/pensando/ionic/ionic_main.c | 6 ++ .../net/ethernet/pensando/ionic/ionic_stats.c | 65 ++++++++++++++++++- .../net/ethernet/pensando/ionic/ionic_stats.h | 2 + 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_dev.h b/drivers/net/ethernet/pensando/ionic/ionic_dev.h index 35566f97eaea..5f677bcbaf02 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_dev.h +++ b/drivers/net/ethernet/pensando/ionic/ionic_dev.h @@ -184,6 +184,7 @@ struct ionic_dev { u32 port_info_sz; struct ionic_port_info *port_info; dma_addr_t port_info_pa; + struct ionic_port_extra_stats port_extra_stats_cache; struct ionic_devinfo dev_info; }; diff --git a/drivers/net/ethernet/pensando/ionic/ionic_main.c b/drivers/net/ethernet/pensando/ionic/ionic_main.c index 3c5200e2fdb7..306d9d160e17 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_main.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_main.c @@ -731,6 +731,12 @@ int ionic_port_init(struct ionic *ionic) return -ENOMEM; } + /* If the driver knows about more "extra stats" than the firmware, + * make sure these stats are marked as invalid. + */ + memset(&idev->port_info->extra_stats, 0xff, + sizeof(idev->port_info->extra_stats)); + sz = min(sizeof(ident->port.config), sizeof(idev->dev_cmd_regs->data)); mutex_lock(&ionic->dev_cmd_lock); diff --git a/drivers/net/ethernet/pensando/ionic/ionic_stats.c b/drivers/net/ethernet/pensando/ionic/ionic_stats.c index 0107599a9dd4..428d5cca930f 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_stats.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_stats.c @@ -167,6 +167,7 @@ static const struct ionic_stat_desc ionic_rx_stats_desc[] = { #define IONIC_NUM_PORT_STATS ARRAY_SIZE(ionic_port_stats_desc) #define IONIC_NUM_TX_STATS ARRAY_SIZE(ionic_tx_stats_desc) #define IONIC_NUM_RX_STATS ARRAY_SIZE(ionic_rx_stats_desc) +#define IONIC_NUM_EXTRA_PORT_STATS 1 #define MAX_Q(lif) ((lif)->netdev->real_num_tx_queues) @@ -232,6 +233,33 @@ static void ionic_get_lif_stats(struct ionic_lif *lif, stats->hw_tx_aborted_errors = ns.tx_aborted_errors; } +static u32 ionic_extra_port_stats_get_count(struct ionic_lif *lif) +{ + struct ionic_dev *idev = &lif->ionic->idev; + struct ionic_port_extra_stats *pes_cache; + u32 count = 0; + + if (!(lif->ionic->ident.dev.capabilities & + cpu_to_le64(IONIC_DEV_CAP_EXTRA_STATS))) + return count; + + pes_cache = &idev->port_extra_stats_cache; + /* Treat all of the extra port stats as invalid in subsequent calls if + * port_info isn't set; otherwise cache a valid snapshot for them. + */ + if (!idev->port_info) { + memset(pes_cache, 0xff, sizeof(*pes_cache)); + return count; + } + + *pes_cache = idev->port_info->extra_stats; + + if (pes_cache->rx_bits_phy != IONIC_STAT_INVALID) + count++; + + return count; +} + static u64 ionic_sw_stats_get_count(struct ionic_lif *lif) { u64 total = 0, tx_queues = MAX_Q(lif), rx_queues = MAX_Q(lif); @@ -243,7 +271,7 @@ static u64 ionic_sw_stats_get_count(struct ionic_lif *lif) rx_queues += 1; total += IONIC_NUM_LIF_STATS; - total += IONIC_NUM_PORT_STATS; + total += IONIC_NUM_PORT_STATS + ionic_extra_port_stats_get_count(lif); total += tx_queues * IONIC_NUM_TX_STATS; total += rx_queues * IONIC_NUM_RX_STATS; @@ -271,6 +299,20 @@ static void ionic_sw_stats_get_rx_strings(struct ionic_lif *lif, u8 **buf, ionic_rx_stats_desc[i].name); } +static void ionic_extra_port_stats_get_strings(struct ionic_lif *lif, u8 **buf) +{ + struct ionic_port_extra_stats *pes_cache; + + if (!(lif->ionic->ident.dev.capabilities & + cpu_to_le64(IONIC_DEV_CAP_EXTRA_STATS))) + return; + + pes_cache = &lif->ionic->idev.port_extra_stats_cache; + + if (pes_cache->rx_bits_phy != IONIC_STAT_INVALID) + ethtool_puts(buf, "rx_bits_phy"); +} + static void ionic_sw_stats_get_strings(struct ionic_lif *lif, u8 **buf) { int i, q_num; @@ -280,6 +322,7 @@ static void ionic_sw_stats_get_strings(struct ionic_lif *lif, u8 **buf) for (i = 0; i < IONIC_NUM_PORT_STATS; i++) ethtool_puts(buf, ionic_port_stats_desc[i].name); + ionic_extra_port_stats_get_strings(lif, buf); for (q_num = 0; q_num < MAX_Q(lif); q_num++) ionic_sw_stats_get_tx_strings(lif, buf, q_num); @@ -322,6 +365,25 @@ static void ionic_sw_stats_get_rxq_values(struct ionic_lif *lif, u64 **buf, } } +static void ionic_extra_port_stats_get_values(struct ionic_lif *lif, u64 **buf) +{ + struct ionic_port_extra_stats *pes_cache; + + if (!(lif->ionic->ident.dev.capabilities & + cpu_to_le64(IONIC_DEV_CAP_EXTRA_STATS))) + return; + + /* The number of statistics added to @buf here must equal + * ionic_extra_port_stats_get_count(). + */ + pes_cache = &lif->ionic->idev.port_extra_stats_cache; + + if (pes_cache->rx_bits_phy != IONIC_STAT_INVALID) { + **buf = le64_to_cpu(pes_cache->rx_bits_phy); + (*buf)++; + } +} + static void ionic_sw_stats_get_values(struct ionic_lif *lif, u64 **buf) { struct ionic_port_stats *port_stats; @@ -341,6 +403,7 @@ static void ionic_sw_stats_get_values(struct ionic_lif *lif, u64 **buf) &ionic_port_stats_desc[i]); (*buf)++; } + ionic_extra_port_stats_get_values(lif, buf); for (q_num = 0; q_num < MAX_Q(lif); q_num++) ionic_sw_stats_get_txq_values(lif, buf, q_num); diff --git a/drivers/net/ethernet/pensando/ionic/ionic_stats.h b/drivers/net/ethernet/pensando/ionic/ionic_stats.h index 2a725834f792..7ed935868e84 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_stats.h +++ b/drivers/net/ethernet/pensando/ionic/ionic_stats.h @@ -4,6 +4,8 @@ #ifndef _IONIC_STATS_H_ #define _IONIC_STATS_H_ +#define IONIC_STAT_INVALID (cpu_to_le64(~0ULL)) + #define IONIC_STAT_TO_OFFSET(type, stat_name) (offsetof(type, stat_name)) #define IONIC_STAT_DESC(type, stat_name) { \ From 3277e605ac01fd11d0a7c6c68c617547ba66c87f Mon Sep 17 00:00:00 2001 From: Eric Joyner Date: Sun, 14 Jun 2026 13:53:02 -0700 Subject: [PATCH 4/4] ionic: Get "link_down_count" ext link stat from firmware The number of times that link has gone down at the port level is tracked by the firmware and sent to the driver via regular DMA writes to an instance of struct ionic_port_status in the driver's memory. This statistic was never reported in favor of a driver-derived stat, but doing it in the driver was never necessary since firmware had been reporting it the whole time. Since it would be more accurate and true to the description of the statistic to get this count at the PHY level, replace the driver-calculated statistic with one derived from the firmware one and remove the driver-calculated one entirely. The stat reported by the ethtool .get_link_ext_stats() handler is normalized to 0 on driver load and any device resets that require the driver to rebuild state while also handling overflows. Signed-off-by: Eric Joyner Link: https://patch.msgid.link/20260614205303.48088-5-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/pensando/ionic/ionic_dev.c | 10 +++++++++ .../net/ethernet/pensando/ionic/ionic_dev.h | 5 +++++ .../ethernet/pensando/ionic/ionic_ethtool.c | 22 ++++++++++++++++--- .../net/ethernet/pensando/ionic/ionic_lif.c | 4 +++- .../net/ethernet/pensando/ionic/ionic_lif.h | 1 - .../net/ethernet/pensando/ionic/ionic_main.c | 2 ++ 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_dev.c b/drivers/net/ethernet/pensando/ionic/ionic_dev.c index 3838c4a70766..648d9d24be85 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_dev.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_dev.c @@ -1076,3 +1076,13 @@ bool ionic_q_is_posted(struct ionic_queue *q, unsigned int pos) return ((pos - tail) & mask) < ((head - tail) & mask); } + +void ionic_reset_link_down_count(struct ionic_dev *idev) +{ + if (!READ_ONCE(idev->link_down_count_init)) { + idev->link_down_count_total = 0; + idev->link_down_count_last = + le16_to_cpu(idev->port_info->status.link_down_count); + WRITE_ONCE(idev->link_down_count_init, true); + } +} diff --git a/drivers/net/ethernet/pensando/ionic/ionic_dev.h b/drivers/net/ethernet/pensando/ionic/ionic_dev.h index 5f677bcbaf02..db90e39a1442 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_dev.h +++ b/drivers/net/ethernet/pensando/ionic/ionic_dev.h @@ -185,6 +185,9 @@ struct ionic_dev { struct ionic_port_info *port_info; dma_addr_t port_info_pa; struct ionic_port_extra_stats port_extra_stats_cache; + bool link_down_count_init; + u16 link_down_count_last; + u32 link_down_count_total; struct ionic_devinfo dev_info; }; @@ -395,4 +398,6 @@ bool ionic_adminq_poke_doorbell(struct ionic_queue *q); bool ionic_txq_poke_doorbell(struct ionic_queue *q); bool ionic_rxq_poke_doorbell(struct ionic_queue *q); +void ionic_reset_link_down_count(struct ionic_dev *idev); + #endif /* _IONIC_DEV_H_ */ diff --git a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c index 6069fa460913..c4ab4b5caa0a 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c @@ -115,16 +115,32 @@ static void ionic_get_link_ext_stats(struct net_device *netdev, struct ethtool_link_ext_stats *stats) { struct ionic_lif *lif = netdev_priv(netdev); + struct ionic *ionic = lif->ionic; + u64 link_down_count_total; + u16 link_down_count_fw; - if (lif->ionic->pdev->is_virtfn) + if (ionic->pdev->is_virtfn) return; - if (!lif->ionic->idev.port_info) { + if (!ionic->idev.port_info) { netdev_err_once(netdev, "port_info not initialized\n"); return; } - stats->link_down_events = lif->link_down_count; + link_down_count_fw = + le16_to_cpu(ionic->idev.port_info->status.link_down_count); + link_down_count_total = ionic->idev.link_down_count_total + + link_down_count_fw - + ionic->idev.link_down_count_last; + + /* The firmware counter is only 16 bits and can wraparound */ + if (link_down_count_fw < ionic->idev.link_down_count_last) + link_down_count_total += BIT(16); + + ionic->idev.link_down_count_last = link_down_count_fw; + ionic->idev.link_down_count_total = link_down_count_total; + + stats->link_down_events = link_down_count_total; } static int ionic_get_link_ksettings(struct net_device *netdev, diff --git a/drivers/net/ethernet/pensando/ionic/ionic_lif.c b/drivers/net/ethernet/pensando/ionic/ionic_lif.c index 637e635bbf03..fd3ee9820531 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_lif.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_lif.c @@ -140,6 +140,7 @@ void ionic_lif_deferred_enqueue(struct ionic_lif *lif, static void ionic_link_status_check(struct ionic_lif *lif) { + struct ionic_dev *idev = &lif->ionic->idev; struct net_device *netdev = lif->netdev; u16 link_status; bool link_up; @@ -153,6 +154,8 @@ static void ionic_link_status_check(struct ionic_lif *lif) return; } + ionic_reset_link_down_count(idev); + link_status = le16_to_cpu(lif->info->status.link_status); link_up = link_status == IONIC_PORT_OPER_STATUS_UP; @@ -179,7 +182,6 @@ static void ionic_link_status_check(struct ionic_lif *lif) } } else { if (netif_carrier_ok(netdev)) { - lif->link_down_count++; netdev_info(netdev, "Link down\n"); netif_carrier_off(netdev); } diff --git a/drivers/net/ethernet/pensando/ionic/ionic_lif.h b/drivers/net/ethernet/pensando/ionic/ionic_lif.h index 8e10f66dc50e..d34692462036 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_lif.h +++ b/drivers/net/ethernet/pensando/ionic/ionic_lif.h @@ -214,7 +214,6 @@ struct ionic_lif { bool registered; bool doorbell_wa; u16 lif_type; - unsigned int link_down_count; unsigned int nmcast; unsigned int nucast; unsigned int nvlans; diff --git a/drivers/net/ethernet/pensando/ionic/ionic_main.c b/drivers/net/ethernet/pensando/ionic/ionic_main.c index 306d9d160e17..6e6f3ed07271 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_main.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_main.c @@ -737,6 +737,8 @@ int ionic_port_init(struct ionic *ionic) memset(&idev->port_info->extra_stats, 0xff, sizeof(idev->port_info->extra_stats)); + WRITE_ONCE(idev->link_down_count_init, false); + sz = min(sizeof(ident->port.config), sizeof(idev->dev_cmd_regs->data)); mutex_lock(&ionic->dev_cmd_lock);