From: Rasesh Mody Date: Wed, 8 Jul 2020 22:50:54 +0000 (-0700) Subject: net/qede: support registers dump X-Git-Url: http://git.droids-corp.org/?a=commitdiff_plain;h=a50d7cbbdad74c05aa9ac937ccf930a8b067e6b4;hp=ec55c118792b1deefe8c149e1a2657c1ddc488c1;p=dpdk.git net/qede: support registers dump Add support for .get_reg eth_dev ops which will be used to collect the firmware debug data. PMD on detecting on some HW errors will collect the FW/HW Dump to a buffer and then it will save it to a file implemented in qede_save_fw_dump(). Dump file location and name: Location: or DPDK root Name: qede_pmd_dump_mm-dd-yy_hh-mm-ss.bin DPDK applications can initiate a debug data collection by invoking DPDK library’s rte_eth_dev_get_reg_info() API. This API invokes .get_reg() interface in the PMD. PMD implementation of .get_reg() collects the FW/HW Dump, saves it to data field of rte_dev_reg_info and passes it to the application. It’s the responsibility of the application to save the FW/HW Dump to a file. We recommendation using the file name format used by qede_save_fw_dump(). Signed-off-by: Rasesh Mody Signed-off-by: Igor Russkikh --- diff --git a/doc/guides/nics/features/qede.ini b/doc/guides/nics/features/qede.ini index 20c90e6264..f8716523ed 100644 --- a/doc/guides/nics/features/qede.ini +++ b/doc/guides/nics/features/qede.ini @@ -31,6 +31,7 @@ Packet type parsing = Y Basic stats = Y Extended stats = Y Stats per queue = Y +Registers dump = Y Multiprocess aware = Y Linux UIO = Y Linux VFIO = Y diff --git a/drivers/net/qede/Makefile b/drivers/net/qede/Makefile index 3b00338ff7..0e8a67b0da 100644 --- a/drivers/net/qede/Makefile +++ b/drivers/net/qede/Makefile @@ -104,5 +104,6 @@ SRCS-$(CONFIG_RTE_LIBRTE_QEDE_PMD) += qede_main.c SRCS-$(CONFIG_RTE_LIBRTE_QEDE_PMD) += qede_rxtx.c SRCS-$(CONFIG_RTE_LIBRTE_QEDE_PMD) += qede_filter.c SRCS-$(CONFIG_RTE_LIBRTE_QEDE_PMD) += qede_debug.c +SRCS-$(CONFIG_RTE_LIBRTE_QEDE_PMD) += qede_regs.c include $(RTE_SDK)/mk/rte.lib.mk diff --git a/drivers/net/qede/base/bcm_osal.c b/drivers/net/qede/base/bcm_osal.c index 45557fe3c3..65837b53d0 100644 --- a/drivers/net/qede/base/bcm_osal.c +++ b/drivers/net/qede/base/bcm_osal.c @@ -246,6 +246,28 @@ qede_get_mcp_proto_stats(struct ecore_dev *edev, } } +static void qede_hw_err_handler(void *dev, enum ecore_hw_err_type err_type) +{ + struct ecore_dev *edev = dev; + + switch (err_type) { + case ECORE_HW_ERR_FAN_FAIL: + break; + + case ECORE_HW_ERR_MFW_RESP_FAIL: + case ECORE_HW_ERR_HW_ATTN: + case ECORE_HW_ERR_DMAE_FAIL: + case ECORE_HW_ERR_RAMROD_FAIL: + case ECORE_HW_ERR_FW_ASSERT: + OSAL_SAVE_FW_DUMP(0); /* Using port 0 as default port_id */ + break; + + default: + DP_NOTICE(edev, false, "Unknown HW error [%d]\n", err_type); + return; + } +} + void qede_hw_err_notify(struct ecore_hwfn *p_hwfn, enum ecore_hw_err_type err_type) { @@ -275,6 +297,9 @@ qede_hw_err_notify(struct ecore_hwfn *p_hwfn, enum ecore_hw_err_type err_type) } DP_ERR(p_hwfn, "HW error occurred [%s]\n", err_str); + + qede_hw_err_handler(p_hwfn->p_dev, err_type); + ecore_int_attn_clr_enable(p_hwfn->p_dev, true); } diff --git a/drivers/net/qede/base/bcm_osal.h b/drivers/net/qede/base/bcm_osal.h index b4b94231b4..5d4df5907a 100644 --- a/drivers/net/qede/base/bcm_osal.h +++ b/drivers/net/qede/base/bcm_osal.h @@ -371,6 +371,11 @@ void qede_hw_err_notify(struct ecore_hwfn *p_hwfn, /* TODO: */ #define OSAL_SCHEDULE_RECOVERY_HANDLER(hwfn) nothing + +int qede_save_fw_dump(uint8_t port_id); + +#define OSAL_SAVE_FW_DUMP(port_id) qede_save_fw_dump(port_id) + #define OSAL_HW_ERROR_OCCURRED(hwfn, err_type) \ qede_hw_err_notify(hwfn, err_type) diff --git a/drivers/net/qede/meson.build b/drivers/net/qede/meson.build index 50c9fad7e0..05c9bff737 100644 --- a/drivers/net/qede/meson.build +++ b/drivers/net/qede/meson.build @@ -10,6 +10,7 @@ sources = files( 'qede_main.c', 'qede_rxtx.c', 'qede_debug.c', + 'qede_regs.c', ) if cc.has_argument('-Wno-format-nonliteral') diff --git a/drivers/net/qede/qede_ethdev.c b/drivers/net/qede/qede_ethdev.c index b5d6c7c430..e5a2581dda 100644 --- a/drivers/net/qede/qede_ethdev.c +++ b/drivers/net/qede/qede_ethdev.c @@ -2426,6 +2426,7 @@ static const struct eth_dev_ops qede_eth_dev_ops = { .udp_tunnel_port_add = qede_udp_dst_port_add, .udp_tunnel_port_del = qede_udp_dst_port_del, .fw_version_get = qede_fw_version_get, + .get_reg = qede_get_regs, }; static const struct eth_dev_ops qede_eth_vf_dev_ops = { diff --git a/drivers/net/qede/qede_ethdev.h b/drivers/net/qede/qede_ethdev.h index b988a73f23..76c5dae3b8 100644 --- a/drivers/net/qede/qede_ethdev.h +++ b/drivers/net/qede/qede_ethdev.h @@ -214,6 +214,8 @@ struct qede_tunn_params { uint16_t udp_port; }; +#define QEDE_FW_DUMP_FILE_SIZE 128 + /* * Structure to store private data for each port. */ @@ -252,6 +254,7 @@ struct qede_dev { char drv_ver[QEDE_PMD_DRV_VER_STR_SIZE]; bool vport_started; int vlan_offload_mask; + char dump_file[QEDE_FW_DUMP_FILE_SIZE]; void *ethdev; }; @@ -313,4 +316,26 @@ void qede_config_accept_any_vlan(struct qede_dev *qdev, bool flg); int qede_ucast_filter(struct rte_eth_dev *eth_dev, struct ecore_filter_ucast *ucast, bool add); + +#define REGDUMP_HEADER_SIZE sizeof(u32) +#define REGDUMP_HEADER_FEATURE_SHIFT 24 +#define REGDUMP_HEADER_ENGINE_SHIFT 31 +#define REGDUMP_HEADER_OMIT_ENGINE_SHIFT 30 + +enum debug_print_features { + OLD_MODE = 0, + IDLE_CHK = 1, + GRC_DUMP = 2, + MCP_TRACE = 3, + REG_FIFO = 4, + PROTECTION_OVERRIDE = 5, + IGU_FIFO = 6, + PHY = 7, + FW_ASSERTS = 8, +}; + +int qede_get_regs_len(struct qede_dev *qdev); +int qede_get_regs(struct rte_eth_dev *dev, struct rte_dev_reg_info *regs); +void qede_config_rx_mode(struct rte_eth_dev *eth_dev); +void qed_dbg_dump(struct rte_eth_dev *eth_dev); #endif /* _QEDE_ETHDEV_H_ */ diff --git a/drivers/net/qede/qede_regs.c b/drivers/net/qede/qede_regs.c new file mode 100644 index 0000000000..1f2dbc6e7b --- /dev/null +++ b/drivers/net/qede/qede_regs.c @@ -0,0 +1,272 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) 2020 Marvell Semiconductor Inc. + * All rights reserved. + * www.marvell.com + */ + +#include +#include +#include +#include +#include +#include "base/bcm_osal.h" +#include "qede_ethdev.h" + +int +qede_get_regs_len(struct qede_dev *qdev) +{ + struct ecore_dev *edev = &qdev->edev; + int cur_engine, num_of_hwfns, regs_len = 0; + uint8_t org_engine; + + if (IS_VF(edev)) + return 0; + + if (qdev->ops && qdev->ops->common) { + num_of_hwfns = qdev->dev_info.common.num_hwfns; + org_engine = qdev->ops->common->dbg_get_debug_engine(edev); + for (cur_engine = 0; cur_engine < num_of_hwfns; cur_engine++) { + /* compute required buffer size for idle_chks and + * grcDump for each hw function + */ + DP_NOTICE(edev, false, + "Calculating idle_chk and grcdump register length for current engine\n"); + qdev->ops->common->dbg_set_debug_engine(edev, + cur_engine); + regs_len += REGDUMP_HEADER_SIZE + + qdev->ops->common->dbg_idle_chk_size(edev) + + REGDUMP_HEADER_SIZE + + qdev->ops->common->dbg_idle_chk_size(edev) + + REGDUMP_HEADER_SIZE + + qdev->ops->common->dbg_grc_size(edev) + + REGDUMP_HEADER_SIZE + + qdev->ops->common->dbg_reg_fifo_size(edev) + + REGDUMP_HEADER_SIZE + + qdev->ops->common->dbg_protection_override_size(edev) + + REGDUMP_HEADER_SIZE + + qdev->ops->common->dbg_igu_fifo_size(edev) + + REGDUMP_HEADER_SIZE + + qdev->ops->common->dbg_fw_asserts_size(edev); + } + /* compute required buffer size for mcp trace and add it to the + * total required buffer size + */ + regs_len += REGDUMP_HEADER_SIZE + + qdev->ops->common->dbg_mcp_trace_size(edev); + + qdev->ops->common->dbg_set_debug_engine(edev, org_engine); + } + DP_NOTICE(edev, false, "Total length = %u\n", regs_len); + + return regs_len; +} + +static uint32_t +qede_calc_regdump_header(enum debug_print_features feature, int engine, + uint32_t feature_size, uint8_t omit_engine) +{ + /* insert the engine, feature and mode inside the header and + * combine it with feature size + */ + return (feature_size | (feature << REGDUMP_HEADER_FEATURE_SHIFT) | + (omit_engine << REGDUMP_HEADER_OMIT_ENGINE_SHIFT) | + (engine << REGDUMP_HEADER_ENGINE_SHIFT)); +} + +int qede_get_regs(struct rte_eth_dev *eth_dev, struct rte_dev_reg_info *regs) +{ + struct qede_dev *qdev = eth_dev->data->dev_private; + struct ecore_dev *edev = &qdev->edev; + uint32_t *buffer = regs->data; + int cur_engine, num_of_hwfns; + /* '1' tells the parser to omit the engine number in the output files */ + uint8_t omit_engine = 0; + uint8_t org_engine; + uint32_t feature_size; + uint32_t offset = 0; + + if (IS_VF(edev)) + return -ENOTSUP; + + if (buffer == NULL) { + regs->length = qede_get_regs_len(qdev); + regs->width = sizeof(uint32_t); + DP_INFO(edev, "Length %u\n", regs->length); + return 0; + } + + memset(buffer, 0, regs->length); + num_of_hwfns = qdev->dev_info.common.num_hwfns; + if (num_of_hwfns == 1) + omit_engine = 1; + + OSAL_MUTEX_ACQUIRE(&edev->dbg_lock); + + org_engine = qdev->ops->common->dbg_get_debug_engine(edev); + for (cur_engine = 0; cur_engine < num_of_hwfns; cur_engine++) { + /* collect idle_chks and grcDump for each hw function */ + DP_NOTICE(edev, false, "obtaining idle_chk and grcdump for current engine\n"); + qdev->ops->common->dbg_set_debug_engine(edev, cur_engine); + + /* first idle_chk */ + qdev->ops->common->dbg_idle_chk(edev, (uint8_t *)buffer + + offset + REGDUMP_HEADER_SIZE, &feature_size); + *(uint32_t *)((uint8_t *)buffer + offset) = + qede_calc_regdump_header(IDLE_CHK, cur_engine, + feature_size, omit_engine); + offset += (feature_size + REGDUMP_HEADER_SIZE); + DP_NOTICE(edev, false, "Idle Check1 feature_size %u\n", + feature_size); + + /* second idle_chk */ + qdev->ops->common->dbg_idle_chk(edev, (uint8_t *)buffer + + offset + REGDUMP_HEADER_SIZE, &feature_size); + *(uint32_t *)((uint8_t *)buffer + offset) = + qede_calc_regdump_header(IDLE_CHK, cur_engine, + feature_size, omit_engine); + offset += (feature_size + REGDUMP_HEADER_SIZE); + DP_NOTICE(edev, false, "Idle Check2 feature_size %u\n", + feature_size); + + /* reg_fifo dump */ + qdev->ops->common->dbg_reg_fifo(edev, (uint8_t *)buffer + + offset + REGDUMP_HEADER_SIZE, &feature_size); + *(uint32_t *)((uint8_t *)buffer + offset) = + qede_calc_regdump_header(REG_FIFO, cur_engine, + feature_size, omit_engine); + offset += (feature_size + REGDUMP_HEADER_SIZE); + DP_NOTICE(edev, false, "Reg fifo feature_size %u\n", + feature_size); + + /* igu_fifo dump */ + qdev->ops->common->dbg_igu_fifo(edev, (uint8_t *)buffer + + offset + REGDUMP_HEADER_SIZE, &feature_size); + *(uint32_t *)((uint8_t *)buffer + offset) = + qede_calc_regdump_header(IGU_FIFO, cur_engine, + feature_size, omit_engine); + offset += (feature_size + REGDUMP_HEADER_SIZE); + DP_NOTICE(edev, false, "IGU fifo feature_size %u\n", + feature_size); + + /* protection_override dump */ + qdev->ops->common->dbg_protection_override(edev, + (uint8_t *)buffer + + offset + REGDUMP_HEADER_SIZE, &feature_size); + *(uint32_t *)((uint8_t *)buffer + offset) = + qede_calc_regdump_header(PROTECTION_OVERRIDE, cur_engine, + feature_size, omit_engine); + offset += (feature_size + REGDUMP_HEADER_SIZE); + DP_NOTICE(edev, false, "Protection override feature_size %u\n", + feature_size); + + /* fw_asserts dump */ + qdev->ops->common->dbg_fw_asserts(edev, (uint8_t *)buffer + + offset + REGDUMP_HEADER_SIZE, &feature_size); + *(uint32_t *)((uint8_t *)buffer + offset) = + qede_calc_regdump_header(FW_ASSERTS, cur_engine, + feature_size, omit_engine); + offset += (feature_size + REGDUMP_HEADER_SIZE); + DP_NOTICE(edev, false, "FW assert feature_size %u\n", + feature_size); + + /* grc dump */ + qdev->ops->common->dbg_grc(edev, (uint8_t *)buffer + + offset + REGDUMP_HEADER_SIZE, &feature_size); + *(uint32_t *)((uint8_t *)buffer + offset) = + qede_calc_regdump_header(GRC_DUMP, cur_engine, + feature_size, omit_engine); + offset += (feature_size + REGDUMP_HEADER_SIZE); + DP_NOTICE(edev, false, "GRC dump feature_size %u\n", + feature_size); + } + + /* mcp_trace */ + qdev->ops->common->dbg_mcp_trace(edev, (uint8_t *)buffer + + offset + REGDUMP_HEADER_SIZE, &feature_size); + *(uint32_t *)((uint8_t *)buffer + offset) = + qede_calc_regdump_header(MCP_TRACE, cur_engine, feature_size, + omit_engine); + offset += (feature_size + REGDUMP_HEADER_SIZE); + DP_NOTICE(edev, false, "MCP trace feature_size %u\n", feature_size); + + qdev->ops->common->dbg_set_debug_engine(edev, org_engine); + + OSAL_MUTEX_RELEASE(&edev->dbg_lock); + + return 0; +} + +static void +qede_set_fw_dump_file_name(struct qede_dev *qdev) +{ + time_t ltime; + struct tm *tm; + + ltime = time(NULL); + tm = localtime(<ime); + snprintf(qdev->dump_file, QEDE_FW_DUMP_FILE_SIZE, + "qede_pmd_dump_%02d-%02d-%02d_%02d-%02d-%02d.bin", + tm->tm_mon + 1, (int)tm->tm_mday, 1900 + tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + +static int +qede_write_fwdump(const char *dump_file, void *dump, size_t len) +{ + int err = 0; + FILE *f; + size_t bytes; + + f = fopen(dump_file, "wb+"); + + if (!f) { + fprintf(stderr, "Can't open file %s: %s\n", + dump_file, strerror(errno)); + return 1; + } + bytes = fwrite(dump, 1, len, f); + if (bytes != len) { + fprintf(stderr, + "Can not write all of dump data bytes=%zd len=%zd\n", + bytes, len); + err = 1; + } + + if (fclose(f)) { + fprintf(stderr, "Can't close file %s: %s\n", + dump_file, strerror(errno)); + err = 1; + } + + return err; +} + +int +qede_save_fw_dump(uint8_t port_id) +{ + struct rte_eth_dev *eth_dev = &rte_eth_devices[port_id]; + struct rte_dev_reg_info regs; + struct qede_dev *qdev = eth_dev->data->dev_private; + struct ecore_dev *edev = &qdev->edev; + int rc = 0; + + if (!rte_eth_dev_is_valid_port(port_id)) { + DP_ERR(edev, "port %u invalid port ID", port_id); + return -ENODEV; + } + + memset(®s, 0, sizeof(regs)); + regs.length = qede_get_regs_len(qdev); + regs.data = OSAL_ZALLOC(eth_dev, GFP_KERNEL, regs.length); + if (regs.data) { + qede_get_regs(eth_dev, ®s); + qede_set_fw_dump_file_name(qdev); + rc = qede_write_fwdump(qdev->dump_file, regs.data, regs.length); + if (!rc) + DP_NOTICE(edev, false, "FW dump written to %s file\n", + qdev->dump_file); + OSAL_FREE(edev, regs.data); + } + + return rc; +}