// SPDX-License-Identifier: GPL-2.0-or-later /* Marvell/Qlogic FastLinQ NIC driver * * Copyright (C) 2020 Marvell International Ltd. */ #include #include #include #include "qed.h" #include "qed_devlink.h" enum qed_devlink_param_id { QED_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX, QED_DEVLINK_PARAM_ID_IWARP_CMT, }; struct qed_fw_fatal_ctx { enum qed_hw_err_type err_type; }; int qed_report_fatal_error(struct devlink *devlink, enum qed_hw_err_type err_type) { struct qed_devlink *qdl = devlink_priv(devlink); struct qed_fw_fatal_ctx fw_fatal_ctx = { .err_type = err_type, }; if (qdl->fw_reporter) devlink_health_report(qdl->fw_reporter, "Fatal error occurred", &fw_fatal_ctx); return 0; } static int qed_fw_fatal_reporter_dump(struct devlink_health_reporter *reporter, struct devlink_fmsg *fmsg, void *priv_ctx, struct netlink_ext_ack *extack) { struct qed_devlink *qdl = devlink_health_reporter_priv(reporter); struct qed_fw_fatal_ctx *fw_fatal_ctx = priv_ctx; struct qed_dev *cdev = qdl->cdev; u32 dbg_data_buf_size; u8 *p_dbg_data_buf; int err; /* Having context means that was a dump request after fatal, * so we enable extra debugging while gathering the dump, * just in case */ cdev->print_dbg_data = fw_fatal_ctx ? true : false; dbg_data_buf_size = qed_dbg_all_data_size(cdev); p_dbg_data_buf = vzalloc(dbg_data_buf_size); if (!p_dbg_data_buf) { DP_NOTICE(cdev, "Failed to allocate memory for a debug data buffer\n"); return -ENOMEM; } err = qed_dbg_all_data(cdev, p_dbg_data_buf); if (err) { DP_NOTICE(cdev, "Failed to obtain debug data\n"); vfree(p_dbg_data_buf); return err; } err = devlink_fmsg_binary_pair_put(fmsg, "dump_data", p_dbg_data_buf, dbg_data_buf_size); vfree(p_dbg_data_buf); return err; } static int qed_fw_fatal_reporter_recover(struct devlink_health_reporter *reporter, void *priv_ctx, struct netlink_ext_ack *extack) { struct qed_devlink *qdl = devlink_health_reporter_priv(reporter); struct qed_dev *cdev = qdl->cdev; qed_recovery_process(cdev); return 0; } static const struct devlink_health_reporter_ops qed_fw_fatal_reporter_ops = { .name = "fw_fatal", .recover = qed_fw_fatal_reporter_recover, .dump = qed_fw_fatal_reporter_dump, }; #define QED_REPORTER_FW_GRACEFUL_PERIOD 1200000 void qed_fw_reporters_create(struct devlink *devlink) { struct qed_devlink *dl = devlink_priv(devlink); dl->fw_reporter = devlink_health_reporter_create(devlink, &qed_fw_fatal_reporter_ops, QED_REPORTER_FW_GRACEFUL_PERIOD, dl); if (IS_ERR(dl->fw_reporter)) { DP_NOTICE(dl->cdev, "Failed to create fw reporter, err = %ld\n", PTR_ERR(dl->fw_reporter)); dl->fw_reporter = NULL; } } void qed_fw_reporters_destroy(struct devlink *devlink) { struct qed_devlink *dl = devlink_priv(devlink); struct devlink_health_reporter *rep; rep = dl->fw_reporter; if (!IS_ERR_OR_NULL(rep)) devlink_health_reporter_destroy(rep); } static int qed_dl_param_get(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx) { struct qed_devlink *qed_dl = devlink_priv(dl); struct qed_dev *cdev; cdev = qed_dl->cdev; ctx->val.vbool = cdev->iwarp_cmt; return 0; } static int qed_dl_param_set(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx) { struct qed_devlink *qed_dl = devlink_priv(dl); struct qed_dev *cdev; cdev = qed_dl->cdev; cdev->iwarp_cmt = ctx->val.vbool; return 0; } static const struct devlink_param qed_devlink_params[] = { DEVLINK_PARAM_DRIVER(QED_DEVLINK_PARAM_ID_IWARP_CMT, "iwarp_cmt", DEVLINK_PARAM_TYPE_BOOL, BIT(DEVLINK_PARAM_CMODE_RUNTIME), qed_dl_param_get, qed_dl_param_set, NULL), }; static int qed_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req, struct netlink_ext_ack *extack) { struct qed_devlink *qed_dl = devlink_priv(devlink); struct qed_dev *cdev = qed_dl->cdev; struct qed_dev_info *dev_info; char buf[100]; int err; dev_info = &cdev->common_dev_info; err = devlink_info_driver_name_put(req, KBUILD_MODNAME); if (err) return err; memcpy(buf, cdev->hwfns[0].hw_info.part_num, sizeof(cdev->hwfns[0].hw_info.part_num)); buf[sizeof(cdev->hwfns[0].hw_info.part_num)] = 0; if (buf[0]) { err = devlink_info_board_serial_number_put(req, buf); if (err) return err; } snprintf(buf, sizeof(buf), "%d.%d.%d.%d", GET_MFW_FIELD(dev_info->mfw_rev, QED_MFW_VERSION_3), GET_MFW_FIELD(dev_info->mfw_rev, QED_MFW_VERSION_2), GET_MFW_FIELD(dev_info->mfw_rev, QED_MFW_VERSION_1), GET_MFW_FIELD(dev_info->mfw_rev, QED_MFW_VERSION_0)); err = devlink_info_version_stored_put(req, DEVLINK_INFO_VERSION_GENERIC_FW_MGMT, buf); if (err) return err; snprintf(buf, sizeof(buf), "%d.%d.%d.%d", dev_info->fw_major, dev_info->fw_minor, dev_info->fw_rev, dev_info->fw_eng); return devlink_info_version_running_put(req, DEVLINK_INFO_VERSION_GENERIC_FW_APP, buf); } static const struct devlink_ops qed_dl_ops = { .info_get = qed_devlink_info_get, }; struct devlink *qed_devlink_register(struct qed_dev *cdev) { union devlink_param_value value; struct qed_devlink *qdevlink; struct devlink *dl; int rc; dl = devlink_alloc(&qed_dl_ops, sizeof(struct qed_devlink)); if (!dl) return ERR_PTR(-ENOMEM); qdevlink = devlink_priv(dl); qdevlink->cdev = cdev; rc = devlink_register(dl, &cdev->pdev->dev); if (rc) goto err_free; rc = devlink_params_register(dl, qed_devlink_params, ARRAY_SIZE(qed_devlink_params)); if (rc) goto err_unregister; value.vbool = false; devlink_param_driverinit_value_set(dl, QED_DEVLINK_PARAM_ID_IWARP_CMT, value); devlink_params_publish(dl); cdev->iwarp_cmt = false; qed_fw_reporters_create(dl); return dl; err_unregister: devlink_unregister(dl); err_free: devlink_free(dl); return ERR_PTR(rc); } void qed_devlink_unregister(struct devlink *devlink) { if (!devlink) return; qed_fw_reporters_destroy(devlink); devlink_params_unregister(devlink, qed_devlink_params, ARRAY_SIZE(qed_devlink_params)); devlink_unregister(devlink); devlink_free(devlink); }