| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- // SPDX-License-Identifier: GPL-2.0
- /* Copyright (c) Meta Platforms, Inc. and affiliates. */
- #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;
- /* First, use the standard PCI matching function */
- 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);
- /* If PCI match is successful, check for vendor-specific descriptors */
- 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;
- }
- /* Compare versions and return error if they do not match */
- 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;
- /* Wait for firmware to ack firmware upgrade start */
- 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;
- }
- /* Once firmware receives the request to start upgrading it responds
- * with two messages:
- * 1. An ACK that it received the message and possible error code
- * indicating that an upgrade is not currently possible.
- * 2. A request for the first chunk of data
- *
- * Setup completions for write before issuing the start message so
- * the driver can catch both messages.
- */
- 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;
- /* Verify firmware is requesting the next chunk in the seq. */
- 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);
- /* Mailbox will set length to 0 once it receives the finish
- * message.
- */
- 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);
- /* Handle error returned by firmware */
- 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;
- }
- /* Read the dump, we can only transfer TLV_MAX_DATA at a time */
- 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;
- /* Populate pointer table w/ pointer offsets */
- 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++) {
- /* First iteration installs completion */
- 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 we didn't see the reply record as incomplete */
- 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;
- /* Device is most likely down, we're not exchanging heartbeats */
- 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);
- /* Dump OTP status */
- devlink_fmsg_pair_nest_start(fmsg, "OTP");
- devlink_fmsg_obj_nest_start(fmsg);
- devlink_fmsg_u32_pair_put(fmsg, "Status", otp_status);
- /* Extract OTP Write Data status */
- m = FBNIC_NS_OTP_WRITE_DATA_STATUS_MASK;
- devlink_fmsg_u32_pair_put(fmsg, "Data",
- FIELD_GET(m, otp_write_status));
- /* Extract OTP Write ECC 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)
- {
- /* Check if there is anything to report */
- 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);
- }
|