Contributors: 7
Author Tokens Token Proportion Commits Commit Proportion
Jedrzej Jagielski 2322 96.47% 8 57.14%
Peter P. Waskiewicz Jr 55 2.29% 1 7.14%
Auke-Jan H Kok 14 0.58% 1 7.14%
Shannon Nelson 9 0.37% 1 7.14%
Andrii Staikov 4 0.17% 1 7.14%
Alexander Duyck 2 0.08% 1 7.14%
Jeff Kirsher 1 0.04% 1 7.14%
Total 2407 14


// 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;
}