net/qede: support registers dump
authorRasesh Mody <rmody@marvell.com>
Wed, 8 Jul 2020 22:50:54 +0000 (15:50 -0700)
committerFerruh Yigit <ferruh.yigit@intel.com>
Sat, 11 Jul 2020 04:18:53 +0000 (06:18 +0200)
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: <RTE_SDK> 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 <rmody@marvell.com>
Signed-off-by: Igor Russkikh <irusskikh@marvell.com>
doc/guides/nics/features/qede.ini
drivers/net/qede/Makefile
drivers/net/qede/base/bcm_osal.c
drivers/net/qede/base/bcm_osal.h
drivers/net/qede/meson.build
drivers/net/qede/qede_ethdev.c
drivers/net/qede/qede_ethdev.h
drivers/net/qede/qede_regs.c [new file with mode: 0644]

index 20c90e6..f871652 100644 (file)
@@ -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
index 3b00338..0e8a67b 100644 (file)
@@ -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
index 45557fe..65837b5 100644 (file)
@@ -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);
 }
 
index b4b9423..5d4df59 100644 (file)
@@ -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)
 
index 50c9fad..05c9bff 100644 (file)
@@ -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')
index b5d6c7c..e5a2581 100644 (file)
@@ -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 = {
index b988a73..76c5dae 100644 (file)
@@ -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 (file)
index 0000000..1f2dbc6
--- /dev/null
@@ -0,0 +1,272 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2020 Marvell Semiconductor Inc.
+ * All rights reserved.
+ * www.marvell.com
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <time.h>
+#include <rte_ethdev.h>
+#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(&ltime);
+       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(&regs, 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, &regs);
+               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;
+}