[v5,4/4] net/qede: add support for get register operation
diff mbox series

Message ID 20200708225054.19665-5-rmody@marvell.com
State Accepted, archived
Delegated to: Jerin Jacob
Headers show
Series
  • net/qede: add FW debug data collection support
Related show

Checks

Context Check Description
ci/Intel-compilation fail Compilation issues
ci/checkpatch success coding style OK

Commit Message

Rasesh Mody July 8, 2020, 10:50 p.m. UTC
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 |   1 +
 drivers/net/qede/Makefile         |   1 +
 drivers/net/qede/base/bcm_osal.c  |  25 +++
 drivers/net/qede/base/bcm_osal.h  |   5 +
 drivers/net/qede/meson.build      |   1 +
 drivers/net/qede/qede_ethdev.c    |   1 +
 drivers/net/qede/qede_ethdev.h    |  25 +++
 drivers/net/qede/qede_regs.c      | 271 ++++++++++++++++++++++++++++++
 8 files changed, 330 insertions(+)
 create mode 100644 drivers/net/qede/qede_regs.c

Comments

Jerin Jacob July 9, 2020, 10:11 a.m. UTC | #1
On Thu, Jul 9, 2020 at 4:22 AM Rasesh Mody <rmody@marvell.com> wrote:
>
> 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>


Series applied to dpdk-next-net-mrvl/master. Thanks.

>  doc/guides/nics/features/qede.ini |   1 +
>  drivers/net/qede/Makefile         |   1 +
>  drivers/net/qede/base/bcm_osal.c  |  25 +++
>  drivers/net/qede/base/bcm_osal.h  |   5 +
>  drivers/net/qede/meson.build      |   1 +
>  drivers/net/qede/qede_ethdev.c    |   1 +
>  drivers/net/qede/qede_ethdev.h    |  25 +++
>  drivers/net/qede/qede_regs.c      | 271 ++++++++++++++++++++++++++++++
>  8 files changed, 330 insertions(+)
>  create mode 100644 drivers/net/qede/qede_regs.c
>
> diff --git a/doc/guides/nics/features/qede.ini b/doc/guides/nics/features/qede.ini
> index 20c90e626..f8716523e 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 3b00338ff..0e8a67b0d 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 45557fe3c..65837b53d 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 b4b94231b..5d4df5907 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 50c9fad7e..05c9bff73 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 b5d6c7c43..e5a2581dd 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 b988a73f2..76c5dae3b 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 000000000..73760bdfd
> --- /dev/null
> +++ b/drivers/net/qede/qede_regs.c
> @@ -0,0 +1,271 @@
> +/* 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=%ld len=%ld\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;
> +}
> --
> 2.18.0
>
Ferruh Yigit July 9, 2020, 4:32 p.m. UTC | #2
On 7/8/2020 11:50 PM, Rasesh Mody wrote:
> 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>

<...>

> +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=%ld len=%ld\n",
> +			bytes, len);

Travis is giving build error on this for 32 bits [1], fixing while merging by
replacing '%ld' -> '%zd', please double check in next-net.

[1]
../drivers/net/qede/qede_regs.c: In function ‘qede_write_fwdump’:
../drivers/net/qede/qede_regs.c:229:59: error: format ‘%ld’ expects argument of
type ‘long int’, but argument 3 has type ‘size_t {aka unsigned int}’
[-Werror=format=]
   fprintf(stderr, "Can not write all of dump data bytes=%ld len=%ld\n",
                                                         ~~^
                                                         %d
../drivers/net/qede/qede_regs.c:229:67: error: format ‘%ld’ expects argument of
type ‘long int’, but argument 4 has type ‘size_t {aka unsigned int}’
[-Werror=format=]
   fprintf(stderr, "Can not write all of dump data bytes=%ld len=%ld\n",
                                                                 ~~^
                                                                 %d
Rasesh Mody July 9, 2020, 11:30 p.m. UTC | #3
>From: Ferruh Yigit <ferruh.yigit@intel.com>
>Sent: Thursday, July 09, 2020 9:33 AM
>
>On 7/8/2020 11:50 PM, Rasesh Mody wrote:
>> 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>
>
><...>
>
>> +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=%ld
>len=%ld\n",
>> +			bytes, len);
>
>Travis is giving build error on this for 32 bits [1], fixing while merging by
>replacing '%ld' -> '%zd', please double check in next-net.
>

Thanks Ferruh, change looks good.

>[1]
>../drivers/net/qede/qede_regs.c: In function ‘qede_write_fwdump’:
>../drivers/net/qede/qede_regs.c:229:59: error: format ‘%ld’ expects
>argument of type ‘long int’, but argument 3 has type ‘size_t {aka unsigned
>int}’
>[-Werror=format=]
>   fprintf(stderr, "Can not write all of dump data bytes=%ld len=%ld\n",
>                                                         ~~^
>                                                         %d
>../drivers/net/qede/qede_regs.c:229:67: error: format ‘%ld’ expects
>argument of type ‘long int’, but argument 4 has type ‘size_t {aka unsigned
>int}’
>[-Werror=format=]
>   fprintf(stderr, "Can not write all of dump data bytes=%ld len=%ld\n",
>                                                                 ~~^
>                                                                 %d

Patch
diff mbox series

diff --git a/doc/guides/nics/features/qede.ini b/doc/guides/nics/features/qede.ini
index 20c90e626..f8716523e 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 3b00338ff..0e8a67b0d 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 45557fe3c..65837b53d 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 b4b94231b..5d4df5907 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 50c9fad7e..05c9bff73 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 b5d6c7c43..e5a2581dd 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 b988a73f2..76c5dae3b 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 000000000..73760bdfd
--- /dev/null
+++ b/drivers/net/qede/qede_regs.c
@@ -0,0 +1,271 @@ 
+/* 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=%ld len=%ld\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;
+}