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