From: Mohammad Shuab Siddique <[email protected]> When a PCIe DMA transaction fails, firmware places the affected function into FUNC_ERROR state and rejects all HWRM commands with HWRM_ERR_CODE_FUNC_RESET_REQUIRED until the driver issues HWRM_FUNC_RESET. There is no async event; the driver discovers the condition as a side effect of its next ordinary HWRM command. This is lighter-weight than Adapter Error Recovery: no core reset, no blackout, other functions unaffected.
Detection is added to bnxt_hwrm_send_message(), which covers every HWRM command the driver issues. Recovery is gated on the new "func-error-support" devarg (default off), since firmware advertises no capability bit for it. When triggered, a deferred callback optionally reads diagnostics via HWRM_FUNC_ERROR_QUERY, issues HWRM_FUNC_RESET, then reuses the existing RESET_NOTIFY reinit chain (bnxt_dev_reset_and_resume) to rebuild all rings and filters. A self-rearming one-second alarm bounds discovery time when the application issues no HWRM traffic of its own. Signed-off-by: Mohammad Shuab Siddique <[email protected]> --- doc/guides/nics/bnxt.rst | 17 ++ doc/guides/rel_notes/release_26_11.rst | 4 + drivers/net/bnxt/bnxt.h | 4 + drivers/net/bnxt/bnxt_ethdev.c | 55 +++++- drivers/net/bnxt/bnxt_func_error.c | 228 +++++++++++++++++++++++++ drivers/net/bnxt/bnxt_func_error.h | 33 ++++ drivers/net/bnxt/bnxt_hwrm.c | 87 ++++++++++ drivers/net/bnxt/bnxt_hwrm.h | 2 + drivers/net/bnxt/bnxt_stats.c | 21 +++ drivers/net/bnxt/bnxt_stats.h | 2 +- drivers/net/bnxt/hsi_struct_def_dpdk.h | 130 ++++++++++++++ drivers/net/bnxt/meson.build | 1 + 12 files changed, 581 insertions(+), 3 deletions(-) create mode 100644 drivers/net/bnxt/bnxt_func_error.c create mode 100644 drivers/net/bnxt/bnxt_func_error.h diff --git a/doc/guides/nics/bnxt.rst b/doc/guides/nics/bnxt.rst index 8c36cec1027..76a63368143 100644 --- a/doc/guides/nics/bnxt.rst +++ b/doc/guides/nics/bnxt.rst @@ -352,6 +352,23 @@ passed to ``rte_eth_tx_queue_setup()``, rounded up to the nearest power of two supported by the hardware (up to 256). A ``wthresh`` of 0 disables Tx completion coalescing. +FUNC_ERROR Recovery +^^^^^^^^^^^^^^^^^^^ + +When a PCIe DMA transaction fails, the firmware places the affected function +into FUNC_ERROR state and rejects HWRM commands until the driver resets the +function. The BNXT PMD can recover from this without a full adapter reset +by resetting the function and re-creating its rings and filters. + +Recovery is disabled by default. It is enabled with the ``func-error-support`` +device argument:: + + -a 0000:82:00.0,func-error-support=1 + +The number of recoveries is reported by the ``func_error_recovery`` extended +statistic, and DMA faults captured during recovery are reflected in +``oerrors``. + Checksum Offload ^^^^^^^^^^^^^^^^ diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst index df476005da0..41d22b80226 100644 --- a/doc/guides/rel_notes/release_26_11.rst +++ b/doc/guides/rel_notes/release_26_11.rst @@ -143,6 +143,10 @@ New Features from 4096/8192 to 16384 each. * Enabled hardware Tx completion coalescing on P7 devices, controlled by the existing ``wthresh`` Tx queue configuration field. + * Added recovery from a firmware-reported FUNC_ERROR/PXP_ERROR condition + (a failed PCIe DMA transaction) without a full adapter reset, + gated behind the new ``func-error-support`` devarg (default off). + Added a ``func_error_recovery`` xstat to report the recovery count. * **Updated Intel iavf driver.** diff --git a/drivers/net/bnxt/bnxt.h b/drivers/net/bnxt/bnxt.h index 8a5c78080e1..2433a39a65a 100644 --- a/drivers/net/bnxt/bnxt.h +++ b/drivers/net/bnxt/bnxt.h @@ -802,6 +802,8 @@ enum bnxt_session_type { #define BNXT_BUF_SPLIT_OFFSET_CAP 1 #define BNXT_BUF_SPLIT_ALIGN_CAP 0 +struct bnxt_func_error_info; + struct bnxt { void *bar0; @@ -816,6 +818,7 @@ struct bnxt { #define BNXT_FLAG_PORT_STATS BIT(2) #define BNXT_FLAG_JUMBO BIT(3) #define BNXT_FLAG_SHORT_CMD BIT(4) +#define BNXT_FLAG_PXP_ERROR BIT(5) #define BNXT_FLAG_PTP_SUPPORTED BIT(6) #define BNXT_FLAG_MULTI_HOST BIT(7) #define BNXT_FLAG_EXT_RX_PORT_STATS BIT(8) @@ -1088,6 +1091,7 @@ struct bnxt { struct tf tfp[BNXT_SESSION_TYPE_LAST]; struct tfc tfcp; struct bnxt_ulp_context *ulp_ctx; + struct bnxt_func_error_info *func_error_info; struct bnxt_flow_stat_info *flow_stat; uint16_t max_num_kflows; uint8_t app_id; diff --git a/drivers/net/bnxt/bnxt_ethdev.c b/drivers/net/bnxt/bnxt_ethdev.c index 23edd944440..55403f68155 100644 --- a/drivers/net/bnxt/bnxt_ethdev.c +++ b/drivers/net/bnxt/bnxt_ethdev.c @@ -18,6 +18,7 @@ #include "bnxt.h" #include "bnxt_filter.h" +#include "bnxt_func_error.h" #include "bnxt_hwrm.h" #include "bnxt_irq.h" #include "bnxt_reps.h" @@ -126,6 +127,7 @@ static const char *const bnxt_dev_args[] = { BNXT_DEVARG_MPC, BNXT_DEVARG_SCALAR_MODE, BNXT_DEVARD_APP_INST_ID, + BNXT_DEVARG_FUNC_ERROR_SUPPORT, NULL }; @@ -1728,6 +1730,7 @@ static int bnxt_dev_stop(struct rte_eth_dev *eth_dev) bnxt_ulp_port_deinit(bp); bnxt_cancel_fw_health_check(bp); + bnxt_cancel_func_error_poll(bp); if (BNXT_P5_PTP_TIMESYNC_ENABLED(bp)) bnxt_cancel_ptp_alarm(bp); @@ -1783,7 +1786,10 @@ int bnxt_dev_stop_op(struct rte_eth_dev *eth_dev) struct bnxt *bp = eth_dev->data->dev_private; pthread_mutex_lock(&bp->err_recovery_lock); - if (bp->flags & BNXT_FLAG_FW_RESET) { + if (bp->flags & BNXT_FLAG_FW_RESET || + (bp->func_error_info != NULL && + rte_atomic_load_explicit(&bp->func_error_info->recovery_pending, + rte_memory_order_acquire))) { PMD_DRV_LOG_LINE(ERR, "Adapter recovering from error..Please retry"); pthread_mutex_unlock(&bp->err_recovery_lock); @@ -1895,6 +1901,7 @@ int bnxt_dev_start_op(struct rte_eth_dev *eth_dev) bnxt_update_max_rx_burst(bp, link); bnxt_schedule_fw_health_check(bp); + bnxt_schedule_func_error_poll(bp); if (BNXT_P5_PTP_TIMESYNC_ENABLED(bp)) bnxt_schedule_ptp_alarm(bp); @@ -1969,7 +1976,10 @@ static int bnxt_dev_close_op(struct rte_eth_dev *eth_dev) return 0; pthread_mutex_lock(&bp->err_recovery_lock); - if (bp->flags & BNXT_FLAG_FW_RESET) { + if (bp->flags & BNXT_FLAG_FW_RESET || + (bp->func_error_info != NULL && + rte_atomic_load_explicit(&bp->func_error_info->recovery_pending, + rte_memory_order_acquire))) { PMD_DRV_LOG_LINE(ERR, "Adapter recovering from error...Please retry"); pthread_mutex_unlock(&bp->err_recovery_lock); @@ -1986,6 +1996,17 @@ static int bnxt_dev_close_op(struct rte_eth_dev *eth_dev) if (eth_dev->data->dev_started) ret = bnxt_dev_stop(eth_dev); + /* Stop FUNC_ERROR detection before the teardown below: a HWRM + * command issued while closing could otherwise schedule a recovery + * that rebuilds resources concurrently with this teardown. Wait for + * any recovery already scheduled or running. + */ + rte_atomic_store_explicit(&bp->func_error_info->support, 0, + rte_memory_order_relaxed); + rte_eal_alarm_cancel(bnxt_func_error_recovery_cb, (void *)bp); + rte_eal_alarm_cancel(bnxt_dev_reset_and_resume, (void *)bp); + rte_eal_alarm_cancel(bnxt_dev_recover, (void *)bp); + bnxt_free_prev_ring_stats_ext(bp); bnxt_free_prev_ring_stats(bp); bnxt_uninit_resources(bp, false); @@ -4664,6 +4685,7 @@ static void bnxt_dev_recover(void *arg) } /* clear reset flag as the device is initialized now */ bp->flags &= ~BNXT_FLAG_FW_RESET; + bnxt_func_error_clear_pending(bp); rc = bnxt_dev_start_op(bp->eth_dev); if (rc) { @@ -4691,6 +4713,13 @@ static void bnxt_dev_recover(void *arg) err_start: bnxt_dev_stop(bp->eth_dev); err: + /* Whatever failed here, don't leave a FUNC_ERROR recovery attempt + * permanently marked in-flight -- this is a terminal state for this + * attempt (BNXT_FLAG_FATAL_ERROR below takes over from here), and a + * stuck pending flag would silently block every future FUNC_ERROR + * trigger on this port. + */ + bnxt_func_error_clear_pending(bp); bp->flags |= BNXT_FLAG_FATAL_ERROR; bnxt_uninit_resources(bp, false); rte_eth_dev_callback_process(bp->eth_dev, @@ -4713,6 +4742,7 @@ void bnxt_dev_reset_and_resume(void *arg) int rc; bnxt_dev_cleanup(bp); + bp->flags &= ~BNXT_FLAG_PXP_ERROR; PMD_DRV_LOG_LINE(INFO, "Port: %u Finished bnxt_dev_cleanup", bp->eth_dev->data->port_id); @@ -4737,6 +4767,14 @@ void bnxt_dev_reset_and_resume(void *arg) } } + /* rte_eal_alarm_set() rejects a zero delay (-EINVAL); fw_reset_min_msecs + * is legitimately 0 for paths with no blackout period (e.g. FUNC_ERROR + * recovery), so clamp to the minimum valid delay instead of skipping + * the alarm -- bnxt_dev_recover() must still run to reinit the port. + */ + if (us == 0) + us = 1; + rc = rte_eal_alarm_set(us, bnxt_dev_recover, (void *)bp); if (rc) PMD_DRV_LOG_LINE(ERR, "Port %u: Error setting recovery alarm", @@ -6438,6 +6476,14 @@ bnxt_parse_dev_args(struct bnxt *bp, struct rte_devargs *devargs) rte_kvargs_process(kvlist, BNXT_DEVARD_APP_INST_ID, bnxt_parse_devarg_app_instance_id, bp); + /* + * Handler for "func-error-support" devarg. + * Invoked as for ex: "-a 0000:00:0d.0,func-error-support=1" + * Opt-in only; default is off. + */ + rte_kvargs_process(kvlist, BNXT_DEVARG_FUNC_ERROR_SUPPORT, + bnxt_parse_devarg_func_error_support, bp); + rte_kvargs_free(kvlist); return ret; } @@ -6562,6 +6608,10 @@ bnxt_dev_init(struct rte_eth_dev *eth_dev, void *params __rte_unused) /* set the default app id */ bp->app_id = bnxt_ulp_default_app_id_get(); + rc = bnxt_func_error_info_alloc(bp); + if (rc) + goto error_free; + /* Parse dev arguments passed on when starting the DPDK application. */ rc = bnxt_parse_dev_args(bp, pci_dev->device.devargs); if (rc) @@ -6684,6 +6734,7 @@ bnxt_uninit_resources(struct bnxt *bp, bool reconfig_dev) bp->mcast_addr_list = NULL; rte_free(bp->rss_conf.rss_key); bp->rss_conf.rss_key = NULL; + bnxt_func_error_info_free(bp); } bnxt_uninit_ctx_mem(bp); diff --git a/drivers/net/bnxt/bnxt_func_error.c b/drivers/net/bnxt/bnxt_func_error.c new file mode 100644 index 00000000000..4dec2579767 --- /dev/null +++ b/drivers/net/bnxt/bnxt_func_error.c @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Broadcom + * All rights reserved. + */ + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> + +#include <rte_alarm.h> +#include <rte_malloc.h> + +#include "bnxt.h" +#include "bnxt_cpr.h" +#include "bnxt_func_error.h" +#include "bnxt_hwrm.h" + +static void bnxt_func_error_poll_cb(void *arg); + +int bnxt_func_error_info_alloc(struct bnxt *bp) +{ + bp->func_error_info = rte_zmalloc("bnxt_func_error_info", + sizeof(struct bnxt_func_error_info), 0); + if (bp->func_error_info == NULL) + return -ENOMEM; + + return 0; +} + +void bnxt_func_error_info_free(struct bnxt *bp) +{ + rte_eal_alarm_cancel(bnxt_func_error_recovery_cb, (void *)bp); + rte_eal_alarm_cancel(bnxt_func_error_poll_cb, (void *)bp); + rte_free(bp->func_error_info); + bp->func_error_info = NULL; +} + +void bnxt_func_error_clear_pending(struct bnxt *bp) +{ + rte_atomic_store_explicit(&bp->func_error_info->recovery_pending, 0, + rte_memory_order_release); +} + +static bool bnxt_func_error_acquire_pending(struct bnxt *bp) +{ + unsigned int expected = 0; + + return rte_atomic_compare_exchange_strong_explicit(&bp->func_error_info->recovery_pending, + &expected, 1, + rte_memory_order_acq_rel, + rte_memory_order_acquire); +} + +void bnxt_func_error_trigger_recovery(struct bnxt *bp) +{ + if (!bnxt_func_error_acquire_pending(bp)) + return; + + PMD_DRV_LOG_LINE(ERR, + "Port %u: HWRM_ERR_CODE_FUNC_RESET_REQUIRED -- function is in " + "FUNC_ERROR state, scheduling recovery", + bp->eth_dev->data->port_id); + + if (rte_eal_alarm_set(US_PER_MS, bnxt_func_error_recovery_cb, (void *)bp)) { + PMD_DRV_LOG_LINE(ERR, + "Port %u: Failed to schedule FUNC_ERROR recovery", + bp->eth_dev->data->port_id); + bnxt_func_error_clear_pending(bp); + } +} + +void bnxt_func_error_recovery_cb(void *arg) +{ + struct bnxt *bp = arg; + int rc; + + pthread_mutex_lock(&bp->err_recovery_lock); + if (bp->flags & BNXT_FLAG_FW_RESET) { + pthread_mutex_unlock(&bp->err_recovery_lock); + bnxt_func_error_clear_pending(bp); + return; + } + bp->flags |= BNXT_FLAG_FW_RESET; + + rte_atomic_fetch_add_explicit(&bp->func_error_info->recovery_count, 1, + rte_memory_order_relaxed); + bp->fw_reset_min_msecs = 0; + bp->fw_reset_max_msecs = BNXT_MAX_FW_RESET_TIMEOUT; + + bnxt_stop_rxtx(bp->eth_dev); + + rc = bnxt_hwrm_func_error_query(bp); + if (rc) + PMD_DRV_LOG_LINE(INFO, + "Port %u: FUNC_ERROR diagnostic query failed rc:%d " + "(no diagnostic data, proceeding with reset anyway)", + bp->eth_dev->data->port_id, rc); + + rc = bnxt_hwrm_func_reset(bp); + if (rc) { + PMD_DRV_LOG_LINE(ERR, + "FUNC_ERROR recovery: HWRM_FUNC_RESET failed"); + bp->flags &= ~BNXT_FLAG_FW_RESET; + pthread_mutex_unlock(&bp->err_recovery_lock); + bnxt_func_error_clear_pending(bp); + return; + } + + /* HWRM_FUNC_RESET just reclaimed every ring/VNIC/stat-ctx for this + * function; bnxt_dev_cleanup()'s own per-resource frees would only + * be rejected as "not allocated". Gate them out until cleanup runs. + */ + bp->flags |= BNXT_FLAG_PXP_ERROR; + pthread_mutex_unlock(&bp->err_recovery_lock); + + if (rte_eal_alarm_set(US_PER_MS, bnxt_dev_reset_and_resume, + (void *)bp)) { + PMD_DRV_LOG_LINE(ERR, + "Port %u: Failed to schedule FUNC_ERROR reinit -- " + "HWRM_FUNC_RESET already reclaimed all resources, " + "marking port fatal", + bp->eth_dev->data->port_id); + /* Nothing will ever call bnxt_dev_reset_and_resume() to + * rebuild the rings/VNICs HWRM_FUNC_RESET just reclaimed -- + * clearing PXP_ERROR/FW_RESET here would leave the driver + * thinking the port is healthy when it has no resources left. + */ + pthread_mutex_lock(&bp->err_recovery_lock); + bp->flags |= BNXT_FLAG_FATAL_ERROR; + bp->flags &= ~(BNXT_FLAG_PXP_ERROR | BNXT_FLAG_FW_RESET); + pthread_mutex_unlock(&bp->err_recovery_lock); + bnxt_func_error_clear_pending(bp); + } +} + +static void bnxt_func_error_poll_cb(void *arg) +{ + struct bnxt *bp = arg; + + if (!rte_atomic_load_explicit(&bp->func_error_info->support, + rte_memory_order_relaxed)) + return; + + if (!rte_atomic_load_explicit(&bp->func_error_info->recovery_pending, + rte_memory_order_acquire)) + bnxt_hwrm_func_error_poll(bp); + + /* bnxt_cancel_func_error_poll() clears poll_scheduled before calling + * rte_eal_alarm_cancel(), which blocks until this callback (if + * already running) returns -- re-check here so a cancel that landed + * while this invocation was in flight isn't outrun by this rearm. + */ + if (rte_atomic_load_explicit(&bp->func_error_info->poll_scheduled, + rte_memory_order_acquire) && + rte_eal_alarm_set(US_PER_S, bnxt_func_error_poll_cb, (void *)bp) != 0) + rte_atomic_store_explicit(&bp->func_error_info->poll_scheduled, 0, + rte_memory_order_release); +} + +void bnxt_schedule_func_error_poll(struct bnxt *bp) +{ + if (!rte_atomic_load_explicit(&bp->func_error_info->support, + rte_memory_order_relaxed) || + rte_atomic_load_explicit(&bp->func_error_info->poll_scheduled, + rte_memory_order_acquire)) + return; + + if (rte_eal_alarm_set(US_PER_S, bnxt_func_error_poll_cb, (void *)bp) != 0) + return; + + rte_atomic_store_explicit(&bp->func_error_info->poll_scheduled, 1, + rte_memory_order_release); +} + +void bnxt_cancel_func_error_poll(struct bnxt *bp) +{ + if (!rte_atomic_load_explicit(&bp->func_error_info->poll_scheduled, + rte_memory_order_acquire)) + return; + + /* Clear before cancelling: rte_eal_alarm_cancel() blocks until an + * in-flight invocation of the callback returns, and that invocation + * re-checks this flag right before its own rearm -- clearing first + * closes the window where it could rearm after cancel returns. + */ + rte_atomic_store_explicit(&bp->func_error_info->poll_scheduled, 0, + rte_memory_order_release); + rte_eal_alarm_cancel(bnxt_func_error_poll_cb, (void *)bp); +} + +int bnxt_parse_devarg_func_error_support(const char *key __rte_unused, + const char *value, void *opaque_arg) +{ + struct bnxt *bp = opaque_arg; + unsigned long func_error_support; + char *end = NULL; + + if (!value || !opaque_arg) { + PMD_DRV_LOG_LINE(ERR, + "Invalid parameter passed to func-error-support " + "devargs."); + return -EINVAL; + } + + func_error_support = strtoul(value, &end, 10); + if (end == NULL || *end != '\0' || + (func_error_support == ULONG_MAX && errno == ERANGE)) { + PMD_DRV_LOG_LINE(ERR, + "Invalid parameter passed to func-error-support " + "devargs."); + return -EINVAL; + } + + if (BNXT_DEVARG_FUNC_ERROR_SUPPORT_INVALID(func_error_support)) { + PMD_DRV_LOG_LINE(ERR, "Invalid func-error-support(%d) devargs.", + (uint8_t)func_error_support); + return -EINVAL; + } + + rte_atomic_store_explicit(&bp->func_error_info->support, + func_error_support, rte_memory_order_relaxed); + if (func_error_support) + PMD_DRV_LOG_LINE(INFO, + "func-error-support=1: FUNC_ERROR/PXP_ERROR " + "recovery opted in."); + + return 0; +} diff --git a/drivers/net/bnxt/bnxt_func_error.h b/drivers/net/bnxt/bnxt_func_error.h new file mode 100644 index 00000000000..f0797157bf5 --- /dev/null +++ b/drivers/net/bnxt/bnxt_func_error.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Broadcom + * All rights reserved. + */ + +#ifndef _BNXT_FUNC_ERROR_H_ +#define _BNXT_FUNC_ERROR_H_ + +#include "bnxt.h" + +#define BNXT_DEVARG_FUNC_ERROR_SUPPORT "func-error-support" +#define BNXT_DEVARG_FUNC_ERROR_SUPPORT_INVALID(val) ((val) > 1) + +struct bnxt_func_error_info { + uint8_t __rte_atomic support; + unsigned int __rte_atomic recovery_pending; + uint64_t __rte_atomic recovery_count; + uint64_t __rte_atomic oerrors; + unsigned int __rte_atomic poll_scheduled; +}; + +int bnxt_func_error_info_alloc(struct bnxt *bp); +void bnxt_func_error_info_free(struct bnxt *bp); + +void bnxt_func_error_trigger_recovery(struct bnxt *bp); +void bnxt_func_error_recovery_cb(void *arg); +void bnxt_func_error_clear_pending(struct bnxt *bp); +void bnxt_schedule_func_error_poll(struct bnxt *bp); +void bnxt_cancel_func_error_poll(struct bnxt *bp); +int bnxt_parse_devarg_func_error_support(const char *key, const char *value, + void *opaque_arg); + +#endif /* _BNXT_FUNC_ERROR_H_ */ diff --git a/drivers/net/bnxt/bnxt_hwrm.c b/drivers/net/bnxt/bnxt_hwrm.c index 870411ace90..0226bca6979 100644 --- a/drivers/net/bnxt/bnxt_hwrm.c +++ b/drivers/net/bnxt/bnxt_hwrm.c @@ -15,6 +15,7 @@ #include "bnxt.h" #include "bnxt_filter.h" +#include "bnxt_func_error.h" #include "bnxt_hwrm.h" #include "bnxt_rxq.h" #include "bnxt_rxr.h" @@ -501,6 +502,12 @@ static int bnxt_hwrm_send_message(struct bnxt *bp, void *msg, if (bp->flags & BNXT_FLAG_FATAL_ERROR) return 0; + /* HWRM_FUNC_RESET already reclaimed every ring/VNIC/stat-ctx for this + * function; skip the redundant per-resource frees during cleanup. + */ + if (bp->flags & BNXT_FLAG_PXP_ERROR) + return 0; + /* If previous HWRM command timed out, do not send new HWRM command */ if (bp->flags & BNXT_FLAG_FW_TIMEDOUT) return 0; @@ -611,6 +618,15 @@ static int bnxt_hwrm_send_message(struct bnxt *bp, void *msg, bp->flags |= BNXT_FLAG_FW_TIMEDOUT; return -ETIMEDOUT; } + + /* Any HWRM command can be rejected with this once the function + * enters FUNC_ERROR state; it's the only way the driver finds out. + */ + if (rte_atomic_load_explicit(&bp->func_error_info->support, + rte_memory_order_relaxed) && + rte_le_to_cpu_16(resp->error_code) == HWRM_ERR_CODE_FUNC_RESET_REQUIRED) + bnxt_func_error_trigger_recovery(bp); + return 0; } @@ -1375,6 +1391,9 @@ int bnxt_hwrm_func_driver_register(struct bnxt *bp) flags = HWRM_FUNC_DRV_RGTR_INPUT_FLAGS_HOT_RESET_SUPPORT; if (bp->fw_cap & BNXT_FW_CAP_ERROR_RECOVERY) flags |= HWRM_FUNC_DRV_RGTR_INPUT_FLAGS_ERROR_RECOVERY_SUPPORT; + if (rte_atomic_load_explicit(&bp->func_error_info->support, + rte_memory_order_relaxed)) + flags |= HWRM_FUNC_DRV_RGTR_INPUT_FLAGS_PXP_ERROR_SUPPORT; /* PFs and trusted VFs should indicate the support of the * Master capability on non Stingray platform @@ -1441,6 +1460,74 @@ int bnxt_hwrm_func_driver_register(struct bnxt *bp) return rc; } +int bnxt_hwrm_func_error_poll(struct bnxt *bp) +{ + struct hwrm_ver_get_input req = {.req_type = 0 }; + struct hwrm_ver_get_output *resp = bp->hwrm_cmd_resp_addr; + int rc; + + HWRM_PREP(&req, HWRM_VER_GET, BNXT_USE_CHIMP_MB); + + req.hwrm_intf_maj = HWRM_VERSION_MAJOR; + req.hwrm_intf_min = HWRM_VERSION_MINOR; + req.hwrm_intf_upd = HWRM_VERSION_UPDATE; + + rc = bnxt_hwrm_send_message(bp, &req, sizeof(req), BNXT_USE_CHIMP_MB); + HWRM_CHECK_RESULT_SILENT(); + HWRM_UNLOCK(); + + return rc; +} + +/* Called before BNXT_FLAG_PXP_ERROR is set, so not blocked by the + * post-reset HWRM gate, and safe to call before the HWRM_FUNC_RESET + * that clears the error state. Failure here is + * non-fatal to recovery, so callers only need to log it. + */ +int bnxt_hwrm_func_error_query(struct bnxt *bp) +{ + struct hwrm_func_error_query_input req = {.req_type = 0 }; + struct hwrm_func_error_query_output *resp = bp->hwrm_cmd_resp_addr; + int rc; + + HWRM_PREP(&req, HWRM_FUNC_ERROR_QUERY, BNXT_USE_CHIMP_MB); + + rc = bnxt_hwrm_send_message(bp, &req, sizeof(req), BNXT_USE_CHIMP_MB); + HWRM_CHECK_RESULT(); + + if (resp->func_error_type != HWRM_FUNC_ERROR_QUERY_OUTPUT_FUNC_ERROR_TYPE_PXP_ERROR) { + /* Unexpected here -- only called from the recovery path. */ + PMD_DRV_LOG_LINE(INFO, + "Port %u: FUNC_ERROR diag: function not in FUNC_ERROR " + "state (func_error_type=%u)", + bp->eth_dev->data->port_id, resp->func_error_type); + } else if (resp->client_err == 0) { + /* Fault was propagated from a parent PF, not captured + * directly on this function. + */ + PMD_DRV_LOG_LINE(INFO, + "Port %u: FUNC_ERROR diag: no direct fault captured " + "on this function (parent PF fault propagation)", + bp->eth_dev->data->port_id); + } else { + rte_atomic_fetch_add_explicit(&bp->func_error_info->oerrors, 1, + rte_memory_order_relaxed); + PMD_DRV_LOG_LINE(ERR, + "Port %u: FUNC_ERROR diag: client_id=%u client_err=%#x " + "bad_dma_addr=%#" PRIx64 " (valid=%u) req_sig=%#x", + bp->eth_dev->data->port_id, resp->client_id, + rte_le_to_cpu_16(resp->client_err), + rte_le_to_cpu_64(resp->bad_dma_addr), + !!(rte_le_to_cpu_32(resp->flags) & + HWRM_FUNC_ERROR_QUERY_OUTPUT_FLAGS_BAD_DMA_ADDR_VALID), + rte_le_to_cpu_32(resp->client_req_sig)); + } + + HWRM_UNLOCK(); + + return rc; +} + int bnxt_hwrm_check_vf_rings(struct bnxt *bp) { if (!(BNXT_VF(bp) && (bp->flags & BNXT_FLAG_NEW_RM))) diff --git a/drivers/net/bnxt/bnxt_hwrm.h b/drivers/net/bnxt/bnxt_hwrm.h index 1dd66710462..9fe0202174e 100644 --- a/drivers/net/bnxt/bnxt_hwrm.h +++ b/drivers/net/bnxt/bnxt_hwrm.h @@ -202,6 +202,8 @@ int bnxt_hwrm_fwd_resp(struct bnxt *bp, uint16_t target_id, int bnxt_hwrm_func_buf_rgtr(struct bnxt *bp, int num_vfs); int bnxt_hwrm_func_buf_unrgtr(struct bnxt *bp); int bnxt_hwrm_func_driver_register(struct bnxt *bp); +int bnxt_hwrm_func_error_query(struct bnxt *bp); +int bnxt_hwrm_func_error_poll(struct bnxt *bp); int bnxt_hwrm_func_qcaps(struct bnxt *bp); int bnxt_hwrm_func_reset(struct bnxt *bp); int bnxt_hwrm_func_driver_unregister(struct bnxt *bp); diff --git a/drivers/net/bnxt/bnxt_stats.c b/drivers/net/bnxt/bnxt_stats.c index 1c3c6b209ba..867483a514a 100644 --- a/drivers/net/bnxt/bnxt_stats.c +++ b/drivers/net/bnxt/bnxt_stats.c @@ -11,6 +11,7 @@ #include "bnxt.h" #include "bnxt_cpr.h" #include "bnxt_filter.h" +#include "bnxt_func_error.h" #include "bnxt_hwrm.h" #include "bnxt_rxq.h" #include "bnxt_stats.h" @@ -653,6 +654,9 @@ static int bnxt_stats_get_ext(struct rte_eth_dev *eth_dev, struct bnxt *bp = eth_dev->data->dev_private; unsigned int num_q_stats; + bnxt_stats->oerrors += rte_atomic_load_explicit(&bp->func_error_info->oerrors, + rte_memory_order_relaxed); + num_q_stats = RTE_MIN(bp->rx_cp_nr_rings, eth_dev->data->nb_rx_queues); for (i = 0; i < bp->rx_cp_nr_rings; i++) { struct bnxt_rx_queue *rxq = bp->rx_queues[i]; @@ -718,6 +722,9 @@ int bnxt_stats_get_op(struct rte_eth_dev *eth_dev, if (BNXT_TPA_V2_P7(bp)) return bnxt_stats_get_ext(eth_dev, bnxt_stats, qstats); + bnxt_stats->oerrors += rte_atomic_load_explicit(&bp->func_error_info->oerrors, + rte_memory_order_relaxed); + num_q_stats = RTE_MIN(bp->rx_cp_nr_rings, eth_dev->data->nb_rx_queues); for (i = 0; i < bp->rx_cp_nr_rings; i++) { struct bnxt_rx_queue *rxq = bp->rx_queues[i]; @@ -817,6 +824,11 @@ int bnxt_stats_reset_op(struct rte_eth_dev *eth_dev) rte_memory_order_relaxed); } + rte_atomic_store_explicit(&bp->func_error_info->recovery_count, 0, + rte_memory_order_relaxed); + rte_atomic_store_explicit(&bp->func_error_info->oerrors, 0, + rte_memory_order_relaxed); + bnxt_clear_prev_stat(bp); return ret; @@ -1049,6 +1061,11 @@ int bnxt_dev_xstats_get_op(struct rte_eth_dev *eth_dev, rte_memory_order_relaxed); count++; + xstats[count].id = count; + xstats[count].value = rte_atomic_load_explicit(&bp->func_error_info->recovery_count, + rte_memory_order_relaxed); + count++; + if (bp->fw_cap & BNXT_FW_CAP_ADV_FLOW_COUNTERS && bp->fw_cap & BNXT_FW_CAP_ADV_FLOW_MGMT && BNXT_FLOW_XSTATS_EN(bp)) { @@ -1182,6 +1199,10 @@ int bnxt_dev_xstats_get_names_op(struct rte_eth_dev *eth_dev, sizeof(xstats_names[count].name)); count++; + rte_strscpy(xstats_names[count].name, "func_error_recovery", + sizeof(xstats_names[count].name)); + count++; + if (bp->fw_cap & BNXT_FW_CAP_ADV_FLOW_COUNTERS && bp->fw_cap & BNXT_FW_CAP_ADV_FLOW_MGMT && BNXT_FLOW_XSTATS_EN(bp)) { diff --git a/drivers/net/bnxt/bnxt_stats.h b/drivers/net/bnxt/bnxt_stats.h index 534b9af5e80..f3df6d988d1 100644 --- a/drivers/net/bnxt/bnxt_stats.h +++ b/drivers/net/bnxt/bnxt_stats.h @@ -9,7 +9,7 @@ #include <ethdev_driver.h> /* Number of software (non-HWRM) xstats appended after the FW-reported ones. */ -#define BNXT_NUM_SW_XSTATS 1 +#define BNXT_NUM_SW_XSTATS 2 void bnxt_free_stats(struct bnxt *bp); int bnxt_stats_get_op(struct rte_eth_dev *eth_dev, diff --git a/drivers/net/bnxt/hsi_struct_def_dpdk.h b/drivers/net/bnxt/hsi_struct_def_dpdk.h index 1a8874f5b62..335b8dbc3ae 100644 --- a/drivers/net/bnxt/hsi_struct_def_dpdk.h +++ b/drivers/net/bnxt/hsi_struct_def_dpdk.h @@ -293,6 +293,11 @@ struct __rte_packed_begin cmd_nums { #define HWRM_FUNC_VF_RESC_FREE UINT32_C(0x1b) #define HWRM_FUNC_VF_VNIC_IDS_QUERY UINT32_C(0x1c) #define HWRM_FUNC_DRV_RGTR UINT32_C(0x1d) + /* + * This command is used to query detailed diagnostic data when a + * function is in the FUNC_ERROR state. + */ + #define HWRM_FUNC_ERROR_QUERY UINT32_C(0x1cb) #define HWRM_FUNC_DRV_QVER UINT32_C(0x1e) #define HWRM_FUNC_BUF_RGTR UINT32_C(0x1f) #define HWRM_PORT_PHY_CFG UINT32_C(0x20) @@ -1174,6 +1179,14 @@ struct __rte_packed_begin ret_codes { * requested by the host is not present or does not exist. */ #define HWRM_ERR_CODE_ENTITY_NOT_PRESENT UINT32_C(0x13) + /* + * This error code is reported by Firmware when a command cannot be + * processed because the function is in the FUNC_ERROR state. The + * driver must issue HWRM_FUNC_RESET to clear the error state before + * resubmitting any gated commands. This code is returned for both PF + * and VF functions in the FUNC_ERROR state. + */ + #define HWRM_ERR_CODE_FUNC_RESET_REQUIRED UINT32_C(0x15) /* * This value indicates that the HWRM response is in TLV format and * should be interpreted as one or more TLVs starting with the @@ -18343,6 +18356,17 @@ struct __rte_packed_begin hwrm_func_drv_rgtr_input { */ #define HWRM_FUNC_DRV_RGTR_INPUT_FLAGS_TF_INGRESS_NIC_FLOW_MODE \ UINT32_C(0x800) + /* + * When this bit is '1', the function driver is indicating support + * for the FUNC_ERROR state machine. If a hardware error (like a PXP + * DMA error) occurs, firmware will leave the function blocked and + * reject subsequent HWRM commands with + * HWRM_ERR_CODE_FUNC_RESET_REQUIRED until the driver issues + * HWRM_FUNC_RESET. If '0', firmware falls back to legacy + * silent-clear behavior. + */ + #define HWRM_FUNC_DRV_RGTR_INPUT_FLAGS_PXP_ERROR_SUPPORT \ + UINT32_C(0x4000) uint32_t enables; /* * This bit must be '1' for the os_type field to be @@ -26677,6 +26701,112 @@ struct __rte_packed_begin hwrm_func_vlan_qcfg_output { uint8_t valid; } __rte_packed_end; +/************************* + * hwrm_func_error_query * + *************************/ + + +/* hwrm_func_error_query_input (size:192b/24B) */ +struct __rte_packed_begin hwrm_func_error_query_input { + /* The HWRM command request type. */ + uint16_t req_type; + /* + * The completion ring to send the completion event on. This should + * be the NQ ID returned from the `nq_alloc` HWRM command. + */ + uint16_t cmpl_ring; + /* + * The sequence ID is used by the driver for tracking multiple + * commands. This ID is treated as opaque data by the firmware and + * the value is returned in the `hwrm_resp_hdr` upon completion. + */ + uint16_t seq_id; + /* + * The target ID of the command: + * * 0x0-0xFFF8 - The function ID + * * 0xFFF8-0xFFFC, 0xFFFE - Reserved for internal processors + * * 0xFFFD - Reserved for user-space HWRM interface + * * 0xFFFF - HWRM + */ + uint16_t target_id; + /* + * A physical address pointer pointing to a host buffer that the + * command's response data will be written. This can be either a host + * physical address (HPA) or a guest physical address (GPA) and must + * point to a physically contiguous block of memory. + */ + uint64_t resp_addr; + uint8_t unused_0[8]; +} __rte_packed_end; + +/* hwrm_func_error_query_output (size:256b/32B) */ +struct __rte_packed_begin hwrm_func_error_query_output { + /* The specific error status for the command. */ + uint16_t error_code; + /* The HWRM command request type. */ + uint16_t req_type; + /* The sequence ID from the original command. */ + uint16_t seq_id; + /* The length of the response data in number of bytes. */ + uint16_t resp_len; + /* + * The type of error that caused the FUNC_ERROR state. If the function + * is not currently in FUNC_ERROR state, this field is set to none (0) + * and all other error fields are zeroed. + */ + uint8_t func_error_type; + /* No FUNC_ERROR is active. The function is operating normally. */ + #define HWRM_FUNC_ERROR_QUERY_OUTPUT_FUNC_ERROR_TYPE_NONE \ + UINT32_C(0x0) + /* A PXP error occurred (e.g., PCIe transaction error). */ + #define HWRM_FUNC_ERROR_QUERY_OUTPUT_FUNC_ERROR_TYPE_PXP_ERROR \ + UINT32_C(0x1) + #define HWRM_FUNC_ERROR_QUERY_OUTPUT_FUNC_ERROR_TYPE_LAST \ + HWRM_FUNC_ERROR_QUERY_OUTPUT_FUNC_ERROR_TYPE_PXP_ERROR + /* + * The internal hardware client ID that caused the error + * (e.g., TX engine, RX engine). + */ + uint8_t client_id; + /* + * The specific hardware error code (e.g., 4K boundary violation, + * poison TLP). A value of 0 indicates no direct fault was captured + * on this function; the function was placed into FUNC_ERROR by + * parent PF fault propagation. + */ + uint16_t client_err; + uint32_t flags; + /* + * If set to 1, the bad_dma_addr field contains a validly + * captured host memory address that is bad and triggered + * the fault. + */ + #define HWRM_FUNC_ERROR_QUERY_OUTPUT_FLAGS_BAD_DMA_ADDR_VALID \ + UINT32_C(0x1) + /* + * The 64-bit host memory address that triggered the fault (stashed + * by firmware at the time of the error). Check bad_dma_addr_valid + * before using this field. + */ + uint64_t bad_dma_addr; + /* + * The hardware-captured signature of the client request that + * triggered the fault. This is an opaque value stashed by firmware + * at the time of the error; log verbatim for offline diagnostic + * correlation. Valid when func_error_type is pxp_error. + */ + uint32_t client_req_sig; + uint8_t unused_1[3]; + /* + * This field is used in output records to indicate that the output + * is completely written to host memory. This field should be read as + * '1' to indicate that the output has been completely written. When + * writing a command completion or response to an internal processor, + * the order of writes has to be such that this field is written last. + */ + uint8_t valid; +} __rte_packed_end; + /********************** * hwrm_func_vlan_cfg * **********************/ diff --git a/drivers/net/bnxt/meson.build b/drivers/net/bnxt/meson.build index dc122fb3dff..67bfd3f79b6 100644 --- a/drivers/net/bnxt/meson.build +++ b/drivers/net/bnxt/meson.build @@ -35,6 +35,7 @@ sources = files( 'bnxt_ethdev.c', 'bnxt_filter.c', 'bnxt_flow.c', + 'bnxt_func_error.c', 'bnxt_hwrm.c', 'bnxt_irq.c', 'bnxt_mpc.c',

