Contributors: 9
Author Tokens Token Proportion Commits Commit Proportion
Slawomir Mrozowicz 975 95.96% 1 11.11%
Auke-Jan H Kok 10 0.98% 1 11.11%
Peter P. Waskiewicz Jr 10 0.98% 1 11.11%
Jesse Brandeburg 9 0.89% 1 11.11%
Greg Rose 7 0.69% 1 11.11%
Alexander Duyck 2 0.20% 1 11.11%
Jacob E Keller 1 0.10% 1 11.11%
Jeff Kirsher 1 0.10% 1 11.11%
Joe Perches 1 0.10% 1 11.11%
Total 1016 9


// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025, Intel Corporation. */

#include "ixgbe.h"
#include "devlink.h"

#define IXGBE_DEVLINK_READ_BLK_SIZE (1024 * 1024)

static const struct devlink_region_ops ixgbe_nvm_region_ops;
static const struct devlink_region_ops ixgbe_sram_region_ops;

static int ixgbe_devlink_parse_region(struct ixgbe_hw *hw,
				      const struct devlink_region_ops *ops,
				      bool *read_shadow_ram, u32 *nvm_size)
{
	if (ops == &ixgbe_nvm_region_ops) {
		*read_shadow_ram = false;
		*nvm_size = hw->flash.flash_size;
	} else if (ops == &ixgbe_sram_region_ops) {
		*read_shadow_ram = true;
		*nvm_size = hw->flash.sr_words * 2u;
	} else {
		return -EOPNOTSUPP;
	}

	return 0;
}

/**
 * ixgbe_devlink_nvm_snapshot - Capture a snapshot of the NVM content
 * @devlink: the devlink instance
 * @ops: the devlink region being snapshotted
 * @extack: extended ACK response structure
 * @data: on exit points to snapshot data buffer
 *
 * This function is called in response to the DEVLINK_CMD_REGION_NEW cmd.
 *
 * Capture a snapshot of the whole requested NVM region.
 *
 * No need to worry with freeing @data, devlink core takes care if it.
 *
 * Return: 0 on success, -EOPNOTSUPP for unsupported regions, -EBUSY when
 * cannot lock NVM, -ENOMEM when cannot alloc mem and -EIO when error
 * occurs during reading.
 */
static int ixgbe_devlink_nvm_snapshot(struct devlink *devlink,
				      const struct devlink_region_ops *ops,
				      struct netlink_ext_ack *extack, u8 **data)
{
	struct ixgbe_adapter *adapter = devlink_priv(devlink);
	struct ixgbe_hw *hw = &adapter->hw;
	bool read_shadow_ram;
	u8 *nvm_data, *buf;
	u32 nvm_size, left;
	u8 num_blks;
	int err;

	err = ixgbe_devlink_parse_region(hw, ops, &read_shadow_ram, &nvm_size);
	if (err)
		return err;

	nvm_data = kvzalloc(nvm_size, GFP_KERNEL);
	if (!nvm_data)
		return -ENOMEM;

	num_blks = DIV_ROUND_UP(nvm_size, IXGBE_DEVLINK_READ_BLK_SIZE);
	buf = nvm_data;
	left = nvm_size;

	for (int i = 0; i < num_blks; i++) {
		u32 read_sz = min_t(u32, IXGBE_DEVLINK_READ_BLK_SIZE, left);

		/* Need to acquire NVM lock during each loop run because the
		 * total period of reading whole NVM is longer than the maximum
		 * period the lock can be taken defined by the IXGBE_NVM_TIMEOUT.
		 */
		err = ixgbe_acquire_nvm(hw, IXGBE_RES_READ);
		if (err) {
			NL_SET_ERR_MSG_MOD(extack,
					   "Failed to acquire NVM semaphore");
			kvfree(nvm_data);
			return -EBUSY;
		}

		err = ixgbe_read_flat_nvm(hw, i * IXGBE_DEVLINK_READ_BLK_SIZE,
					  &read_sz, buf, read_shadow_ram);
		if (err) {
			NL_SET_ERR_MSG_MOD(extack,
					   "Failed to read RAM content");
			ixgbe_release_nvm(hw);
			kvfree(nvm_data);
			return -EIO;
		}

		ixgbe_release_nvm(hw);

		buf += read_sz;
		left -= read_sz;
	}

	*data = nvm_data;
	return 0;
}

/**
 * ixgbe_devlink_devcaps_snapshot - Capture a snapshot of device capabilities
 * @devlink: the devlink instance
 * @ops: the devlink region being snapshotted
 * @extack: extended ACK response structure
 * @data: on exit points to snapshot data buffer
 *
 * This function is called in response to the DEVLINK_CMD_REGION_NEW for
 * the device-caps devlink region.
 *
 * Capture a snapshot of the device capabilities reported by firmware.
 *
 * No need to worry with freeing @data, devlink core takes care if it.
 *
 * Return: 0 on success, -ENOMEM when cannot alloc mem, or return code of
 * the reading operation.
 */
static int ixgbe_devlink_devcaps_snapshot(struct devlink *devlink,
					  const struct devlink_region_ops *ops,
					  struct netlink_ext_ack *extack,
					  u8 **data)
{
	struct ixgbe_adapter *adapter = devlink_priv(devlink);
	struct ixgbe_aci_cmd_list_caps_elem *caps;
	struct ixgbe_hw *hw = &adapter->hw;
	int err;

	caps = kvzalloc(IXGBE_ACI_MAX_BUFFER_SIZE, GFP_KERNEL);
	if (!caps)
		return -ENOMEM;

	err = ixgbe_aci_list_caps(hw, caps, IXGBE_ACI_MAX_BUFFER_SIZE, NULL,
				  ixgbe_aci_opc_list_dev_caps);
	if (err) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Failed to read device capabilities");
		kvfree(caps);
		return err;
	}

	*data = (u8 *)caps;
	return 0;
}

/**
 * ixgbe_devlink_nvm_read - Read a portion of NVM flash content
 * @devlink: the devlink instance
 * @ops: the devlink region to snapshot
 * @extack: extended ACK response structure
 * @offset: the offset to start at
 * @size: the amount to read
 * @data: the data buffer to read into
 *
 * This function is called in response to DEVLINK_CMD_REGION_READ to directly
 * read a section of the NVM contents.
 *
 * Read from either the nvm-flash region either shadow-ram region.
 *
 * Return: 0 on success, -EOPNOTSUPP for unsupported regions, -EBUSY when
 * cannot lock NVM, -ERANGE when buffer limit exceeded and -EIO when error
 * occurs during reading.
 */
static int ixgbe_devlink_nvm_read(struct devlink *devlink,
				  const struct devlink_region_ops *ops,
				  struct netlink_ext_ack *extack,
				  u64 offset, u32 size, u8 *data)
{
	struct ixgbe_adapter *adapter = devlink_priv(devlink);
	struct ixgbe_hw *hw = &adapter->hw;
	bool read_shadow_ram;
	u32 nvm_size;
	int err;

	err = ixgbe_devlink_parse_region(hw, ops, &read_shadow_ram, &nvm_size);
	if (err)
		return err;

	if (offset + size > nvm_size) {
		NL_SET_ERR_MSG_MOD(extack, "Cannot read beyond the region size");
		return -ERANGE;
	}

	err = ixgbe_acquire_nvm(hw, IXGBE_RES_READ);
	if (err) {
		NL_SET_ERR_MSG_MOD(extack, "Failed to acquire NVM semaphore");
		return -EBUSY;
	}

	err = ixgbe_read_flat_nvm(hw, (u32)offset, &size, data, read_shadow_ram);
	if (err) {
		NL_SET_ERR_MSG_MOD(extack, "Failed to read NVM contents");
		ixgbe_release_nvm(hw);
		return -EIO;
	}

	ixgbe_release_nvm(hw);
	return 0;
}

static const struct devlink_region_ops ixgbe_nvm_region_ops = {
	.name = "nvm-flash",
	.destructor = kvfree,
	.snapshot = ixgbe_devlink_nvm_snapshot,
	.read = ixgbe_devlink_nvm_read,
};

static const struct devlink_region_ops ixgbe_sram_region_ops = {
	.name = "shadow-ram",
	.destructor = kvfree,
	.snapshot = ixgbe_devlink_nvm_snapshot,
	.read = ixgbe_devlink_nvm_read,
};

static const struct devlink_region_ops ixgbe_devcaps_region_ops = {
	.name = "device-caps",
	.destructor = kvfree,
	.snapshot = ixgbe_devlink_devcaps_snapshot,
};

/**
 * ixgbe_devlink_init_regions - Initialize devlink regions
 * @adapter: adapter instance
 *
 * Create devlink regions used to enable access to dump the contents of the
 * flash memory of the device.
 */
void ixgbe_devlink_init_regions(struct ixgbe_adapter *adapter)
{
	struct devlink *devlink = adapter->devlink;
	struct device *dev = &adapter->pdev->dev;
	u64 nvm_size, sram_size;

	if (adapter->hw.mac.type != ixgbe_mac_e610)
		return;

	nvm_size = adapter->hw.flash.flash_size;
	adapter->nvm_region = devl_region_create(devlink, &ixgbe_nvm_region_ops,
						 1, nvm_size);
	if (IS_ERR(adapter->nvm_region)) {
		dev_err(dev,
			"Failed to create NVM devlink region, err %ld\n",
			PTR_ERR(adapter->nvm_region));
		adapter->nvm_region = NULL;
	}

	sram_size = adapter->hw.flash.sr_words * 2u;
	adapter->sram_region = devl_region_create(devlink, &ixgbe_sram_region_ops,
						  1, sram_size);
	if (IS_ERR(adapter->sram_region)) {
		dev_err(dev,
			"Failed to create shadow-ram devlink region, err %ld\n",
			PTR_ERR(adapter->sram_region));
		adapter->sram_region = NULL;
	}

	adapter->devcaps_region = devl_region_create(devlink,
						     &ixgbe_devcaps_region_ops,
						     10, IXGBE_ACI_MAX_BUFFER_SIZE);
	if (IS_ERR(adapter->devcaps_region)) {
		dev_err(dev,
			"Failed to create device-caps devlink region, err %ld\n",
			PTR_ERR(adapter->devcaps_region));
		adapter->devcaps_region = NULL;
	}
}

/**
 * ixgbe_devlink_destroy_regions - Destroy devlink regions
 * @adapter: adapter instance
 *
 * Remove previously created regions for this adapter instance.
 */
void ixgbe_devlink_destroy_regions(struct ixgbe_adapter *adapter)
{
	if (adapter->hw.mac.type != ixgbe_mac_e610)
		return;

	if (adapter->nvm_region)
		devl_region_destroy(adapter->nvm_region);

	if (adapter->sram_region)
		devl_region_destroy(adapter->sram_region);

	if (adapter->devcaps_region)
		devl_region_destroy(adapter->devcaps_region);
}