root/drivers/net/ethernet/intel/ixgbe/devlink/region.c
// 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, LIBIE_AQC_RES_ACCESS_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, LIBIE_AQC_RES_ACCESS_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);
}