#include <linux/unaligned.h>
#include <linux/pci.h>
#include <linux/pldmfw.h>
#include <linux/types.h>
#include <net/devlink.h>
#include "fbnic.h"
#include "fbnic_fw.h"
#include "fbnic_tlv.h"
#define FBNIC_SN_STR_LEN 24
static int fbnic_version_running_put(struct devlink_info_req *req,
struct fbnic_fw_ver *fw_ver,
char *ver_name)
{
char running_ver[FBNIC_FW_VER_MAX_SIZE];
int err;
fbnic_mk_fw_ver_str(fw_ver->version, running_ver);
err = devlink_info_version_running_put(req, ver_name, running_ver);
if (err)
return err;
if (strlen(fw_ver->commit) > 0) {
char commit_name[FBNIC_SN_STR_LEN];
snprintf(commit_name, FBNIC_SN_STR_LEN, "%s.commit", ver_name);
err = devlink_info_version_running_put(req, commit_name,
fw_ver->commit);
if (err)
return err;
}
return 0;
}
static int fbnic_version_stored_put(struct devlink_info_req *req,
struct fbnic_fw_ver *fw_ver,
char *ver_name)
{
char stored_ver[FBNIC_FW_VER_MAX_SIZE];
int err;
fbnic_mk_fw_ver_str(fw_ver->version, stored_ver);
err = devlink_info_version_stored_put(req, ver_name, stored_ver);
if (err)
return err;
if (strlen(fw_ver->commit) > 0) {
char commit_name[FBNIC_SN_STR_LEN];
snprintf(commit_name, FBNIC_SN_STR_LEN, "%s.commit", ver_name);
err = devlink_info_version_stored_put(req, commit_name,
fw_ver->commit);
if (err)
return err;
}
return 0;
}
static int fbnic_devlink_info_get(struct devlink *devlink,
struct devlink_info_req *req,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_priv(devlink);
int err;
err = fbnic_version_running_put(req, &fbd->fw_cap.running.mgmt,
DEVLINK_INFO_VERSION_GENERIC_FW);
if (err)
return err;
err = fbnic_version_running_put(req, &fbd->fw_cap.running.bootloader,
DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER);
if (err)
return err;
err = fbnic_version_stored_put(req, &fbd->fw_cap.stored.mgmt,
DEVLINK_INFO_VERSION_GENERIC_FW);
if (err)
return err;
err = fbnic_version_stored_put(req, &fbd->fw_cap.stored.bootloader,
DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER);
if (err)
return err;
err = fbnic_version_stored_put(req, &fbd->fw_cap.stored.undi,
DEVLINK_INFO_VERSION_GENERIC_FW_UNDI);
if (err)
return err;
if (fbd->dsn) {
unsigned char serial[FBNIC_SN_STR_LEN];
u8 dsn[8];
put_unaligned_be64(fbd->dsn, dsn);
err = snprintf(serial, FBNIC_SN_STR_LEN, "%8phD", dsn);
if (err < 0)
return err;
err = devlink_info_serial_number_put(req, serial);
if (err)
return err;
}
return 0;
}
static bool
fbnic_pldm_match_record(struct pldmfw *context, struct pldmfw_record *record)
{
struct pldmfw_desc_tlv *desc;
u32 anti_rollback_ver = 0;
struct devlink *devlink;
struct fbnic_dev *fbd;
struct pci_dev *pdev;
if (!pldmfw_op_pci_match_record(context, record))
return false;
pdev = to_pci_dev(context->dev);
fbd = pci_get_drvdata(pdev);
devlink = priv_to_devlink(fbd);
list_for_each_entry(desc, &record->descs, entry) {
if (desc->type != PLDM_DESC_ID_VENDOR_DEFINED)
continue;
if (desc->size < 21 || desc->data[0] != 1 ||
desc->data[1] != 15)
continue;
if (memcmp(desc->data + 2, "AntiRollbackVer", 15) != 0)
continue;
anti_rollback_ver = get_unaligned_le32(desc->data + 17);
break;
}
if (anti_rollback_ver < fbd->fw_cap.anti_rollback_version) {
char buf[128];
snprintf(buf, sizeof(buf),
"New firmware anti-rollback version (0x%x) is older than device version (0x%x)!",
anti_rollback_ver, fbd->fw_cap.anti_rollback_version);
devlink_flash_update_status_notify(devlink, buf,
"Anti-Rollback", 0, 0);
return false;
}
return true;
}
static int
fbnic_flash_start(struct fbnic_dev *fbd, struct pldmfw_component *component)
{
struct fbnic_fw_completion *cmpl;
int err;
cmpl = fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ);
if (!cmpl)
return -ENOMEM;
err = fbnic_fw_xmit_fw_start_upgrade(fbd, cmpl,
component->identifier,
component->component_size);
if (err)
goto cmpl_free;
if (fbnic_mbx_wait_for_cmpl(cmpl))
err = cmpl->result;
else
err = -ETIMEDOUT;
fbnic_mbx_clear_cmpl(fbd, cmpl);
cmpl_free:
fbnic_fw_put_cmpl(cmpl);
return err;
}
static int
fbnic_flash_component(struct pldmfw *context,
struct pldmfw_component *component)
{
const u8 *data = component->component_data;
const u32 size = component->component_size;
struct fbnic_fw_completion *cmpl;
const char *component_name;
struct devlink *devlink;
struct fbnic_dev *fbd;
struct pci_dev *pdev;
u32 offset = 0;
u32 length = 0;
char buf[32];
int err;
pdev = to_pci_dev(context->dev);
fbd = pci_get_drvdata(pdev);
devlink = priv_to_devlink(fbd);
switch (component->identifier) {
case QSPI_SECTION_CMRT:
component_name = "boot1";
break;
case QSPI_SECTION_CONTROL_FW:
component_name = "boot2";
break;
case QSPI_SECTION_OPTION_ROM:
component_name = "option-rom";
break;
default:
snprintf(buf, sizeof(buf), "Unknown component ID %u!",
component->identifier);
devlink_flash_update_status_notify(devlink, buf, NULL, 0,
size);
return -EINVAL;
}
cmpl = fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ);
if (!cmpl)
return -ENOMEM;
err = fbnic_mbx_set_cmpl(fbd, cmpl);
if (err)
goto cmpl_free;
devlink_flash_update_timeout_notify(devlink, "Initializing",
component_name, 15);
err = fbnic_flash_start(fbd, component);
if (err)
goto err_no_msg;
while (offset < size) {
if (!fbnic_mbx_wait_for_cmpl(cmpl)) {
err = -ETIMEDOUT;
break;
}
err = cmpl->result;
if (err)
break;
if (cmpl->u.fw_update.offset != offset + length) {
err = -EFAULT;
break;
}
offset = cmpl->u.fw_update.offset;
length = cmpl->u.fw_update.length;
if (length > TLV_MAX_DATA || offset + length > size) {
err = -EFAULT;
break;
}
devlink_flash_update_status_notify(devlink, "Flashing",
component_name,
offset, size);
if (!length)
continue;
reinit_completion(&cmpl->done);
err = fbnic_fw_xmit_fw_write_chunk(fbd, data, offset, length,
0);
if (err)
break;
}
if (err) {
fbnic_fw_xmit_fw_write_chunk(fbd, NULL, 0, 0, err);
err_no_msg:
snprintf(buf, sizeof(buf), "Mailbox encountered error %d!",
err);
devlink_flash_update_status_notify(devlink, buf,
component_name, 0, 0);
}
fbnic_mbx_clear_cmpl(fbd, cmpl);
cmpl_free:
fbnic_fw_put_cmpl(cmpl);
return err;
}
static const struct pldmfw_ops fbnic_pldmfw_ops = {
.match_record = fbnic_pldm_match_record,
.flash_component = fbnic_flash_component,
};
static int
fbnic_devlink_flash_update(struct devlink *devlink,
struct devlink_flash_update_params *params,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_priv(devlink);
const struct firmware *fw = params->fw;
struct device *dev = fbd->dev;
struct pldmfw context;
char *err_msg;
int err;
context.ops = &fbnic_pldmfw_ops;
context.dev = dev;
err = pldmfw_flash_image(&context, fw);
if (err) {
switch (err) {
case -EINVAL:
err_msg = "Invalid image";
break;
case -EOPNOTSUPP:
err_msg = "Unsupported image";
break;
case -ENOMEM:
err_msg = "Out of memory";
break;
case -EFAULT:
err_msg = "Invalid header";
break;
case -ENOENT:
err_msg = "No matching record";
break;
case -ENODEV:
err_msg = "No matching device";
break;
case -ETIMEDOUT:
err_msg = "Timed out waiting for reply";
break;
default:
err_msg = "Unknown error";
break;
}
NL_SET_ERR_MSG_FMT_MOD(extack,
"Failed to flash PLDM Image: %s (error: %d)",
err_msg, err);
}
return err;
}
static const struct devlink_ops fbnic_devlink_ops = {
.info_get = fbnic_devlink_info_get,
.flash_update = fbnic_devlink_flash_update,
};
static int fbnic_fw_reporter_dump(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg, void *priv_ctx,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_health_reporter_priv(reporter);
u32 offset, index, index_count, length, size;
struct fbnic_fw_completion *fw_cmpl;
u8 *dump_data, **data;
int err;
fw_cmpl = fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_COREDUMP_GET_INFO_RESP);
if (!fw_cmpl)
return -ENOMEM;
err = fbnic_fw_xmit_coredump_info_msg(fbd, fw_cmpl, true);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"Failed to transmit core dump info msg");
goto cmpl_free;
}
if (!fbnic_mbx_wait_for_cmpl(fw_cmpl)) {
NL_SET_ERR_MSG_MOD(extack,
"Timed out waiting on core dump info");
err = -ETIMEDOUT;
goto cmpl_cleanup;
}
size = fw_cmpl->u.coredump_info.size;
err = fw_cmpl->result;
fbnic_mbx_clear_cmpl(fbd, fw_cmpl);
fbnic_fw_put_cmpl(fw_cmpl);
if (err) {
NL_SET_ERR_MSG_MOD(extack, "Firmware core dump returned error");
return err;
}
if (!size) {
NL_SET_ERR_MSG_MOD(extack,
"Firmware core dump returned size 0");
return -EIO;
}
index_count = DIV_ROUND_UP(size, TLV_MAX_DATA);
fw_cmpl = __fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_COREDUMP_READ_RESP,
sizeof(void *) * index_count + size);
if (!fw_cmpl)
return -ENOMEM;
dump_data = (void *)&fw_cmpl->u.coredump.data[index_count];
data = fw_cmpl->u.coredump.data;
fw_cmpl->u.coredump.size = size;
fw_cmpl->u.coredump.stride = TLV_MAX_DATA;
for (index = 0; index < index_count; index++) {
struct fbnic_fw_completion *cmpl_arg = index ? NULL : fw_cmpl;
offset = index * TLV_MAX_DATA;
length = min(size - offset, TLV_MAX_DATA);
data[index] = dump_data + offset;
err = fbnic_fw_xmit_coredump_read_msg(fbd, cmpl_arg,
offset, length);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"Failed to transmit core dump msg");
if (cmpl_arg)
goto cmpl_free;
else
goto cmpl_cleanup;
}
if (fbnic_mbx_wait_for_cmpl(fw_cmpl)) {
reinit_completion(&fw_cmpl->done);
} else {
NL_SET_ERR_MSG_FMT_MOD(extack,
"Timed out waiting on core dump (%d/%d)",
index + 1, index_count);
err = -ETIMEDOUT;
goto cmpl_cleanup;
}
if (fw_cmpl->u.coredump.data[index]) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"No data for core dump chunk (%d/%d)",
index + 1, index_count);
err = -EIO;
goto cmpl_cleanup;
}
}
devlink_fmsg_binary_pair_nest_start(fmsg, "FW coredump");
for (offset = 0; offset < size; offset += length) {
length = min_t(u32, size - offset, TLV_MAX_DATA);
devlink_fmsg_binary_put(fmsg, dump_data + offset, length);
}
devlink_fmsg_binary_pair_nest_end(fmsg);
cmpl_cleanup:
fbnic_mbx_clear_cmpl(fbd, fw_cmpl);
cmpl_free:
fbnic_fw_put_cmpl(fw_cmpl);
return err;
}
static int
fbnic_fw_reporter_diagnose(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_health_reporter_priv(reporter);
u32 sec, msec;
if (!fbd->prev_firmware_time)
return 0;
sec = div_u64_rem(fbd->firmware_time, MSEC_PER_SEC, &msec);
devlink_fmsg_pair_nest_start(fmsg, "last_heartbeat");
devlink_fmsg_obj_nest_start(fmsg);
devlink_fmsg_pair_nest_start(fmsg, "fw_uptime");
devlink_fmsg_obj_nest_start(fmsg);
devlink_fmsg_u32_pair_put(fmsg, "sec", sec);
devlink_fmsg_u32_pair_put(fmsg, "msec", msec);
devlink_fmsg_obj_nest_end(fmsg);
devlink_fmsg_pair_nest_end(fmsg);
devlink_fmsg_obj_nest_end(fmsg);
devlink_fmsg_pair_nest_end(fmsg);
return 0;
}
void __printf(2, 3)
fbnic_devlink_fw_report(struct fbnic_dev *fbd, const char *format, ...)
{
char msg[FBNIC_FW_LOG_MAX_SIZE];
va_list args;
va_start(args, format);
vsnprintf(msg, FBNIC_FW_LOG_MAX_SIZE, format, args);
va_end(args);
devlink_health_report(fbd->fw_reporter, msg, fbd);
if (fbnic_fw_log_ready(fbd))
fbnic_fw_log_write(fbd, 0, fbd->firmware_time, msg);
}
static const struct devlink_health_reporter_ops fbnic_fw_ops = {
.name = "fw",
.dump = fbnic_fw_reporter_dump,
.diagnose = fbnic_fw_reporter_diagnose,
};
static u32 fbnic_read_otp_status(struct fbnic_dev *fbd)
{
return fbnic_fw_rd32(fbd, FBNIC_NS_OTP_STATUS);
}
static int
fbnic_otp_reporter_dump(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg, void *priv_ctx,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_health_reporter_priv(reporter);
u32 otp_status, otp_write_status, m;
otp_status = fbnic_read_otp_status(fbd);
otp_write_status = fbnic_fw_rd32(fbd, FBNIC_NS_OTP_WRITE_STATUS);
devlink_fmsg_pair_nest_start(fmsg, "OTP");
devlink_fmsg_obj_nest_start(fmsg);
devlink_fmsg_u32_pair_put(fmsg, "Status", otp_status);
m = FBNIC_NS_OTP_WRITE_DATA_STATUS_MASK;
devlink_fmsg_u32_pair_put(fmsg, "Data",
FIELD_GET(m, otp_write_status));
m = FBNIC_NS_OTP_WRITE_ECC_STATUS_MASK;
devlink_fmsg_u32_pair_put(fmsg, "ECC",
FIELD_GET(m, otp_write_status));
devlink_fmsg_obj_nest_end(fmsg);
devlink_fmsg_pair_nest_end(fmsg);
return 0;
}
void fbnic_devlink_otp_check(struct fbnic_dev *fbd, const char *msg)
{
if (!fbnic_read_otp_status(fbd))
return;
devlink_health_report(fbd->otp_reporter, msg, fbd);
if (fbnic_fw_log_ready(fbd))
fbnic_fw_log_write(fbd, 0, fbd->firmware_time, msg);
}
static const struct devlink_health_reporter_ops fbnic_otp_ops = {
.name = "otp",
.dump = fbnic_otp_reporter_dump,
};
int fbnic_devlink_health_create(struct fbnic_dev *fbd)
{
fbd->fw_reporter = devlink_health_reporter_create(priv_to_devlink(fbd),
&fbnic_fw_ops, fbd);
if (IS_ERR(fbd->fw_reporter)) {
dev_warn(fbd->dev,
"Failed to create FW fault reporter: %pe\n",
fbd->fw_reporter);
return PTR_ERR(fbd->fw_reporter);
}
fbd->otp_reporter = devlink_health_reporter_create(priv_to_devlink(fbd),
&fbnic_otp_ops, fbd);
if (IS_ERR(fbd->otp_reporter)) {
devlink_health_reporter_destroy(fbd->fw_reporter);
dev_warn(fbd->dev,
"Failed to create OTP fault reporter: %pe\n",
fbd->otp_reporter);
return PTR_ERR(fbd->otp_reporter);
}
return 0;
}
void fbnic_devlink_health_destroy(struct fbnic_dev *fbd)
{
devlink_health_reporter_destroy(fbd->otp_reporter);
devlink_health_reporter_destroy(fbd->fw_reporter);
}
void fbnic_devlink_free(struct fbnic_dev *fbd)
{
struct devlink *devlink = priv_to_devlink(fbd);
devlink_free(devlink);
}
struct fbnic_dev *fbnic_devlink_alloc(struct pci_dev *pdev)
{
void __iomem * const *iomap_table;
struct devlink *devlink;
struct fbnic_dev *fbd;
devlink = devlink_alloc(&fbnic_devlink_ops, sizeof(struct fbnic_dev),
&pdev->dev);
if (!devlink)
return NULL;
fbd = devlink_priv(devlink);
pci_set_drvdata(pdev, fbd);
fbd->dev = &pdev->dev;
iomap_table = pcim_iomap_table(pdev);
fbd->uc_addr0 = iomap_table[0];
fbd->uc_addr4 = iomap_table[4];
fbd->dsn = pci_get_dsn(pdev);
fbd->mps = pcie_get_mps(pdev);
fbd->readrq = pcie_get_readrq(pdev);
fbd->mac_addr_boundary = FBNIC_RPC_TCAM_MACDA_DEFAULT_BOUNDARY;
return fbd;
}
void fbnic_devlink_register(struct fbnic_dev *fbd)
{
struct devlink *devlink = priv_to_devlink(fbd);
devlink_register(devlink);
}
void fbnic_devlink_unregister(struct fbnic_dev *fbd)
{
struct devlink *devlink = priv_to_devlink(fbd);
devlink_unregister(devlink);
}