2
0
mirror of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2025-09-04 20:19:47 +08:00
linux/drivers/net/ethernet/intel/ixgbe/devlink/devlink.c
Andrii Staikov 4811b0c220 ixgbe: add support for FW rollback mode
The driver should detect whether the device entered FW rollback
mode and then notify user with the dedicated message including
FW and NVM versions.

Even if the driver detected rollback mode, this should not result
in an probe error and the normal flow proceeds.

FW tries to rollback to "old" operational FW located in the
inactive NVM bank in cases when newly loaded FW exhibits faulty
behavior. If something goes wrong during boot the FW may switch
into rollback mode in an attempt to avoid recovery mode and stay
operational. After rollback is successful, the banks are swapped,
and the "rollback" bank becomes the active bank for the next reset.

Reviewed-by: Mateusz Polchlopek <mateusz.polchlopek@intel.com>
Signed-off-by: Andrii Staikov <andrii.staikov@intel.com>
Signed-off-by: Jedrzej Jagielski <jedrzej.jagielski@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
2025-04-15 07:36:33 -07:00

558 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025, Intel Corporation. */
#include "ixgbe.h"
#include "devlink.h"
#include "ixgbe_fw_update.h"
struct ixgbe_info_ctx {
char buf[128];
struct ixgbe_orom_info pending_orom;
struct ixgbe_nvm_info pending_nvm;
struct ixgbe_netlist_info pending_netlist;
struct ixgbe_hw_dev_caps dev_caps;
};
enum ixgbe_devlink_version_type {
IXGBE_DL_VERSION_RUNNING,
IXGBE_DL_VERSION_STORED
};
static void ixgbe_info_get_dsn(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx)
{
u8 dsn[8];
/* Copy the DSN into an array in Big Endian format */
put_unaligned_be64(pci_get_dsn(adapter->pdev), dsn);
snprintf(ctx->buf, sizeof(ctx->buf), "%8phD", dsn);
}
static void ixgbe_info_orom_ver(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx,
enum ixgbe_devlink_version_type type)
{
struct ixgbe_hw *hw = &adapter->hw;
struct ixgbe_nvm_version nvm_ver;
ctx->buf[0] = '\0';
if (hw->mac.type == ixgbe_mac_e610) {
struct ixgbe_orom_info *orom = &adapter->hw.flash.orom;
if (type == IXGBE_DL_VERSION_STORED &&
ctx->dev_caps.common_cap.nvm_update_pending_orom)
orom = &ctx->pending_orom;
snprintf(ctx->buf, sizeof(ctx->buf), "%u.%u.%u",
orom->major, orom->build, orom->patch);
return;
}
ixgbe_get_oem_prod_version(hw, &nvm_ver);
if (nvm_ver.oem_valid) {
snprintf(ctx->buf, sizeof(ctx->buf), "%x.%x.%x",
nvm_ver.oem_major, nvm_ver.oem_minor,
nvm_ver.oem_release);
return;
}
ixgbe_get_orom_version(hw, &nvm_ver);
if (nvm_ver.or_valid)
snprintf(ctx->buf, sizeof(ctx->buf), "%d.%d.%d",
nvm_ver.or_major, nvm_ver.or_build, nvm_ver.or_patch);
}
static void ixgbe_info_eetrack(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx,
enum ixgbe_devlink_version_type type)
{
struct ixgbe_hw *hw = &adapter->hw;
struct ixgbe_nvm_version nvm_ver;
if (hw->mac.type == ixgbe_mac_e610) {
u32 eetrack = hw->flash.nvm.eetrack;
if (type == IXGBE_DL_VERSION_STORED &&
ctx->dev_caps.common_cap.nvm_update_pending_nvm)
eetrack = ctx->pending_nvm.eetrack;
snprintf(ctx->buf, sizeof(ctx->buf), "0x%08x", eetrack);
return;
}
ixgbe_get_oem_prod_version(hw, &nvm_ver);
/* No ETRACK version for OEM */
if (nvm_ver.oem_valid) {
ctx->buf[0] = '\0';
return;
}
ixgbe_get_etk_id(hw, &nvm_ver);
snprintf(ctx->buf, sizeof(ctx->buf), "0x%08x", nvm_ver.etk_id);
}
static void ixgbe_info_fw_api(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx)
{
struct ixgbe_hw *hw = &adapter->hw;
snprintf(ctx->buf, sizeof(ctx->buf), "%u.%u.%u",
hw->api_maj_ver, hw->api_min_ver, hw->api_patch);
}
static void ixgbe_info_fw_build(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx)
{
struct ixgbe_hw *hw = &adapter->hw;
snprintf(ctx->buf, sizeof(ctx->buf), "0x%08x", hw->fw_build);
}
static void ixgbe_info_fw_srev(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx,
enum ixgbe_devlink_version_type type)
{
struct ixgbe_nvm_info *nvm = &adapter->hw.flash.nvm;
if (type == IXGBE_DL_VERSION_STORED &&
ctx->dev_caps.common_cap.nvm_update_pending_nvm)
nvm = &ctx->pending_nvm;
snprintf(ctx->buf, sizeof(ctx->buf), "%u", nvm->srev);
}
static void ixgbe_info_orom_srev(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx,
enum ixgbe_devlink_version_type type)
{
struct ixgbe_orom_info *orom = &adapter->hw.flash.orom;
if (type == IXGBE_DL_VERSION_STORED &&
ctx->dev_caps.common_cap.nvm_update_pending_orom)
orom = &ctx->pending_orom;
snprintf(ctx->buf, sizeof(ctx->buf), "%u", orom->srev);
}
static void ixgbe_info_nvm_ver(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx,
enum ixgbe_devlink_version_type type)
{
struct ixgbe_nvm_info *nvm = &adapter->hw.flash.nvm;
if (type == IXGBE_DL_VERSION_STORED &&
ctx->dev_caps.common_cap.nvm_update_pending_nvm)
nvm = &ctx->pending_nvm;
snprintf(ctx->buf, sizeof(ctx->buf), "%x.%02x", nvm->major, nvm->minor);
}
static void ixgbe_info_netlist_ver(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx,
enum ixgbe_devlink_version_type type)
{
struct ixgbe_netlist_info *netlist = &adapter->hw.flash.netlist;
if (type == IXGBE_DL_VERSION_STORED &&
ctx->dev_caps.common_cap.nvm_update_pending_netlist)
netlist = &ctx->pending_netlist;
/* The netlist version fields are BCD formatted */
snprintf(ctx->buf, sizeof(ctx->buf), "%x.%x.%x-%x.%x.%x",
netlist->major, netlist->minor,
netlist->type >> 16, netlist->type & 0xFFFF,
netlist->rev, netlist->cust_ver);
}
static void ixgbe_info_netlist_build(struct ixgbe_adapter *adapter,
struct ixgbe_info_ctx *ctx,
enum ixgbe_devlink_version_type type)
{
struct ixgbe_netlist_info *netlist = &adapter->hw.flash.netlist;
if (type == IXGBE_DL_VERSION_STORED &&
ctx->dev_caps.common_cap.nvm_update_pending_netlist)
netlist = &ctx->pending_netlist;
snprintf(ctx->buf, sizeof(ctx->buf), "0x%08x", netlist->hash);
}
static int ixgbe_set_ctx_dev_caps(struct ixgbe_hw *hw,
struct ixgbe_info_ctx *ctx,
struct netlink_ext_ack *extack)
{
bool *pending_orom, *pending_nvm, *pending_netlist;
int err;
err = ixgbe_discover_dev_caps(hw, &ctx->dev_caps);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"Unable to discover device capabilities");
return err;
}
pending_orom = &ctx->dev_caps.common_cap.nvm_update_pending_orom;
pending_nvm = &ctx->dev_caps.common_cap.nvm_update_pending_nvm;
pending_netlist = &ctx->dev_caps.common_cap.nvm_update_pending_netlist;
if (*pending_orom) {
err = ixgbe_get_inactive_orom_ver(hw, &ctx->pending_orom);
if (err)
*pending_orom = false;
}
if (*pending_nvm) {
err = ixgbe_get_inactive_nvm_ver(hw, &ctx->pending_nvm);
if (err)
*pending_nvm = false;
}
if (*pending_netlist) {
err = ixgbe_get_inactive_netlist_ver(hw, &ctx->pending_netlist);
if (err)
*pending_netlist = false;
}
return 0;
}
static int ixgbe_devlink_info_get_e610(struct ixgbe_adapter *adapter,
struct devlink_info_req *req,
struct ixgbe_info_ctx *ctx)
{
int err;
ixgbe_info_fw_api(adapter, ctx);
err = devlink_info_version_running_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW_MGMT_API,
ctx->buf);
if (err)
return err;
ixgbe_info_fw_build(adapter, ctx);
err = devlink_info_version_running_put(req, "fw.mgmt.build", ctx->buf);
if (err)
return err;
ixgbe_info_fw_srev(adapter, ctx, IXGBE_DL_VERSION_RUNNING);
err = devlink_info_version_running_put(req, "fw.mgmt.srev", ctx->buf);
if (err)
return err;
ixgbe_info_orom_srev(adapter, ctx, IXGBE_DL_VERSION_RUNNING);
err = devlink_info_version_running_put(req, "fw.undi.srev", ctx->buf);
if (err)
return err;
ixgbe_info_nvm_ver(adapter, ctx, IXGBE_DL_VERSION_RUNNING);
err = devlink_info_version_running_put(req, "fw.psid.api", ctx->buf);
if (err)
return err;
ixgbe_info_netlist_ver(adapter, ctx, IXGBE_DL_VERSION_RUNNING);
err = devlink_info_version_running_put(req, "fw.netlist", ctx->buf);
if (err)
return err;
ixgbe_info_netlist_build(adapter, ctx, IXGBE_DL_VERSION_RUNNING);
return devlink_info_version_running_put(req, "fw.netlist.build",
ctx->buf);
}
static int
ixgbe_devlink_pending_info_get_e610(struct ixgbe_adapter *adapter,
struct devlink_info_req *req,
struct ixgbe_info_ctx *ctx)
{
int err;
ixgbe_info_orom_ver(adapter, ctx, IXGBE_DL_VERSION_STORED);
err = devlink_info_version_stored_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW_UNDI,
ctx->buf);
if (err)
return err;
ixgbe_info_eetrack(adapter, ctx, IXGBE_DL_VERSION_STORED);
err = devlink_info_version_stored_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW_BUNDLE_ID,
ctx->buf);
if (err)
return err;
ixgbe_info_fw_srev(adapter, ctx, IXGBE_DL_VERSION_STORED);
err = devlink_info_version_stored_put(req, "fw.mgmt.srev", ctx->buf);
if (err)
return err;
ixgbe_info_orom_srev(adapter, ctx, IXGBE_DL_VERSION_STORED);
err = devlink_info_version_stored_put(req, "fw.undi.srev", ctx->buf);
if (err)
return err;
ixgbe_info_nvm_ver(adapter, ctx, IXGBE_DL_VERSION_STORED);
err = devlink_info_version_stored_put(req, "fw.psid.api", ctx->buf);
if (err)
return err;
ixgbe_info_netlist_ver(adapter, ctx, IXGBE_DL_VERSION_STORED);
err = devlink_info_version_stored_put(req, "fw.netlist", ctx->buf);
if (err)
return err;
ixgbe_info_netlist_build(adapter, ctx, IXGBE_DL_VERSION_STORED);
return devlink_info_version_stored_put(req, "fw.netlist.build",
ctx->buf);
}
static int ixgbe_devlink_info_get(struct devlink *devlink,
struct devlink_info_req *req,
struct netlink_ext_ack *extack)
{
struct ixgbe_adapter *adapter = devlink_priv(devlink);
struct ixgbe_hw *hw = &adapter->hw;
struct ixgbe_info_ctx *ctx;
int err;
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
if (hw->mac.type == ixgbe_mac_e610)
ixgbe_refresh_fw_version(adapter);
ixgbe_info_get_dsn(adapter, ctx);
err = devlink_info_serial_number_put(req, ctx->buf);
if (err)
goto free_ctx;
err = hw->eeprom.ops.read_pba_string(hw, ctx->buf, sizeof(ctx->buf));
if (err)
goto free_ctx;
err = devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_BOARD_ID,
ctx->buf);
if (err)
goto free_ctx;
ixgbe_info_orom_ver(adapter, ctx, IXGBE_DL_VERSION_RUNNING);
err = devlink_info_version_running_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW_UNDI,
ctx->buf);
if (err)
goto free_ctx;
ixgbe_info_eetrack(adapter, ctx, IXGBE_DL_VERSION_RUNNING);
err = devlink_info_version_running_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW_BUNDLE_ID,
ctx->buf);
if (err || hw->mac.type != ixgbe_mac_e610)
goto free_ctx;
err = ixgbe_set_ctx_dev_caps(hw, ctx, extack);
if (err)
goto free_ctx;
err = ixgbe_devlink_info_get_e610(adapter, req, ctx);
if (err)
goto free_ctx;
err = ixgbe_devlink_pending_info_get_e610(adapter, req, ctx);
free_ctx:
kfree(ctx);
return err;
}
/**
* ixgbe_devlink_reload_empr_start - Start EMP reset to activate new firmware
* @devlink: pointer to the devlink instance to reload
* @netns_change: if true, the network namespace is changing
* @action: the action to perform. Must be DEVLINK_RELOAD_ACTION_FW_ACTIVATE
* @limit: limits on what reload should do, such as not resetting
* @extack: netlink extended ACK structure
*
* Allow user to activate new Embedded Management Processor firmware by
* issuing device specific EMP reset. Called in response to
* a DEVLINK_CMD_RELOAD with the DEVLINK_RELOAD_ACTION_FW_ACTIVATE.
*
* Note that teardown and rebuild of the driver state happens automatically as
* part of an interrupt and watchdog task. This is because all physical
* functions on the device must be able to reset when an EMP reset occurs from
* any source.
*
* Return: the exit code of the operation.
*/
static int ixgbe_devlink_reload_empr_start(struct devlink *devlink,
bool netns_change,
enum devlink_reload_action action,
enum devlink_reload_limit limit,
struct netlink_ext_ack *extack)
{
struct ixgbe_adapter *adapter = devlink_priv(devlink);
struct ixgbe_hw *hw = &adapter->hw;
u8 pending;
int err;
if (hw->mac.type != ixgbe_mac_e610)
return -EOPNOTSUPP;
err = ixgbe_get_pending_updates(adapter, &pending, extack);
if (err)
return err;
/* Pending is a bitmask of which flash banks have a pending update,
* including the main NVM bank, the Option ROM bank, and the netlist
* bank. If any of these bits are set, then there is a pending update
* waiting to be activated.
*/
if (!pending) {
NL_SET_ERR_MSG_MOD(extack, "No pending firmware update");
return -ECANCELED;
}
if (adapter->fw_emp_reset_disabled) {
NL_SET_ERR_MSG_MOD(extack,
"EMP reset is not available. To activate firmware, a reboot or power cycle is needed");
return -ECANCELED;
}
err = ixgbe_aci_nvm_update_empr(hw);
if (err)
NL_SET_ERR_MSG_MOD(extack,
"Failed to trigger EMP device reset to reload firmware");
return err;
}
/*Wait for 10 sec with 0.5 sec tic. EMPR takes no less than half of a sec */
#define IXGBE_DEVLINK_RELOAD_TIMEOUT_SEC 20
/**
* ixgbe_devlink_reload_empr_finish - finishes EMP reset
* @devlink: pointer to the devlink instance
* @action: the action to perform.
* @limit: limits on what reload should do
* @actions_performed: actions performed
* @extack: netlink extended ACK structure
*
* Wait for new NVM to be loaded during EMP reset.
*
* Return: -ETIME when timer is exceeded, 0 on success.
*/
static int ixgbe_devlink_reload_empr_finish(struct devlink *devlink,
enum devlink_reload_action action,
enum devlink_reload_limit limit,
u32 *actions_performed,
struct netlink_ext_ack *extack)
{
struct ixgbe_adapter *adapter = devlink_priv(devlink);
struct ixgbe_hw *hw = &adapter->hw;
int i = 0;
u32 fwsm;
do {
/* Just right away after triggering EMP reset the FWSM register
* may be not cleared yet, so begin the loop with the delay
* in order to not check the not updated register.
*/
mdelay(500);
fwsm = IXGBE_READ_REG(hw, IXGBE_FWSM(hw));
if (i++ >= IXGBE_DEVLINK_RELOAD_TIMEOUT_SEC)
return -ETIME;
} while (!(fwsm & IXGBE_FWSM_FW_VAL_BIT));
*actions_performed = BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE);
adapter->flags2 &= ~(IXGBE_FLAG2_API_MISMATCH |
IXGBE_FLAG2_FW_ROLLBACK);
return 0;
}
static const struct devlink_ops ixgbe_devlink_ops = {
.info_get = ixgbe_devlink_info_get,
.supported_flash_update_params =
DEVLINK_SUPPORT_FLASH_UPDATE_OVERWRITE_MASK,
.flash_update = ixgbe_flash_pldm_image,
.reload_actions = BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE),
.reload_down = ixgbe_devlink_reload_empr_start,
.reload_up = ixgbe_devlink_reload_empr_finish,
};
/**
* ixgbe_allocate_devlink - Allocate devlink instance
* @dev: device to allocate devlink for
*
* Allocate a devlink instance for this physical function.
*
* Return: pointer to the device adapter structure on success,
* ERR_PTR(-ENOMEM) when allocation failed.
*/
struct ixgbe_adapter *ixgbe_allocate_devlink(struct device *dev)
{
struct ixgbe_adapter *adapter;
struct devlink *devlink;
devlink = devlink_alloc(&ixgbe_devlink_ops, sizeof(*adapter), dev);
if (!devlink)
return ERR_PTR(-ENOMEM);
adapter = devlink_priv(devlink);
adapter->devlink = devlink;
return adapter;
}
/**
* ixgbe_devlink_set_switch_id - Set unique switch ID based on PCI DSN
* @adapter: pointer to the device adapter structure
* @ppid: struct with switch id information
*/
static void ixgbe_devlink_set_switch_id(struct ixgbe_adapter *adapter,
struct netdev_phys_item_id *ppid)
{
u64 id = pci_get_dsn(adapter->pdev);
ppid->id_len = sizeof(id);
put_unaligned_be64(id, &ppid->id);
}
/**
* ixgbe_devlink_register_port - Register devlink port
* @adapter: pointer to the device adapter structure
*
* Create and register a devlink_port for this physical function.
*
* Return: 0 on success, error code on failure.
*/
int ixgbe_devlink_register_port(struct ixgbe_adapter *adapter)
{
struct devlink_port *devlink_port = &adapter->devlink_port;
struct devlink *devlink = adapter->devlink;
struct device *dev = &adapter->pdev->dev;
struct devlink_port_attrs attrs = {};
int err;
attrs.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL;
attrs.phys.port_number = adapter->hw.bus.func;
ixgbe_devlink_set_switch_id(adapter, &attrs.switch_id);
devlink_port_attrs_set(devlink_port, &attrs);
err = devl_port_register(devlink, devlink_port, 0);
if (err) {
dev_err(dev,
"devlink port registration failed, err %d\n", err);
}
return err;
}