mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00
eth: fbnic: Add devlink dev flash support
Add support to update the CMRT and control firmware as well as the UEFI driver on fbnic using devlink dev flash. Make sure the shutdown / quiescence paths like suspend take the devlink lock to prevent them from interrupting the FW flashing process. Signed-off-by: Lee Trager <lee@trager.us> Signed-off-by: Jakub Kicinski <kuba@kernel.org> Reviewed-by: Jacob Keller <jacob.e.keller@intel.com> Reviewed-by: Simon Horman <horms@kernel.org> Link: https://patch.msgid.link/20250512190109.2475614-6-lee@trager.us Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
parent
2a4ada8a99
commit
82534f446d
@ -28,6 +28,17 @@ devlink dev info provides version information for all three components. In
|
|||||||
addition to the version the hg commit hash of the build is included as a
|
addition to the version the hg commit hash of the build is included as a
|
||||||
separate entry.
|
separate entry.
|
||||||
|
|
||||||
|
Upgrading Firmware
|
||||||
|
------------------
|
||||||
|
|
||||||
|
fbnic supports updating firmware using signed PLDM images with devlink dev
|
||||||
|
flash. PLDM images are written into the flash. Flashing does not interrupt
|
||||||
|
the operation of the device.
|
||||||
|
|
||||||
|
On host boot the latest UEFI driver is always used, no explicit activation
|
||||||
|
is required. Firmware activation is required to run new control firmware. cmrt
|
||||||
|
firmware can only be activated by power cycling the NIC.
|
||||||
|
|
||||||
Statistics
|
Statistics
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ config FBNIC
|
|||||||
select NET_DEVLINK
|
select NET_DEVLINK
|
||||||
select PAGE_POOL
|
select PAGE_POOL
|
||||||
select PHYLINK
|
select PHYLINK
|
||||||
|
select PLDMFW
|
||||||
help
|
help
|
||||||
This driver supports Meta Platforms Host Network Interface.
|
This driver supports Meta Platforms Host Network Interface.
|
||||||
|
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
#include <linux/unaligned.h>
|
#include <linux/unaligned.h>
|
||||||
#include <linux/pci.h>
|
#include <linux/pci.h>
|
||||||
|
#include <linux/pldmfw.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <net/devlink.h>
|
#include <net/devlink.h>
|
||||||
|
|
||||||
#include "fbnic.h"
|
#include "fbnic.h"
|
||||||
|
#include "fbnic_tlv.h"
|
||||||
|
|
||||||
#define FBNIC_SN_STR_LEN 24
|
#define FBNIC_SN_STR_LEN 24
|
||||||
|
|
||||||
@ -109,8 +111,264 @@ static int fbnic_devlink_info_get(struct devlink *devlink,
|
|||||||
return 0;
|
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 = kzalloc(sizeof(*cmpl), GFP_KERNEL);
|
||||||
|
if (!cmpl)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
fbnic_fw_init_cmpl(cmpl, FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ);
|
||||||
|
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 (wait_for_completion_timeout(&cmpl->done, 10 * HZ))
|
||||||
|
err = cmpl->result;
|
||||||
|
else
|
||||||
|
err = -ETIMEDOUT;
|
||||||
|
|
||||||
|
fbnic_fw_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 = kzalloc(sizeof(*cmpl), GFP_KERNEL);
|
||||||
|
if (!cmpl)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
fbnic_fw_init_cmpl(cmpl, FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ);
|
||||||
|
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 (!wait_for_completion_timeout(&cmpl->done, 15 * HZ)) {
|
||||||
|
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_fw_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 = {
|
static const struct devlink_ops fbnic_devlink_ops = {
|
||||||
.info_get = fbnic_devlink_info_get,
|
.info_get = fbnic_devlink_info_get,
|
||||||
|
.flash_update = fbnic_devlink_flash_update,
|
||||||
};
|
};
|
||||||
|
|
||||||
void fbnic_devlink_free(struct fbnic_dev *fbd)
|
void fbnic_devlink_free(struct fbnic_dev *fbd)
|
||||||
|
@ -100,6 +100,15 @@ do { \
|
|||||||
#define fbnic_mk_fw_ver_str(_rev_id, _str) \
|
#define fbnic_mk_fw_ver_str(_rev_id, _str) \
|
||||||
fbnic_mk_full_fw_ver_str(_rev_id, "", "", _str, sizeof(_str))
|
fbnic_mk_full_fw_ver_str(_rev_id, "", "", _str, sizeof(_str))
|
||||||
|
|
||||||
|
enum {
|
||||||
|
QSPI_SECTION_CMRT = 0,
|
||||||
|
QSPI_SECTION_CONTROL_FW = 1,
|
||||||
|
QSPI_SECTION_UCODE = 2,
|
||||||
|
QSPI_SECTION_OPTION_ROM = 3,
|
||||||
|
QSPI_SECTION_USER = 4,
|
||||||
|
QSPI_SECTION_INVALID,
|
||||||
|
};
|
||||||
|
|
||||||
#define FW_HEARTBEAT_PERIOD (10 * HZ)
|
#define FW_HEARTBEAT_PERIOD (10 * HZ)
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <linux/pci.h>
|
#include <linux/pci.h>
|
||||||
#include <linux/rtnetlink.h>
|
#include <linux/rtnetlink.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
|
#include <net/devlink.h>
|
||||||
|
|
||||||
#include "fbnic.h"
|
#include "fbnic.h"
|
||||||
#include "fbnic_drvinfo.h"
|
#include "fbnic_drvinfo.h"
|
||||||
@ -388,8 +389,12 @@ static int fbnic_pm_suspend(struct device *dev)
|
|||||||
rtnl_unlock();
|
rtnl_unlock();
|
||||||
|
|
||||||
null_uc_addr:
|
null_uc_addr:
|
||||||
|
devl_lock(priv_to_devlink(fbd));
|
||||||
|
|
||||||
fbnic_fw_free_mbx(fbd);
|
fbnic_fw_free_mbx(fbd);
|
||||||
|
|
||||||
|
devl_unlock(priv_to_devlink(fbd));
|
||||||
|
|
||||||
/* Free the IRQs so they aren't trying to occupy sleeping CPUs */
|
/* Free the IRQs so they aren't trying to occupy sleeping CPUs */
|
||||||
fbnic_free_irqs(fbd);
|
fbnic_free_irqs(fbd);
|
||||||
|
|
||||||
@ -420,11 +425,15 @@ static int __fbnic_pm_resume(struct device *dev)
|
|||||||
|
|
||||||
fbd->mac->init_regs(fbd);
|
fbd->mac->init_regs(fbd);
|
||||||
|
|
||||||
|
devl_lock(priv_to_devlink(fbd));
|
||||||
|
|
||||||
/* Re-enable mailbox */
|
/* Re-enable mailbox */
|
||||||
err = fbnic_fw_request_mbx(fbd);
|
err = fbnic_fw_request_mbx(fbd);
|
||||||
if (err)
|
if (err)
|
||||||
goto err_free_irqs;
|
goto err_free_irqs;
|
||||||
|
|
||||||
|
devl_unlock(priv_to_devlink(fbd));
|
||||||
|
|
||||||
/* No netdev means there isn't a network interface to bring up */
|
/* No netdev means there isn't a network interface to bring up */
|
||||||
if (fbnic_init_failure(fbd))
|
if (fbnic_init_failure(fbd))
|
||||||
return 0;
|
return 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user