AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/31 13:30 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the pds_core driver, which is a vendor-specific SmartNIC driver that requires physical PCIe hardware not emulated in standard QEMU.",
  "WorthFuzzing": false
}

1/1 2026/07/31 13:30 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 1dabafcb2e9b03b4e0967c943e5434c59ed2a7df\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Jul 31 13:30:32 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst\nindex 9e8a16c441029..49487a35c1632 100644\n--- a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst\n+++ b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst\n@@ -102,6 +102,95 @@ currently in use, and that bank will used for the next boot::\n   # devlink dev flash pci/0000:b5:00.0 \\\n             file pensando/dsc_fw_1.63.0-22.tar\n \n+Firmware Management (PLDM)\n+==========================\n+\n+Firmware that supports PLDM can be updated using the devlink flash command\n+with a PLDM firmware package. The entire package can be updated at once::\n+\n+  # devlink dev flash pci/0000:b5:00.0 file firmware.pldmfw\n+\n+Individual components can also be updated by specifying the component name::\n+\n+  # devlink dev flash pci/0000:b5:00.0 \\\n+            file firmware.pldmfw component fw.cpld\n+\n+Per-component update uses driver-defined component names (fw, fw.cpld,\n+etc.). Not all components support per-component update -\n+devlink will reject the request if the specified component cannot\n+be updated.\n+\n+Gold (recovery) components can be updated by specifying the base component\n+name (e.g., ``fw`` for ``fw.gold``) with a goldfw package file when the\n+device supports per-component update. The ``.gold`` suffix in devlink info\n+output indicates the gold slot version, not a flash target.\n+\n+Info versions (PLDM)\n+====================\n+\n+Firmware that supports PLDM reports component versions using driver-defined\n+names. The driver reports the following component versions:\n+\n+.. list-table:: devlink info versions for PLDM-capable firmware\n+   :widths: 5 5 90\n+\n+   * - Name\n+     - Type\n+     - Description\n+   * - ``fw``\n+     - running, stored\n+     - Main firmware\n+   * - ``fw.gold``\n+     - stored\n+     - Gold (recovery) firmware\n+   * - ``fw.bootloader``\n+     - running, stored\n+     - Boot loader\n+   * - ``fw.cpld``\n+     - running, stored\n+     - CPLD\n+   * - ``fw.secure``\n+     - running, stored\n+     - Secure boot firmware\n+   * - ``fw.fpga``\n+     - running, stored\n+     - FPGA configuration\n+   * - ``fw.suc``\n+     - running, stored\n+     - System Unit Controller firmware\n+   * - ``fw.suc.bootloader``\n+     - running, stored\n+     - System Unit Controller bootloader\n+   * - ``fw.uboot``\n+     - running, stored\n+     - U-Boot bootloader\n+   * - ``asic.id``\n+     - fixed\n+     - The ASIC type for this device\n+   * - ``asic.rev``\n+     - fixed\n+     - The revision of the ASIC for this device\n+\n+Example output::\n+\n+  $ devlink dev info pci/0000:00:05.0\n+  pci/0000:00:05.0:\n+    driver pds_core\n+    serial_number FLM18420073\n+    versions:\n+        fixed:\n+          asic.id 0x0\n+          asic.rev 0x0\n+        running:\n+          fw.bootloader 1.2.3\n+          fw 1.3.0\n+          fw.cpld 3.18\n+        stored:\n+          fw.bootloader 1.2.3\n+          fw.gold 1.2.0\n+          fw 1.3.0\n+          fw.cpld 3.18\n+\n Health Reporters\n ================\n \ndiff --git a/drivers/net/ethernet/amd/Kconfig b/drivers/net/ethernet/amd/Kconfig\nindex e35991141a1a2..743e3d4b6b94e 100644\n--- a/drivers/net/ethernet/amd/Kconfig\n+++ b/drivers/net/ethernet/amd/Kconfig\n@@ -171,6 +171,7 @@ config PDS_CORE\n \tdepends on 64BIT \u0026\u0026 PCI\n \tselect AUXILIARY_BUS\n \tselect NET_DEVLINK\n+\tselect PLDMFW\n \thelp\n \t  This enables the support for the AMD/Pensando Core device family of\n \t  adapters.  More specific information on this driver can be\ndiff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c\nindex 04ec2569c61c0..922e3ec8af1b9 100644\n--- a/drivers/net/ethernet/amd/pds_core/core.c\n+++ b/drivers/net/ethernet/amd/pds_core/core.c\n@@ -489,6 +489,15 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing)\n \t\tpdsc_devcmd_reset(pdsc);\n \n \tpci_clear_master(pdsc-\u003epdev);\n+\tif (!pdsc-\u003epdev-\u003eis_virtfn) {\n+\t\tu16 val;\n+\n+\t\t/* Flush any in-flight DMA before freeing buffers.\n+\t\t * A config read completion cannot return until all prior\n+\t\t * device-initiated memory writes have completed.\n+\t\t */\n+\t\tpci_read_config_word(pdsc-\u003epdev, PCI_VENDOR_ID, \u0026val);\n+\t}\n \n \tpdsc_core_uninit(pdsc);\n \n@@ -497,6 +506,8 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing)\n \t\tpdsc-\u003eviftype_status = NULL;\n \t}\n \n+\tpdsc_debugfs_del_host_mem(pdsc);\n+\tpdsc_host_mem_free(pdsc);\n \tpdsc_dev_uninit(pdsc);\n \n \tset_bit(PDSC_S_FW_DEAD, \u0026pdsc-\u003estate);\n@@ -506,6 +517,8 @@ int pdsc_start(struct pdsc *pdsc)\n {\n \tpds_core_intr_mask(\u0026pdsc-\u003eintr_ctrl[pdsc-\u003eadminqcq.intx],\n \t\t\t   PDS_CORE_INTR_MASK_CLEAR);\n+\tpdsc_host_mem_add(pdsc);\n+\tpdsc_debugfs_add_host_mem(pdsc);\n \n \treturn 0;\n }\n@@ -587,6 +600,8 @@ void pdsc_fw_up(struct pdsc *pdsc)\n \t\treturn;\n \t}\n \n+\tpdsc_fw_components_invalidate(pdsc);\n+\n \terr = pdsc_setup(pdsc, PDSC_SETUP_RECOVERY);\n \tif (err)\n \t\tgoto err_out;\n@@ -670,3 +685,161 @@ void pdsc_health_thread(struct work_struct *work)\n out_unlock:\n \tmutex_unlock(\u0026pdsc-\u003econfig_lock);\n }\n+\n+static void pdsc_host_mem_del_one(struct pdsc *pdsc, u16 tag, u8 reason)\n+{\n+\tunion pds_core_dev_comp comp = {};\n+\tunion pds_core_dev_cmd cmd = {\n+\t\t.host_mem.opcode = PDS_CORE_CMD_HOST_MEM,\n+\t\t.host_mem.oper = PDS_CORE_HOST_MEM_DEL,\n+\t\t.host_mem.tag = cpu_to_le16(tag),\n+\t\t.host_mem.reason = reason,\n+\t};\n+\n+\tdev_dbg(pdsc-\u003edev, \"Sending devcmd for mem del tag %d\\n\", tag);\n+\tpdsc_devcmd(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout);\n+}\n+\n+static int pdsc_host_mem_add_one(struct pdsc *pdsc, int index)\n+{\n+\tstruct pdsc_host_mem *hm = \u0026pdsc-\u003ehost_mem_reqs[index];\n+\tunion pds_core_dev_comp comp = {};\n+\tunion pds_core_dev_cmd cmd = {};\n+\tint err;\n+\n+\tcmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;\n+\tcmd.host_mem.oper = PDS_CORE_HOST_MEM_QUERY;\n+\tcmd.host_mem.index = cpu_to_le16(index);\n+\tdev_dbg(pdsc-\u003edev, \"Sending devcmd for mem query index %d\\n\", index);\n+\terr = pdsc_devcmd(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout);\n+\tif (err || comp.status != PDS_RC_SUCCESS) {\n+\t\tdev_err(pdsc-\u003edev, \"mem query failed err %d status %d\\n\",\n+\t\t\terr, comp.status);\n+\t\treturn err ? err : -EIO;\n+\t}\n+\thm-\u003esize = le32_to_cpu(comp.host_mem.size);\n+\thm-\u003etag = le16_to_cpu(comp.host_mem.tag);\n+\tdev_dbg(pdsc-\u003edev, \"mem query returned size %d tag %d\\n\",\n+\t\thm-\u003esize, hm-\u003etag);\n+\n+\tif (!hm-\u003esize || hm-\u003esize \u003e PDSC_HOST_MEM_MAX_CONTIG) {\n+\t\tdev_err(pdsc-\u003edev, \"invalid size %d for tag %d\\n\",\n+\t\t\thm-\u003esize, hm-\u003etag);\n+\t\terr = -EINVAL;\n+\t\tgoto err_del;\n+\t}\n+\n+\thm-\u003eorder = get_order(hm-\u003esize);\n+\thm-\u003epg = alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN, hm-\u003eorder);\n+\tif (!hm-\u003epg) {\n+\t\tdev_warn(pdsc-\u003edev, \"alloc order %d failed for tag %d\\n\",\n+\t\t\t hm-\u003eorder, hm-\u003etag);\n+\t\terr = -ENOMEM;\n+\t\tgoto err_del;\n+\t}\n+\n+\thm-\u003epa = dma_map_page(pdsc-\u003edev, hm-\u003epg, 0, hm-\u003esize,\n+\t\t\t      DMA_BIDIRECTIONAL);\n+\tif (dma_mapping_error(pdsc-\u003edev, hm-\u003epa)) {\n+\t\tdev_err(pdsc-\u003edev, \"dma map failed for tag %d size %d\\n\",\n+\t\t\thm-\u003etag, hm-\u003esize);\n+\t\t__free_pages(hm-\u003epg, hm-\u003eorder);\n+\t\thm-\u003epg = NULL;\n+\t\terr = -EIO;\n+\t\tgoto err_del;\n+\t}\n+\n+\t/* Track this allocation so pdsc_host_mem_free() can clean it up */\n+\tpdsc-\u003enum_host_mem_reqs++;\n+\n+\tmemset(\u0026cmd, 0, sizeof(cmd));\n+\tmemset(\u0026comp, 0, sizeof(comp));\n+\tcmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;\n+\tcmd.host_mem.oper = PDS_CORE_HOST_MEM_ADD;\n+\tcmd.host_mem.tag = cpu_to_le16(hm-\u003etag);\n+\tcmd.host_mem.size = cpu_to_le32(hm-\u003esize);\n+\tcmd.host_mem.buf_pa = cpu_to_le64(hm-\u003epa);\n+\n+\tdev_dbg(pdsc-\u003edev, \"Sending devcmd for mem add tag %d size %d pa %pad\\n\",\n+\t\thm-\u003etag, hm-\u003esize, \u0026hm-\u003epa);\n+\terr = pdsc_devcmd(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout);\n+\tif (err || comp.status != PDS_RC_SUCCESS) {\n+\t\tdev_err(pdsc-\u003edev, \"mem add failed err %d status %d for tag %d\\n\",\n+\t\t\terr, comp.status, hm-\u003etag);\n+\t\terr = err ? err : -EIO;\n+\t\tgoto err_del;\n+\t}\n+\tdev_dbg(pdsc-\u003edev, \"mem add completed for tag %d\\n\", hm-\u003etag);\n+\n+\treturn 0;\n+\n+err_del:\n+\t/* After MEM_QUERY succeeds, firmware expects MEM_ADD or MEM_DEL */\n+\tpdsc_host_mem_del_one(pdsc, hm-\u003etag, PDS_RC_ENOMEM);\n+\treturn err;\n+}\n+\n+void pdsc_host_mem_add(struct pdsc *pdsc)\n+{\n+\tunion pds_core_dev_comp comp = {};\n+\tunion pds_core_dev_cmd cmd = {};\n+\tu16 count;\n+\tint err;\n+\tint i;\n+\n+\tif (!(pdsc-\u003edev_ident.capabilities \u0026\n+\t     cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM)))\n+\t\treturn;\n+\n+\tcmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;\n+\tcmd.host_mem.oper = PDS_CORE_HOST_MEM_GET_COUNT;\n+\tcmd.host_mem.index = cpu_to_le16(PDSC_HOST_MEM_MAX_COUNT);\n+\tcmd.host_mem.max_contig = cpu_to_le32(PDSC_HOST_MEM_MAX_CONTIG);\n+\tdev_dbg(pdsc-\u003edev, \"Sending devcmd for mem get count max_contig %u\\n\",\n+\t\tPDSC_HOST_MEM_MAX_CONTIG);\n+\terr = pdsc_devcmd(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout);\n+\tif (err || comp.status != PDS_RC_SUCCESS) {\n+\t\tdev_err(pdsc-\u003edev, \"mem get count failed err %d status %d\\n\",\n+\t\t\terr, comp.status);\n+\t\treturn;\n+\t}\n+\n+\tcount = min(le16_to_cpu(comp.host_mem.count),\n+\t\t    PDSC_HOST_MEM_MAX_COUNT);\n+\tdev_dbg(pdsc-\u003edev, \"mem get count returned count %d\\n\", count);\n+\tif (count == 0)\n+\t\treturn;\n+\n+\tpdsc-\u003ehost_mem_reqs = kzalloc_objs(*pdsc-\u003ehost_mem_reqs, count,\n+\t\t\t\t\t   GFP_KERNEL);\n+\tif (!pdsc-\u003ehost_mem_reqs) {\n+\t\tdev_err(pdsc-\u003edev, \"failed to alloc host_mem_reqs array\\n\");\n+\t\treturn;\n+\t}\n+\n+\tfor (i = 0; i \u003c count; i++) {\n+\t\terr = pdsc_host_mem_add_one(pdsc, i);\n+\t\tif (err)\n+\t\t\tbreak;\n+\t}\n+}\n+\n+void pdsc_host_mem_free(struct pdsc *pdsc)\n+{\n+\tint i;\n+\n+\tif (!pdsc-\u003ehost_mem_reqs)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c pdsc-\u003enum_host_mem_reqs; i++) {\n+\t\tdma_unmap_page(pdsc-\u003edev, pdsc-\u003ehost_mem_reqs[i].pa,\n+\t\t\t       pdsc-\u003ehost_mem_reqs[i].size,\n+\t\t\t       DMA_BIDIRECTIONAL);\n+\t\t__free_pages(pdsc-\u003ehost_mem_reqs[i].pg,\n+\t\t\t     pdsc-\u003ehost_mem_reqs[i].order);\n+\t}\n+\n+\tkfree(pdsc-\u003ehost_mem_reqs);\n+\tpdsc-\u003ehost_mem_reqs = NULL;\n+\tpdsc-\u003enum_host_mem_reqs = 0;\n+}\ndiff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h\nindex b7fe9ad733498..791756a50870e 100644\n--- a/drivers/net/ethernet/amd/pds_core/core.h\n+++ b/drivers/net/ethernet/amd/pds_core/core.h\n@@ -5,6 +5,7 @@\n #define _PDSC_H_\n \n #include \u003clinux/debugfs.h\u003e\n+#include \u003clinux/mmzone.h\u003e\n #include \u003cnet/devlink.h\u003e\n \n #include \u003clinux/pds/pds_common.h\u003e\n@@ -23,6 +24,20 @@\n #define PDSC_SETUP_RECOVERY\tfalse\n #define PDSC_SETUP_INIT\t\ttrue\n \n+/* Use fixed 4MB instead of PAGE_SIZE \u003c\u003c MAX_PAGE_ORDER to avoid\n+ * cpu_to_le32() truncation on large-page configs\n+ */\n+#define PDSC_HOST_MEM_MAX_CONTIG (4 * 1024 * 1024)\n+#define PDSC_HOST_MEM_MAX_COUNT  256\n+\n+struct pdsc_deferred_dma {\n+\tstruct list_head list;\n+\tdma_addr_t dma_addr;\n+\tvoid *va;\n+\tsize_t size;\n+\tenum dma_data_direction dir;\n+};\n+\n struct pdsc_dev_bar {\n \tvoid __iomem *vaddr;\n \tphys_addr_t bus_addr;\n@@ -141,6 +156,14 @@ struct pdsc_viftype {\n \tstruct pds_auxiliary_dev *padev;\n };\n \n+struct pdsc_host_mem {\n+\tu32 size;\n+\tu16 tag;\n+\tu8 order;\n+\tstruct page *pg;\n+\tdma_addr_t pa;\n+};\n+\n /* No state flags set means we are in a steady running state */\n enum pdsc_state_flags {\n \tPDSC_S_FW_DEAD,\t\t    /* stopped, wait on startup or recovery */\n@@ -185,6 +208,8 @@ struct pdsc {\n \tstruct mutex devcmd_lock;\t/* lock for dev_cmd operations */\n \tstruct mutex config_lock;\t/* lock for configuration operations */\n \tspinlock_t adminq_lock;\t\t/* lock for adminq operations */\n+\tstruct list_head deferred_dma_list;\n+\tspinlock_t deferred_dma_lock;\t/* lock for deferred DMA list */\n \trefcount_t adminq_refcnt;\n \tstruct pds_core_dev_info_regs __iomem *info_regs;\n \tstruct pds_core_dev_cmd_regs __iomem *cmd_regs;\n@@ -199,6 +224,11 @@ struct pdsc {\n \tu64 last_eid;\n \tstruct pdsc_viftype *viftype_status;\n \tstruct work_struct pci_reset_work;\n+\n+\tstruct pdsc_host_mem *host_mem_reqs;\n+\tu16 num_host_mem_reqs;\n+\n+\tstruct pds_core_component_list_info fw_components;\n };\n \n /** enum pds_core_dbell_bits - bitwise composition of dbell values.\n@@ -275,14 +305,24 @@ void pdsc_debugfs_add_viftype(struct pdsc *pdsc);\n void pdsc_debugfs_add_irqs(struct pdsc *pdsc);\n void pdsc_debugfs_add_qcq(struct pdsc *pdsc, struct pdsc_qcq *qcq);\n void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq);\n+void pdsc_debugfs_add_host_mem(struct pdsc *pdsc);\n+void pdsc_debugfs_del_host_mem(struct pdsc *pdsc);\n \n int pdsc_err_to_errno(enum pds_core_status_code code);\n bool pdsc_is_fw_running(struct pdsc *pdsc);\n bool pdsc_is_fw_good(struct pdsc *pdsc);\n int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n \t\tunion pds_core_dev_comp *comp, int max_seconds);\n+int pdsc_devcmd_with_data(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n+\t\t\t  const void *data, size_t data_len,\n+\t\t\t  union pds_core_dev_comp *comp, int max_seconds);\n+int pdsc_devcmd_with_data_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n+\t\t\t\tconst void *data, size_t data_len,\n+\t\t\t\tunion pds_core_dev_comp *comp, int max_seconds);\n int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n \t\t       union pds_core_dev_comp *comp, int max_seconds);\n+int pdsc_devcmd_locked_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n+\t\t\t     union pds_core_dev_comp *comp, int max_seconds);\n int pdsc_devcmd_init(struct pdsc *pdsc);\n int pdsc_devcmd_reset(struct pdsc *pdsc);\n int pdsc_dev_init(struct pdsc *pdsc);\n@@ -315,11 +355,23 @@ void pdsc_process_adminq(struct pdsc_qcq *qcq);\n void pdsc_work_thread(struct work_struct *work);\n irqreturn_t pdsc_adminq_isr(int irq, void *data);\n \n-int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,\n+int pdsc_firmware_update(struct pdsc *pdsc,\n+\t\t\t struct devlink_flash_update_params *params,\n \t\t\t struct netlink_ext_ack *extack);\n+int pdsc_get_component_info(struct pdsc *pdsc);\n+const char *pdsc_fw_type_to_name(u8 type);\n+void pdsc_fw_components_invalidate(struct pdsc *pdsc);\n \n void pdsc_fw_down(struct pdsc *pdsc);\n void pdsc_fw_up(struct pdsc *pdsc);\n void pdsc_pci_reset_thread(struct work_struct *work);\n \n+void pdsc_host_mem_add(struct pdsc *pdsc);\n+void pdsc_host_mem_free(struct pdsc *pdsc);\n+\n+void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry,\n+\t\t\t   dma_addr_t dma_addr, void *va, size_t size,\n+\t\t\t   enum dma_data_direction dir);\n+void pdsc_deferred_dma_free(struct pdsc *pdsc);\n+\n #endif /* _PDSC_H_ */\ndiff --git a/drivers/net/ethernet/amd/pds_core/debugfs.c b/drivers/net/ethernet/amd/pds_core/debugfs.c\nindex 810a0cd9bcac8..ef0a1b7d159b5 100644\n--- a/drivers/net/ethernet/amd/pds_core/debugfs.c\n+++ b/drivers/net/ethernet/amd/pds_core/debugfs.c\n@@ -178,3 +178,48 @@ void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq)\n \tdebugfs_remove_recursive(qcq-\u003edentry);\n \tqcq-\u003edentry = NULL;\n }\n+\n+static int host_mem_show(struct seq_file *seq, void *v)\n+{\n+\tstruct pdsc *pdsc = seq-\u003eprivate;\n+\tstruct pdsc_host_mem *hm;\n+\tint i;\n+\n+\tif (!pdsc-\u003ehost_mem_reqs || pdsc-\u003enum_host_mem_reqs == 0) {\n+\t\tseq_puts(seq, \"No host memory allocated\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\tseq_printf(seq, \"Host memory requests: %u\\n\\n\",\n+\t\t   pdsc-\u003enum_host_mem_reqs);\n+\tseq_puts(seq, \"Tag    Size         Order  PA\\n\");\n+\tseq_puts(seq, \"---    ----         -----  --\\n\");\n+\n+\tfor (i = 0; i \u003c pdsc-\u003enum_host_mem_reqs; i++) {\n+\t\thm = \u0026pdsc-\u003ehost_mem_reqs[i];\n+\n+\t\tif (!hm-\u003epg)\n+\t\t\tcontinue;\n+\n+\t\tseq_printf(seq, \"%-6u %-12u %-6u %pad\\n\",\n+\t\t\t   hm-\u003etag, hm-\u003esize, hm-\u003eorder, \u0026hm-\u003epa);\n+\t}\n+\n+\treturn 0;\n+}\n+DEFINE_SHOW_ATTRIBUTE(host_mem);\n+\n+void pdsc_debugfs_add_host_mem(struct pdsc *pdsc)\n+{\n+\tif (!(pdsc-\u003edev_ident.capabilities \u0026\n+\t     cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM)))\n+\t\treturn;\n+\n+\tdebugfs_create_file(\"host_mem\", 0400, pdsc-\u003edentry,\n+\t\t\t    pdsc, \u0026host_mem_fops);\n+}\n+\n+void pdsc_debugfs_del_host_mem(struct pdsc *pdsc)\n+{\n+\tdebugfs_lookup_and_remove(\"host_mem\", pdsc-\u003edentry);\n+}\ndiff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c\nindex bded6b33289ce..c50778175e2f3 100644\n--- a/drivers/net/ethernet/amd/pds_core/dev.c\n+++ b/drivers/net/ethernet/amd/pds_core/dev.c\n@@ -126,7 +126,8 @@ static const char *pdsc_devcmd_str(int opcode)\n \t}\n }\n \n-static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)\n+static int __pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds,\n+\t\t\t      const bool do_msg)\n {\n \tstruct device *dev = pdsc-\u003edev;\n \tunsigned long start_time;\n@@ -179,7 +180,7 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)\n \n \tstatus = pdsc_devcmd_status(pdsc);\n \terr = pdsc_err_to_errno(status);\n-\tif (err \u0026\u0026 err != -EAGAIN)\n+\tif (do_msg \u0026\u0026 err \u0026\u0026 err != -EAGAIN)\n \t\tdev_err(dev, \"DEVCMD %d %s failed, status=%d err %d %pe\\n\",\n \t\t\topcode, pdsc_devcmd_str(opcode), status, err,\n \t\t\tERR_PTR(err));\n@@ -187,8 +188,9 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)\n \treturn err;\n }\n \n-int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n-\t\t       union pds_core_dev_comp *comp, int max_seconds)\n+static int __pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n+\t\t\t\tunion pds_core_dev_comp *comp, int max_seconds,\n+\t\t\t\tconst bool do_msg)\n {\n \tint err;\n \n@@ -197,16 +199,63 @@ int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n \n \tmemcpy_toio(\u0026pdsc-\u003ecmd_regs-\u003ecmd, cmd, sizeof(*cmd));\n \tpdsc_devcmd_dbell(pdsc);\n-\terr = pdsc_devcmd_wait(pdsc, cmd-\u003eopcode, max_seconds);\n+\terr = __pdsc_devcmd_wait(pdsc, cmd-\u003eopcode, max_seconds, do_msg);\n \n \tif ((err == -ENXIO || err == -ETIMEDOUT) \u0026\u0026 pdsc-\u003ewq)\n \t\tqueue_work(pdsc-\u003ewq, \u0026pdsc-\u003ehealth_work);\n \telse\n \t\tmemcpy_fromio(comp, \u0026pdsc-\u003ecmd_regs-\u003ecomp, sizeof(*comp));\n \n+\tif (err != -ETIMEDOUT \u0026\u0026 err != -EAGAIN)\n+\t\tpdsc_deferred_dma_free(pdsc);\n+\n \treturn err;\n }\n \n+void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry,\n+\t\t\t   dma_addr_t dma_addr, void *va, size_t size,\n+\t\t\t   enum dma_data_direction dir)\n+{\n+\tentry-\u003edma_addr = dma_addr;\n+\tentry-\u003eva = va;\n+\tentry-\u003esize = size;\n+\tentry-\u003edir = dir;\n+\n+\tspin_lock(\u0026pdsc-\u003edeferred_dma_lock);\n+\tlist_add_tail(\u0026entry-\u003elist, \u0026pdsc-\u003edeferred_dma_list);\n+\tspin_unlock(\u0026pdsc-\u003edeferred_dma_lock);\n+}\n+\n+void pdsc_deferred_dma_free(struct pdsc *pdsc)\n+{\n+\tstruct pdsc_deferred_dma *entry, *tmp;\n+\tLIST_HEAD(local_list);\n+\n+\tspin_lock(\u0026pdsc-\u003edeferred_dma_lock);\n+\tlist_splice_init(\u0026pdsc-\u003edeferred_dma_list, \u0026local_list);\n+\tspin_unlock(\u0026pdsc-\u003edeferred_dma_lock);\n+\n+\tlist_for_each_entry_safe(entry, tmp, \u0026local_list, list) {\n+\t\tdma_unmap_single(pdsc-\u003edev, entry-\u003edma_addr,\n+\t\t\t\t entry-\u003esize, entry-\u003edir);\n+\t\tkfree(entry-\u003eva);\n+\t\tlist_del(\u0026entry-\u003elist);\n+\t\tkfree(entry);\n+\t}\n+}\n+\n+int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n+\t\t       union pds_core_dev_comp *comp, int max_seconds)\n+{\n+\treturn __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, true);\n+}\n+\n+int pdsc_devcmd_locked_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n+\t\t\t     union pds_core_dev_comp *comp, int max_seconds)\n+{\n+\treturn __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, false);\n+}\n+\n int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n \t\tunion pds_core_dev_comp *comp, int max_seconds)\n {\n@@ -219,6 +268,47 @@ int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n \treturn err;\n }\n \n+static int __pdsc_devcmd_with_data(struct pdsc *pdsc,\n+\t\t\t\t   union pds_core_dev_cmd *cmd,\n+\t\t\t\t   const void *data, size_t data_len,\n+\t\t\t\t   union pds_core_dev_comp *comp,\n+\t\t\t\t   int max_seconds, bool do_msg)\n+{\n+\tint err;\n+\n+\tmutex_lock(\u0026pdsc-\u003edevcmd_lock);\n+\tif (!pdsc-\u003ecmd_regs) {\n+\t\terr = -ENXIO;\n+\t\tgoto unlock;\n+\t}\n+\tif (data_len \u003e sizeof(pdsc-\u003ecmd_regs-\u003edata)) {\n+\t\terr = -ENOSPC;\n+\t\tgoto unlock;\n+\t}\n+\tmemcpy_toio(\u0026pdsc-\u003ecmd_regs-\u003edata, data, data_len);\n+\terr = __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, do_msg);\n+unlock:\n+\tmutex_unlock(\u0026pdsc-\u003edevcmd_lock);\n+\n+\treturn err;\n+}\n+\n+int pdsc_devcmd_with_data(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n+\t\t\t  const void *data, size_t data_len,\n+\t\t\t  union pds_core_dev_comp *comp, int max_seconds)\n+{\n+\treturn __pdsc_devcmd_with_data(pdsc, cmd, data, data_len,\n+\t\t\t\t       comp, max_seconds, true);\n+}\n+\n+int pdsc_devcmd_with_data_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,\n+\t\t\t\tconst void *data, size_t data_len,\n+\t\t\t\tunion pds_core_dev_comp *comp, int max_seconds)\n+{\n+\treturn __pdsc_devcmd_with_data(pdsc, cmd, data, data_len,\n+\t\t\t\t       comp, max_seconds, false);\n+}\n+\n int pdsc_devcmd_init(struct pdsc *pdsc)\n {\n \tunion pds_core_dev_comp comp = {};\n@@ -242,15 +332,17 @@ int pdsc_devcmd_reset(struct pdsc *pdsc)\n \treturn pdsc_devcmd(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout);\n }\n \n-static int pdsc_devcmd_identify_locked(struct pdsc *pdsc)\n+static int pdsc_devcmd_identify_locked(struct pdsc *pdsc, u8 drv_ident_ver,\n+\t\t\t\t       bool do_msg)\n {\n \tunion pds_core_dev_comp comp = {};\n \tunion pds_core_dev_cmd cmd = {\n \t\t.identify.opcode = PDS_CORE_CMD_IDENTIFY,\n-\t\t.identify.ver = PDS_CORE_IDENTITY_VERSION_1,\n+\t\t.identify.ver = drv_ident_ver,\n \t};\n \n-\treturn pdsc_devcmd_locked(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout);\n+\treturn __pdsc_devcmd_locked(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout,\n+\t\t\t\t    do_msg);\n }\n \n static void pdsc_init_devinfo(struct pdsc *pdsc)\n@@ -273,8 +365,9 @@ static void pdsc_init_devinfo(struct pdsc *pdsc)\n \tdev_dbg(pdsc-\u003edev, \"fw_version %s\\n\", pdsc-\u003edev_info.fw_version);\n }\n \n-static int pdsc_identify(struct pdsc *pdsc)\n+static int pdsc_identify_ver(struct pdsc *pdsc, u8 drv_ident_ver)\n {\n+\tbool do_msg = drv_ident_ver == PDS_CORE_IDENTITY_VERSION_1;\n \tstruct pds_core_drv_identity drv = {};\n \tsize_t sz;\n \tint err;\n@@ -297,19 +390,22 @@ static int pdsc_identify(struct pdsc *pdsc)\n \tsz = min_t(size_t, sizeof(drv), sizeof(pdsc-\u003ecmd_regs-\u003edata));\n \tmemcpy_toio(\u0026pdsc-\u003ecmd_regs-\u003edata, \u0026drv, sz);\n \n-\terr = pdsc_devcmd_identify_locked(pdsc);\n+\terr = pdsc_devcmd_identify_locked(pdsc, drv_ident_ver, do_msg);\n \tif (!err) {\n \t\tsz = min_t(size_t, sizeof(pdsc-\u003edev_ident),\n \t\t\t   sizeof(pdsc-\u003ecmd_regs-\u003edata));\n \t\tmemcpy_fromio(\u0026pdsc-\u003edev_ident, \u0026pdsc-\u003ecmd_regs-\u003edata, sz);\n+\n+\t\t/* V1 firmware doesn't set capabilities, so the field may\n+\t\t * contain garbage from the outgoing driver identity.\n+\t\t */\n+\t\tif (pdsc-\u003edev_ident.version \u003c PDS_CORE_IDENTITY_VERSION_2)\n+\t\t\tpdsc-\u003edev_ident.capabilities = 0;\n \t}\n \tmutex_unlock(\u0026pdsc-\u003edevcmd_lock);\n \n-\tif (err) {\n-\t\tdev_err(pdsc-\u003edev, \"Cannot identify device: %pe\\n\",\n-\t\t\tERR_PTR(err));\n+\tif (err)\n \t\treturn err;\n-\t}\n \n \tif (isprint(pdsc-\u003edev_info.fw_version[0]) \u0026\u0026\n \t    isascii(pdsc-\u003edev_info.fw_version[0]))\n@@ -326,6 +422,26 @@ static int pdsc_identify(struct pdsc *pdsc)\n \treturn 0;\n }\n \n+static int pdsc_identify(struct pdsc *pdsc)\n+{\n+\tint err;\n+\n+\t/* Older firmware rejects anything but PDS_CORE_IDENTITY_VERSION_1\n+\t * with PDS_RC_EVERSION (-EINVAL), so retry with V1 on version\n+\t * rejection. Don't retry on other errors like -ENXIO/-ETIMEDOUT\n+\t * which indicate firmware is not running or hung.\n+\t */\n+\terr = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_2);\n+\tif (err == -EINVAL)\n+\t\terr = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_1);\n+\n+\tif (err)\n+\t\tdev_err(pdsc-\u003edev, \"Cannot identify device: %pe\\n\",\n+\t\t\tERR_PTR(err));\n+\n+\treturn err;\n+}\n+\n void pdsc_dev_uninit(struct pdsc *pdsc)\n {\n \tif (pdsc-\u003eintr_info) {\ndiff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c\nindex 8adae7b18898d..63fe45e91f710 100644\n--- a/drivers/net/ethernet/amd/pds_core/devlink.c\n+++ b/drivers/net/ethernet/amd/pds_core/devlink.c\n@@ -90,13 +90,112 @@ int pdsc_dl_flash_update(struct devlink *dl,\n {\n \tstruct pdsc *pdsc = devlink_priv(dl);\n \n-\tif (params-\u003ecomponent) {\n-\t\tNL_SET_ERR_MSG_MOD(extack,\n-\t\t\t\t   \"Component update not supported by this device\");\n-\t\treturn -EOPNOTSUPP;\n+\treturn pdsc_firmware_update(pdsc, params, extack);\n+}\n+\n+static int pdsc_dl_report_component(struct devlink_info_req *req,\n+\t\t\t\t    struct pds_core_fw_component_info *info)\n+{\n+\tenum devlink_info_version_type ver_type;\n+\tu16 flags = le16_to_cpu(info-\u003eflags);\n+\tchar *ver = info-\u003eversion;\n+\tconst char *name;\n+\tchar buf[32];\n+\n+\t/* Main firmware is reported as generic \"fw\" */\n+\tif (info-\u003ecomponent_type == PDS_CORE_FW_TYPE_MAIN) {\n+\t\tif (info-\u003eslot_id == PDS_CORE_FW_SLOT_GOLD)\n+\t\t\tsnprintf(buf, sizeof(buf), \"fw.gold\");\n+\t\telse\n+\t\t\tsnprintf(buf, sizeof(buf), \"fw\");\n+\t} else {\n+\t\tname = pdsc_fw_type_to_name(info-\u003ecomponent_type);\n+\t\tif (!name)\n+\t\t\treturn 0;\n+\n+\t\tif (info-\u003eslot_id == PDS_CORE_FW_SLOT_GOLD)\n+\t\t\tsnprintf(buf, sizeof(buf), \"fw.%s.gold\", name);\n+\t\telse\n+\t\t\tsnprintf(buf, sizeof(buf), \"fw.%s\", name);\n+\t}\n+\n+\tver_type = DEVLINK_INFO_VERSION_TYPE_NONE;\n+\tif (flags \u0026 PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME)\n+\t\tver_type = DEVLINK_INFO_VERSION_TYPE_COMPONENT;\n+\n+\tif (flags \u0026 PDS_CORE_FW_COMPONENT_INFO_F_FIXED) {\n+\t\tint err;\n+\n+\t\terr = devlink_info_version_fixed_put(req, buf, ver);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\tif (flags \u0026 PDS_CORE_FW_COMPONENT_INFO_F_RUNNING) {\n+\t\tint err;\n+\n+\t\terr = devlink_info_version_running_put_ext(req, buf,\n+\t\t\t\t\t\t\t   ver, ver_type);\n+\t\tif (err)\n+\t\t\treturn err;\n \t}\n \n-\treturn pdsc_firmware_update(pdsc, params-\u003efw, extack);\n+\tif (flags \u0026 PDS_CORE_FW_COMPONENT_INFO_F_STARTUP) {\n+\t\tint err;\n+\n+\t\terr = devlink_info_version_stored_put_ext(req, buf,\n+\t\t\t\t\t\t\t  ver, ver_type);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int pdsc_dl_report_fw_ver(struct devlink_info_req *req, char *fw_ver)\n+{\n+\treturn devlink_info_version_running_put(req,\n+\t\t\t\t\t\tDEVLINK_INFO_VERSION_GENERIC_FW,\n+\t\t\t\t\t\tfw_ver);\n+}\n+\n+static int pdsc_dl_component_info_get(struct devlink *dl,\n+\t\t\t\t      struct devlink_info_req *req,\n+\t\t\t\t      struct netlink_ext_ack *extack)\n+{\n+\tstruct pdsc *pdsc = devlink_priv(dl);\n+\tu8 num_components;\n+\tint err;\n+\tint i;\n+\n+\t/* Pairs with WRITE_ONCE in pdsc_fw_components_invalidate().\n+\t * Use READ_ONCE to get a consistent snapshot of num_components.\n+\t * pdsc_fw_components_invalidate() can zero it concurrently during\n+\t * firmware recovery; using the local copy avoids iterating zero\n+\t * times when we already decided the cache was valid.\n+\t */\n+\tnum_components = READ_ONCE(pdsc-\u003efw_components.num_components);\n+\tif (!num_components) {\n+\t\terr = pdsc_get_component_info(pdsc);\n+\t\tif (err)\n+\t\t\treturn pdsc_dl_report_fw_ver(req,\n+\t\t\t\t\t\t    pdsc-\u003edev_info.fw_version);\n+\t\tnum_components = READ_ONCE(pdsc-\u003efw_components.num_components);\n+\t\tif (!num_components)\n+\t\t\treturn pdsc_dl_report_fw_ver(req,\n+\t\t\t\t\t\t    pdsc-\u003edev_info.fw_version);\n+\t}\n+\n+\tnum_components = min_t(u16, num_components,\n+\t\t\t       le16_to_cpu(pdsc-\u003edev_ident.max_fw_slots));\n+\tfor (i = 0; i \u003c num_components; i++) {\n+\t\terr = pdsc_dl_report_component(req,\n+\t\t\t\t\t       \u0026pdsc-\u003efw_components.info[i]);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\treturn 0;\n }\n \n static char *fw_slotnames[] = {\n@@ -105,8 +204,9 @@ static char *fw_slotnames[] = {\n \t\"fw.mainfwb\",\n };\n \n-int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,\n-\t\t     struct netlink_ext_ack *extack)\n+static int pdsc_dl_fw_list_info_get(struct devlink *dl,\n+\t\t\t\t    struct devlink_info_req *req,\n+\t\t\t\t    struct netlink_ext_ack *extack)\n {\n \tunion pds_core_dev_cmd cmd = {\n \t\t.fw_control.opcode = PDS_CORE_CMD_FW_CONTROL,\n@@ -140,12 +240,41 @@ int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,\n \t\t\treturn err;\n \t}\n \n-\terr = devlink_info_version_running_put(req,\n-\t\t\t\t\t       DEVLINK_INFO_VERSION_GENERIC_FW,\n-\t\t\t\t\t       pdsc-\u003edev_info.fw_version);\n+\treturn 0;\n+}\n+\n+static int pdsc_dl_info_get_v1(struct devlink *dl,\n+\t\t\t       struct devlink_info_req *req,\n+\t\t\t       struct netlink_ext_ack *extack)\n+{\n+\tstruct pdsc *pdsc = devlink_priv(dl);\n+\tint err;\n+\n+\terr = pdsc_dl_fw_list_info_get(dl, req, extack);\n \tif (err)\n \t\treturn err;\n \n+\t/* Version 1: report fw from dev_info (running only) */\n+\treturn pdsc_dl_report_fw_ver(req, pdsc-\u003edev_info.fw_version);\n+}\n+\n+int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,\n+\t\t     struct netlink_ext_ack *extack)\n+{\n+\tstruct pdsc *pdsc = devlink_priv(dl);\n+\tchar buf[32];\n+\tint err;\n+\n+\tif (pdsc-\u003edev_ident.version \u003e= PDS_CORE_IDENTITY_VERSION_2) {\n+\t\terr = pdsc_dl_component_info_get(dl, req, extack);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t} else {\n+\t\terr = pdsc_dl_info_get_v1(dl, req, extack);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n \tsnprintf(buf, sizeof(buf), \"0x%x\", pdsc-\u003edev_info.asic_type);\n \terr = devlink_info_version_fixed_put(req,\n \t\t\t\t\t     DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,\ndiff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c\nindex fa626719e68d1..5ccf017f6af47 100644\n--- a/drivers/net/ethernet/amd/pds_core/fw.c\n+++ b/drivers/net/ethernet/amd/pds_core/fw.c\n@@ -1,6 +1,9 @@\n // SPDX-License-Identifier: GPL-2.0\n /* Copyright(c) 2023 Advanced Micro Devices, Inc */\n \n+#include \u003clinux/pldmfw.h\u003e\n+#include \u003clinux/vmalloc.h\u003e\n+\n #include \"core.h\"\n \n /* The worst case wait for the install activity is about 25 minutes when\n@@ -14,6 +17,58 @@\n /* Number of periodic log updates during fw file download */\n #define PDSC_FW_INTERVAL_FRACTION\t32\n \n+#define PDSC_FW_COMPONENT_PREFIX\t\t\"fw.\"\n+#define PDSC_FW_COMPONENT_FULL_NAME_BUFLEN \\\n+\t(sizeof(PDSC_FW_COMPONENT_PREFIX) + PDS_CORE_FW_COMPONENT_NAME_BUFLEN)\n+\n+/* Driver-defined component type to name mapping.\n+ * PDS_CORE_FW_TYPE_MAIN is NULL - handled specially as \"fw\" without prefix.\n+ */\n+static const char * const pdsc_fw_type_names[] = {\n+\t[PDS_CORE_FW_TYPE_MAIN]      = NULL,\n+\t[PDS_CORE_FW_TYPE_BOOT]      = \"bootloader\",\n+\t[PDS_CORE_FW_TYPE_CPLD]      = \"cpld\",\n+\t[PDS_CORE_FW_TYPE_SECURE]    = \"secure\",\n+\t[PDS_CORE_FW_TYPE_FPGA]      = \"fpga\",\n+\t[PDS_CORE_FW_TYPE_SUC_MAIN]  = \"suc\",\n+\t[PDS_CORE_FW_TYPE_SUC_BOOT]  = \"suc.bootloader\",\n+\t[PDS_CORE_FW_TYPE_UBOOT]     = \"uboot\",\n+};\n+\n+const char *pdsc_fw_type_to_name(u8 type)\n+{\n+\tif (type \u003c ARRAY_SIZE(pdsc_fw_type_names) \u0026\u0026 pdsc_fw_type_names[type])\n+\t\treturn pdsc_fw_type_names[type];\n+\treturn NULL;\n+}\n+\n+void pdsc_fw_components_invalidate(struct pdsc *pdsc)\n+{\n+\t/* Pairs with READ_ONCE in pdsc_dl_component_info_get() */\n+\tWRITE_ONCE(pdsc-\u003efw_components.num_components, 0);\n+}\n+\n+static u8 pdsc_name_to_fw_type(const char *name)\n+{\n+\tsize_t prefix_len;\n+\tint i;\n+\n+\t/* \"fw\" without suffix maps to main firmware */\n+\tif (!strcmp(name, \"fw\"))\n+\t\treturn PDS_CORE_FW_TYPE_MAIN;\n+\n+\tprefix_len = str_has_prefix(name, PDSC_FW_COMPONENT_PREFIX);\n+\tif (prefix_len)\n+\t\tname += prefix_len;\n+\n+\tfor (i = 1; i \u003c ARRAY_SIZE(pdsc_fw_type_names); i++) {\n+\t\tif (pdsc_fw_type_names[i] \u0026\u0026\n+\t\t    !strcmp(name, pdsc_fw_type_names[i]))\n+\t\t\treturn i;\n+\t}\n+\treturn 0;\n+}\n+\n static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr,\n \t\t\t\t\t  u32 offset, u32 length)\n {\n@@ -23,7 +78,7 @@ static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr,\n \t\t.fw_download.addr = cpu_to_le64(addr),\n \t\t.fw_download.length = cpu_to_le32(length),\n \t};\n-\tunion pds_core_dev_comp comp;\n+\tunion pds_core_dev_comp comp = {};\n \n \treturn pdsc_devcmd_locked(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout);\n }\n@@ -95,9 +150,12 @@ static int pdsc_fw_status_long_wait(struct pdsc *pdsc,\n \treturn err;\n }\n \n-int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,\n-\t\t\t struct netlink_ext_ack *extack)\n+static int\n+pdsc_legacy_firmware_update(struct pdsc *pdsc,\n+\t\t\t    struct devlink_flash_update_params *params,\n+\t\t\t    struct netlink_ext_ack *extack)\n {\n+\tconst struct firmware *fw = params-\u003efw;\n \tu32 buf_sz, copy_sz, offset;\n \tstruct devlink *dl;\n \tint next_interval;\n@@ -105,6 +163,12 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,\n \tint err = 0;\n \tint fw_slot;\n \n+\tif (params-\u003ecomponent) {\n+\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t   \"Component update not supported by this device\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n \tdev_info(pdsc-\u003edev, \"Installing firmware\\n\");\n \n \tif (!pdsc-\u003ecmd_regs)\n@@ -195,3 +259,729 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,\n \t\t\t\t\t\t   NULL, 0, 0);\n \treturn err;\n }\n+\n+struct pdsc_component_priv {\n+\tu16 component_id;\n+\tbool skip;\n+\tstruct list_head list_entry;\n+};\n+\n+struct pds_core_fwu_priv {\n+\tstruct pldmfw context;\n+\tstruct devlink_flash_update_params *params;\n+\tstruct netlink_ext_ack *extack;\n+\tstruct pdsc *pdsc;\n+\tstruct list_head components;\n+\tbool component_found;\n+};\n+\n+static void pdsc_free_fwu_priv(struct pds_core_fwu_priv *priv)\n+{\n+\tstruct pdsc_component_priv *component_priv, *tmp;\n+\n+\tlist_for_each_entry_safe(component_priv, tmp, \u0026priv-\u003ecomponents,\n+\t\t\t\t list_entry) {\n+\t\tlist_del(\u0026component_priv-\u003elist_entry);\n+\t\tkfree(component_priv);\n+\t}\n+}\n+\n+static int pdsc_devcmd_match_record_desc(struct pdsc *pdsc, u16 desc_type,\n+\t\t\t\t\t u16 desc_size, const u8 *desc_data,\n+\t\t\t\t\t u8 *match)\n+{\n+\tunion pds_core_dev_cmd cmd = {\n+\t\t.match_record_desc.opcode = PDS_CORE_CMD_MATCH_RECORD_DESC,\n+\t\t.match_record_desc.ver = 1,\n+\t\t.match_record_desc.type = cpu_to_le16(desc_type),\n+\t\t.match_record_desc.size = cpu_to_le16(desc_size),\n+\t};\n+\tunion pds_core_dev_comp comp = {};\n+\tint err;\n+\n+\terr = pdsc_devcmd_with_data(pdsc, \u0026cmd, desc_data, desc_size,\n+\t\t\t\t    \u0026comp, pdsc-\u003edevcmd_timeout);\n+\t*match = comp.match_record_desc.match;\n+\n+\treturn err;\n+}\n+\n+static bool pdsc_match_record_descs(struct pldmfw *context,\n+\t\t\t\t    struct pldmfw_record *record)\n+{\n+\tstruct pds_core_fwu_priv *priv =\n+\t\tcontainer_of(context, struct pds_core_fwu_priv, context);\n+\tstruct pdsc *pdsc = priv-\u003epdsc;\n+\tstruct pldmfw_desc_tlv *desc;\n+\n+\tif (!pldmfw_op_pci_match_record(context, record))\n+\t\treturn false;\n+\n+\tlist_for_each_entry(desc, \u0026record-\u003edescs, entry) {\n+\t\tu8 match;\n+\t\tint err;\n+\n+\t\tswitch (desc-\u003etype) {\n+\t\t/* skip types checked in pldmfw_op_pci_match_record */\n+\t\tcase PLDM_DESC_ID_PCI_VENDOR_ID:\n+\t\tcase PLDM_DESC_ID_PCI_DEVICE_ID:\n+\t\tcase PLDM_DESC_ID_PCI_SUBVENDOR_ID:\n+\t\tcase PLDM_DESC_ID_PCI_SUBDEV_ID:\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!desc-\u003esize)\n+\t\t\treturn false;\n+\n+\t\terr = pdsc_devcmd_match_record_desc(pdsc, desc-\u003etype,\n+\t\t\t\t\t\t    desc-\u003esize, desc-\u003edata,\n+\t\t\t\t\t\t    \u0026match);\n+\t\tif (err) {\n+\t\t\tdev_err(pdsc-\u003edev,\n+\t\t\t\t\"match_record_desc failed type: 0x%04x size: %u, err %d\\n\",\n+\t\t\t\tdesc-\u003etype, desc-\u003esize, err);\n+\t\t\treturn false;\n+\t\t}\n+\t\t/* all record descriptors must match */\n+\t\tif (!match)\n+\t\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+static int pdsc_devcmd_send_package_data(struct pdsc *pdsc, u64 addr,\n+\t\t\t\t\t u16 length, u16 offset, u16 total_len)\n+{\n+\tunion pds_core_dev_cmd cmd = {\n+\t\t.send_pkg_data.opcode = PDS_CORE_CMD_SEND_PKG_DATA,\n+\t\t.send_pkg_data.ver = 1,\n+\t\t.send_pkg_data.data_pa = cpu_to_le64(addr),\n+\t\t.send_pkg_data.data_len = cpu_to_le16(length),\n+\t\t.send_pkg_data.offset = cpu_to_le16(offset),\n+\t\t.send_pkg_data.total_len = cpu_to_le16(total_len),\n+\t};\n+\tunion pds_core_dev_comp comp = {};\n+\n+\treturn pdsc_devcmd(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout);\n+}\n+\n+static int pdsc_send_package_data(struct pldmfw *context, const u8 *data,\n+\t\t\t\t  u16 length)\n+{\n+\tstruct pds_core_fwu_priv *priv =\n+\t\tcontainer_of(context, struct pds_core_fwu_priv, context);\n+\tstruct pdsc_deferred_dma *deferred;\n+\tstruct device *dev = context-\u003edev;\n+\tstruct pdsc *pdsc = priv-\u003epdsc;\n+\tdma_addr_t dma_addr;\n+\tu8 *package_data;\n+\tu32 offset;\n+\tint err;\n+\n+\tif (!length)\n+\t\treturn 0;\n+\n+\tdeferred = kmalloc_obj(*deferred, GFP_KERNEL);\n+\tif (!deferred)\n+\t\treturn -ENOMEM;\n+\n+\tpackage_data = kmemdup(data, length, GFP_KERNEL);\n+\tif (!package_data) {\n+\t\tkfree(deferred);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tdma_addr = dma_map_single(dev, package_data, length, DMA_TO_DEVICE);\n+\tif (dma_mapping_error(dev, dma_addr)) {\n+\t\tdev_err(dev, \"Failed to dma_map package_data length 0x%x\\n\",\n+\t\t\tlength);\n+\t\tkfree(package_data);\n+\t\tkfree(deferred);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tfor (offset = 0; offset \u003c length; offset += PDS_PAGE_SIZE) {\n+\t\tu32 copy_sz;\n+\n+\t\tcopy_sz = min_t(unsigned int, PDS_PAGE_SIZE, length - offset);\n+\t\terr = pdsc_devcmd_send_package_data(pdsc, dma_addr + offset,\n+\t\t\t\t\t\t    copy_sz, offset, length);\n+\t\tif (err) {\n+\t\t\tNL_SET_ERR_MSG_MOD(priv-\u003eextack,\n+\t\t\t\t\t   \"Failed to send package data\");\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (err == -ETIMEDOUT || err == -EAGAIN) {\n+\t\tpdsc_deferred_dma_add(pdsc, deferred, dma_addr,\n+\t\t\t\t      package_data, length, DMA_TO_DEVICE);\n+\t\treturn err;\n+\t}\n+\n+\tkfree(deferred);\n+\tdma_unmap_single(dev, dma_addr, length, DMA_TO_DEVICE);\n+\tkfree(package_data);\n+\treturn err;\n+}\n+\n+static bool pdsc_component_type_exists(struct pdsc *pdsc, u8 type)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c pdsc-\u003efw_components.num_components; i++) {\n+\t\tif (pdsc-\u003efw_components.info[i].component_type == type)\n+\t\t\treturn true;\n+\t}\n+\treturn false;\n+}\n+\n+static bool pdsc_component_id_matches_type(struct pdsc *pdsc,\n+\t\t\t\t\t   u8 component_id, u8 type)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c pdsc-\u003efw_components.num_components; i++) {\n+\t\tstruct pds_core_fw_component_info *info =\n+\t\t\t\u0026pdsc-\u003efw_components.info[i];\n+\n+\t\tif (info-\u003eidentifier == component_id \u0026\u0026\n+\t\t    info-\u003ecomponent_type == type)\n+\t\t\treturn true;\n+\t}\n+\treturn false;\n+}\n+\n+static u8 pdsc_get_component_type_by_id(struct pdsc *pdsc, u16 component_id)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c pdsc-\u003efw_components.num_components; i++) {\n+\t\tstruct pds_core_fw_component_info *info =\n+\t\t\t\u0026pdsc-\u003efw_components.info[i];\n+\n+\t\tif (info-\u003eidentifier == component_id)\n+\t\t\treturn info-\u003ecomponent_type;\n+\t}\n+\treturn 0;\n+}\n+\n+static bool pdsc_skip_component(struct pds_core_fwu_priv *priv,\n+\t\t\t\tu16 component_id)\n+{\n+\tstruct pdsc_component_priv *component_priv;\n+\n+\tlist_for_each_entry(component_priv, \u0026priv-\u003ecomponents, list_entry) {\n+\t\tif (component_priv-\u003ecomponent_id == component_id)\n+\t\t\treturn component_priv-\u003eskip;\n+\t}\n+\n+\treturn false;\n+}\n+\n+static int pdsc_send_component_table(struct pldmfw *context,\n+\t\t\t\t     struct pldmfw_component *component,\n+\t\t\t\t     u8 transfer_flag)\n+{\n+\tstruct pds_core_fwu_priv *priv =\n+\t\tcontainer_of(context, struct pds_core_fwu_priv, context);\n+\tstruct pds_core_component_tbl *component_tbl;\n+\tstruct pdsc_component_priv *component_priv;\n+\tstruct device *dev = context-\u003edev;\n+\tunion pds_core_dev_comp comp = {};\n+\tunion pds_core_dev_cmd cmd = {};\n+\tstruct pdsc *pdsc = priv-\u003epdsc;\n+\tbool skip_component = false;\n+\tu8 requested_type = 0;\n+\tu16 buf_sz, tbl_sz;\n+\tint err = 0;\n+\n+\tdev_dbg(dev,\n+\t\t\"component name %s classification %u id %u activation_method %u ver_len %d ver_str %.*s index %u size %u transfer_flag 0x%02x\\n\",\n+\t\tpriv-\u003eparams-\u003ecomponent, component-\u003eclassification,\n+\t\tcomponent-\u003eidentifier, component-\u003eactivation_method,\n+\t\tcomponent-\u003eversion_len, component-\u003eversion_len,\n+\t\tcomponent-\u003eversion_string, component-\u003eindex,\n+\t\tcomponent-\u003ecomponent_size, transfer_flag);\n+\n+\tcomponent_priv = kzalloc_obj(*component_priv, GFP_KERNEL);\n+\tif (!component_priv)\n+\t\treturn -ENOMEM;\n+\n+\tif (priv-\u003eparams-\u003ecomponent) {\n+\t\trequested_type = pdsc_name_to_fw_type(priv-\u003eparams-\u003ecomponent);\n+\t\tif (component-\u003eidentifier \u003e U8_MAX ||\n+\t\t    !pdsc_component_id_matches_type(pdsc,\n+\t\t\t\t\t\t    component-\u003eidentifier,\n+\t\t\t\t\t\t    requested_type)) {\n+\t\t\tskip_component = true;\n+\t\t\tgoto add_component_priv;\n+\t\t}\n+\t\tpriv-\u003ecomponent_found = true;\n+\t}\n+\n+\tbuf_sz = sizeof(pdsc-\u003ecmd_regs-\u003edata);\n+\ttbl_sz = struct_size(component_tbl, version_str,\n+\t\t\t     component-\u003eversion_len);\n+\tif (tbl_sz \u003e buf_sz) {\n+\t\tdev_err(dev, \"component_tbl size %d too big, max size: %d\\n\",\n+\t\t\ttbl_sz, buf_sz);\n+\t\terr = -ENOSPC;\n+\t\tgoto free_component_priv;\n+\t}\n+\tcomponent_tbl = kzalloc(tbl_sz, GFP_KERNEL);\n+\tif (!component_tbl) {\n+\t\terr = -ENOMEM;\n+\t\tgoto free_component_priv;\n+\t}\n+\n+\tcomponent_tbl-\u003ecomparison_stamp =\n+\t\tcpu_to_le32(component-\u003ecomparison_stamp);\n+\tcomponent_tbl-\u003eclassification = cpu_to_le16(component-\u003eclassification);\n+\tcomponent_tbl-\u003eidentifier = cpu_to_le16(component-\u003eidentifier);\n+\tcomponent_tbl-\u003etransfer_flag = transfer_flag;\n+\tcomponent_tbl-\u003eversion_str_type = component-\u003eversion_type;\n+\tcomponent_tbl-\u003eversion_str_len = component-\u003eversion_len;\n+\tmemcpy(component_tbl-\u003eversion_str, component-\u003eversion_string,\n+\t       component-\u003eversion_len);\n+\n+\tcmd.send_component_tbl.opcode = PDS_CORE_CMD_SEND_COMPONENT_TBL;\n+\tcmd.send_component_tbl.ver = 1;\n+\tcmd.send_component_tbl.slot_id = PDS_CORE_FW_SLOT_INVALID;\n+\n+\terr = pdsc_devcmd_with_data(pdsc, \u0026cmd, component_tbl, tbl_sz,\n+\t\t\t\t    \u0026comp, pdsc-\u003edevcmd_timeout);\n+\tkfree(component_tbl);\n+\tif (err) {\n+\t\tdev_err(dev, \"Failed sending component table: %pe\\n\",\n+\t\t\tERR_PTR(err));\n+\t\tgoto free_component_priv;\n+\t}\n+\n+\tskip_component = comp.send_component_tbl.response == 1;\n+\n+add_component_priv:\n+\tcomponent_priv-\u003eskip = skip_component;\n+\tcomponent_priv-\u003ecomponent_id = component-\u003eidentifier;\n+\tlist_add(\u0026component_priv-\u003elist_entry, \u0026priv-\u003ecomponents);\n+\n+\treturn 0;\n+\n+free_component_priv:\n+\tkfree(component_priv);\n+\treturn err;\n+}\n+\n+int pdsc_get_component_info(struct pdsc *pdsc)\n+{\n+\tunion pds_core_dev_cmd cmd = {\n+\t\t.get_component_info.opcode = PDS_CORE_CMD_GET_COMPONENT_INFO,\n+\t\t.get_component_info.ver = 1,\n+\t};\n+\tstruct pds_core_component_list_info *list_info;\n+\tstruct pdsc_deferred_dma *deferred;\n+\tunion pds_core_dev_comp comp = {};\n+\tdma_addr_t dma_addr;\n+\tu8 num_components;\n+\tint err, i;\n+\n+\tdeferred = kmalloc_obj(*deferred);\n+\tif (!deferred)\n+\t\treturn -ENOMEM;\n+\n+\tlist_info = kzalloc(PDS_PAGE_SIZE, GFP_KERNEL);\n+\tif (!list_info) {\n+\t\tkfree(deferred);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tdma_addr = dma_map_single(pdsc-\u003edev, list_info, PDS_PAGE_SIZE,\n+\t\t\t\t  DMA_FROM_DEVICE);\n+\tif (dma_mapping_error(pdsc-\u003edev, dma_addr)) {\n+\t\tdev_err(pdsc-\u003edev,\n+\t\t\t\"Failed to dma_map component_list_info length %d\\n\",\n+\t\t\tPDS_PAGE_SIZE);\n+\t\tkfree(list_info);\n+\t\tkfree(deferred);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tcmd.get_component_info.data_len = cpu_to_le16(PDS_PAGE_SIZE);\n+\tcmd.get_component_info.data_pa = cpu_to_le64(dma_addr);\n+\n+\terr = pdsc_devcmd(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout * 2);\n+\tif (err == -ETIMEDOUT || err == -EAGAIN) {\n+\t\tpdsc_deferred_dma_add(pdsc, deferred, dma_addr, list_info,\n+\t\t\t\t      PDS_PAGE_SIZE, DMA_FROM_DEVICE);\n+\t\treturn err;\n+\t}\n+\n+\tkfree(deferred);\n+\tdma_unmap_single(pdsc-\u003edev, dma_addr, PDS_PAGE_SIZE, DMA_FROM_DEVICE);\n+\tif (err)\n+\t\tgoto out;\n+\n+\tif (comp.get_component_info.ver == 0) {\n+\t\t/* Don't support backward compatibility as version 0 has\n+\t\t * alignment issues, so give a hint to users to update\n+\t\t * their firmware\n+\t\t */\n+\t\tdev_warn_once(pdsc-\u003edev,\n+\t\t\t      \"Incompatible get_component_info version %u reported by firmware\\n\",\n+\t\t\t      comp.get_component_info.ver);\n+\t\terr = 0;\n+\t\tgoto out;\n+\t}\n+\n+\tnum_components = list_info-\u003enum_components;\n+\tif (num_components \u003e PDS_CORE_FW_COMPONENT_LIST_LEN) {\n+\t\terr = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\n+\tpdsc-\u003efw_components.num_components = num_components;\n+\tfor (i = 0; i \u003c num_components; i++) {\n+\t\tstruct pds_core_fw_component_info *info =\n+\t\t\t\u0026pdsc-\u003efw_components.info[i];\n+\n+\t\tmemcpy(info, \u0026list_info-\u003einfo[i], sizeof(*info));\n+\t\tinfo-\u003eversion[PDS_CORE_FW_COMPONENT_VER_BUFLEN - 1] = 0;\n+\t\tinfo-\u003ename[PDS_CORE_FW_COMPONENT_NAME_BUFLEN - 1] = 0;\n+\t}\n+\n+out:\n+\tkfree(list_info);\n+\treturn err;\n+}\n+\n+static int pdsc_devcmd_send_component(struct pdsc *pdsc,\n+\t\t\t\t      struct pds_core_flash_component *info,\n+\t\t\t\t      u16 info_sz, dma_addr_t addr, u32 length,\n+\t\t\t\t      u32 offset, u16 slot_id,\n+\t\t\t\t      union pds_core_dev_comp *comp)\n+{\n+\tunion pds_core_dev_cmd cmd = {\n+\t\t.send_component.opcode = PDS_CORE_CMD_SEND_COMPONENT,\n+\t\t.send_component.ver = 1,\n+\t\t.send_component.operation = PDS_CORE_SEND_COMPONENT_START,\n+\t\t.send_component.data_pa = cpu_to_le64(addr),\n+\t\t.send_component.data_len = cpu_to_le32(length),\n+\t\t.send_component.offset = cpu_to_le32(offset),\n+\t\t.send_component.slot_id = slot_id,\n+\t};\n+\tunsigned long timeout = 300 * HZ;\n+\tunsigned long start_time;\n+\tunsigned long end_time;\n+\tint err;\n+\n+\tstart_time = jiffies;\n+\tend_time = start_time + timeout;\n+\tdo {\n+\t\t/* prevent noisy/benign devcmd failures */\n+\t\terr = pdsc_devcmd_with_data_nomsg(pdsc, \u0026cmd, info, info_sz,\n+\t\t\t\t\t\t  comp, 60);\n+\t\tif (err != -EAGAIN)\n+\t\t\tbreak;\n+\n+\t\t/* if required, subsequent commands check status of\n+\t\t * PDS_CORE_CMD_SEND_COMPONENT command, which returns\n+\t\t * EAGAIN while the command is still running,\n+\t\t * else we get the final command status.\n+\t\t */\n+\t\tcmd.send_component.operation = PDS_CORE_SEND_COMPONENT_STATUS;\n+\t\tmsleep(20);\n+\t} while (time_before(jiffies, end_time));\n+\n+\tif (err == -EAGAIN || err == -ETIMEDOUT)\n+\t\tdev_err(pdsc-\u003edev, \"PDS_CORE_CMD_SEND_COMPONENT timed out\\n\");\n+\n+\treturn err;\n+}\n+\n+static int pdsc_flash_component_chunk(struct pdsc *pdsc, struct device *dev,\n+\t\t\t\t      struct pds_core_flash_component *info,\n+\t\t\t\t      u16 info_sz, const u8 *data, u16 copy_sz,\n+\t\t\t\t      u32 offset, u8 slot_id,\n+\t\t\t\t      union pds_core_dev_comp *comp)\n+{\n+\tstruct pdsc_deferred_dma *deferred;\n+\tdma_addr_t dma_addr;\n+\tu8 *component_data;\n+\tint err;\n+\n+\tdeferred = kmalloc_obj(*deferred, GFP_KERNEL);\n+\tif (!deferred)\n+\t\treturn -ENOMEM;\n+\n+\tcomponent_data = kmemdup(data, copy_sz, GFP_KERNEL);\n+\tif (!component_data) {\n+\t\tkfree(deferred);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tdma_addr = dma_map_single(dev, component_data, copy_sz, DMA_TO_DEVICE);\n+\tif (dma_mapping_error(dev, dma_addr)) {\n+\t\tdev_err(dev,\n+\t\t\t\"Failed to dma_map component_data at offset 0x%x copy_sz 0x%x\\n\",\n+\t\t\toffset, copy_sz);\n+\t\tkfree(component_data);\n+\t\tkfree(deferred);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\terr = pdsc_devcmd_send_component(pdsc, info, info_sz, dma_addr,\n+\t\t\t\t\t copy_sz, offset, slot_id, comp);\n+\tif (err == -ETIMEDOUT || err == -EAGAIN) {\n+\t\tpdsc_deferred_dma_add(pdsc, deferred, dma_addr,\n+\t\t\t\t      component_data, copy_sz, DMA_TO_DEVICE);\n+\t\treturn err;\n+\t}\n+\n+\tkfree(deferred);\n+\tdma_unmap_single(dev, dma_addr, copy_sz, DMA_TO_DEVICE);\n+\tkfree(component_data);\n+\n+\treturn err;\n+}\n+\n+static int pdsc_flash_component(struct pldmfw *context,\n+\t\t\t\tstruct pldmfw_component *component)\n+{\n+\tchar component_name_buf[PDSC_FW_COMPONENT_FULL_NAME_BUFLEN];\n+\tstruct pds_core_fwu_priv *priv =\n+\t\tcontainer_of(context, struct pds_core_fwu_priv, context);\n+\tstruct pds_core_flash_component *component_info;\n+\tconst char *component_name = NULL;\n+\tstruct device *dev = context-\u003edev;\n+\tstruct pdsc *pdsc = priv-\u003epdsc;\n+\tu16 buf_sz, info_sz;\n+\tstruct devlink *dl;\n+\tu8 component_type;\n+\tu32 total_len;\n+\tu32 offset;\n+\tint err;\n+\n+\tcomponent_type = pdsc_get_component_type_by_id(pdsc,\n+\t\t\t\t\t\t       component-\u003eidentifier);\n+\tif (component_type) {\n+\t\tconst char *type_name = pdsc_fw_type_to_name(component_type);\n+\n+\t\tif (component_type == PDS_CORE_FW_TYPE_MAIN) {\n+\t\t\tcomponent_name = \"fw\";\n+\t\t} else if (type_name) {\n+\t\t\tsnprintf(component_name_buf, sizeof(component_name_buf),\n+\t\t\t\t \"%s%s\", PDSC_FW_COMPONENT_PREFIX, type_name);\n+\t\t\tcomponent_name = component_name_buf;\n+\t\t}\n+\t}\n+\n+\tdl = priv_to_devlink(pdsc);\n+\n+\tif (pdsc_skip_component(priv, component-\u003eidentifier)) {\n+\t\tdevlink_flash_update_status_notify(dl, \"Skipped\",\n+\t\t\t\t\t\t   component_name, 0, 0);\n+\t\treturn 0;\n+\t}\n+\n+\ttotal_len = component-\u003ecomponent_size;\n+\tdev_dbg(dev,\n+\t\t\"component name %s class %u id %u act_meth %u ver_str %.*s index %u size %u\\n\",\n+\t\tcomponent_name ?: \"(unknown)\", component-\u003eclassification,\n+\t\tcomponent-\u003eidentifier, component-\u003eactivation_method,\n+\t\tcomponent-\u003eversion_len, component-\u003eversion_string,\n+\t\tcomponent-\u003eindex, component-\u003ecomponent_size);\n+\n+\tbuf_sz = sizeof(pdsc-\u003ecmd_regs-\u003edata);\n+\tinfo_sz = struct_size(component_info, version_str,\n+\t\t\t      component-\u003eversion_len);\n+\tif (info_sz \u003e buf_sz) {\n+\t\tdev_err(dev, \"component_info size %d too big, max size: %d\\n\",\n+\t\t\tinfo_sz, buf_sz);\n+\t\treturn -ENOSPC;\n+\t}\n+\tcomponent_info = vzalloc(info_sz);\n+\tif (!component_info)\n+\t\treturn -ENOMEM;\n+\n+\tcomponent_info-\u003ecomparison_stamp =\n+\t\tcpu_to_le32(component-\u003ecomparison_stamp);\n+\tcomponent_info-\u003eimage_size = cpu_to_le32(total_len);\n+\tcomponent_info-\u003eclassification = cpu_to_le16(component-\u003eclassification);\n+\tcomponent_info-\u003eidentifier = cpu_to_le16(component-\u003eidentifier);\n+\tcomponent_info-\u003eoptions = cpu_to_le16(component-\u003eoptions);\n+\tcomponent_info-\u003eversion_str_type = component-\u003eversion_type;\n+\tcomponent_info-\u003eversion_str_len = component-\u003eversion_len;\n+\tmemcpy(component_info-\u003eversion_str, component-\u003eversion_string,\n+\t       component-\u003eversion_len);\n+\n+\toffset = 0;\n+\twhile (offset \u003c total_len) {\n+\t\tunion pds_core_dev_comp comp = {};\n+\t\tu16 copy_sz;\n+\n+\t\tcopy_sz = min_t(unsigned int, PDS_PAGE_SIZE,\n+\t\t\t\ttotal_len - offset);\n+\n+\t\terr = pdsc_flash_component_chunk(pdsc, dev, component_info,\n+\t\t\t\t\t\t info_sz,\n+\t\t\t\t\t\t component-\u003ecomponent_data +\n+\t\t\t\t\t\t offset, copy_sz, offset,\n+\t\t\t\t\t\t PDS_CORE_FW_SLOT_INVALID,\n+\t\t\t\t\t\t \u0026comp);\n+\t\tif (err \u0026\u0026\n+\t\t    comp.send_component.compat_response \u0026\u0026\n+\t\t    (comp.send_component.compat_response_code ==\n+\t\t     PDS_CORE_COMPONENT_STAMP_IDENTICAL ||\n+\t\t     comp.send_component.compat_response_code ==\n+\t\t     PDS_CORE_COMPONENT_STAMP_LOWER)) {\n+\t\t\terr = 0;\n+\t\t\tdevlink_flash_update_status_notify(dl, \"Skipped\",\n+\t\t\t\t\t\t\t   component_name,\n+\t\t\t\t\t\t\t   0, 0);\n+\t\t\tgoto skip_component;\n+\t\t}\n+\n+\t\tif (err) {\n+\t\t\tNL_SET_ERR_MSG_MOD(priv-\u003eextack,\n+\t\t\t\t\t   \"Failed to flash component\");\n+\t\t\tgoto err_out;\n+\t\t}\n+\n+\t\toffset += copy_sz;\n+\t\tdevlink_flash_update_status_notify(dl,\n+\t\t\t\t\t\t   \"Erasing/Flashing\",\n+\t\t\t\t\t\t   component_name, offset,\n+\t\t\t\t\t\t   total_len);\n+\t}\n+\n+\tvfree(component_info);\n+\treturn 0;\n+\n+err_out:\n+\tdevlink_flash_update_status_notify(dl,\n+\t\t\t\t\t   \"Erasing/Flashing Component Failed\",\n+\t\t\t\t\t   component_name, 0, 0);\n+skip_component:\n+\tvfree(component_info);\n+\treturn err;\n+}\n+\n+static int pdsc_devcmd_finalize_update(struct pdsc *pdsc)\n+{\n+\tunion pds_core_dev_cmd cmd = {\n+\t\t.finalize_update.opcode = PDS_CORE_CMD_FINALIZE_UPDATE,\n+\t\t.finalize_update.ver = 1,\n+\t};\n+\tunion pds_core_dev_comp comp = {};\n+\n+\treturn pdsc_devcmd(pdsc, \u0026cmd, \u0026comp, pdsc-\u003edevcmd_timeout);\n+}\n+\n+static int pdsc_finalize_update(struct pldmfw *context)\n+{\n+\tstruct pds_core_fwu_priv *priv =\n+\t\tcontainer_of(context, struct pds_core_fwu_priv, context);\n+\tconst char *component_name = priv-\u003eparams-\u003ecomponent;\n+\tunsigned long start_time, end_time;\n+\tstruct device *dev = context-\u003edev;\n+\tstruct pdsc *pdsc = priv-\u003epdsc;\n+\tstruct devlink *dl;\n+\tint err;\n+\n+\tdl = priv_to_devlink(pdsc);\n+\n+\tstart_time = jiffies;\n+\tend_time = start_time + (PDSC_FW_INSTALL_TIMEOUT * HZ);\n+\tdo {\n+\t\terr = pdsc_devcmd_finalize_update(pdsc);\n+\t\tif (err != -EAGAIN)\n+\t\t\tbreak;\n+\n+\t\tdev_dbg(dev, \"retrying finalize_update: %pe\\n\", ERR_PTR(err));\n+\t\tmsleep(20);\n+\t} while (time_before(jiffies, end_time) \u0026\u0026 err == -EAGAIN);\n+\n+\tif (err) {\n+\t\tdevlink_flash_update_status_notify(dl, \"Finalize Update Failed\",\n+\t\t\t\t\t\t   component_name, 0, 0);\n+\t\tNL_SET_ERR_MSG_MOD(priv-\u003eextack, \"Finalize update failed\");\n+\t\treturn err;\n+\t}\n+\n+\tdevlink_flash_update_status_notify(dl, \"Finalized Update\",\n+\t\t\t\t\t   component_name, 0, 0);\n+\treturn 0;\n+}\n+\n+static const struct pldmfw_ops pdsc_pldmfw_ops = {\n+\t.match_record = pdsc_match_record_descs,\n+\t.send_package_data = pdsc_send_package_data,\n+\t.send_component_table = pdsc_send_component_table,\n+\t.flash_component = pdsc_flash_component,\n+\t.finalize_update = pdsc_finalize_update\n+};\n+\n+static int pdsc_pldm_firmware_update(struct pdsc *pdsc,\n+\t\t\t\t     struct devlink_flash_update_params *params,\n+\t\t\t\t     struct netlink_ext_ack *extack,\n+\t\t\t\t     const struct firmware *fw)\n+{\n+\tstruct pds_core_fwu_priv priv = {};\n+\tint err;\n+\n+\tif (!pdsc-\u003efw_components.num_components) {\n+\t\terr = pdsc_get_component_info(pdsc);\n+\t\tif (err) {\n+\t\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t\t   \"Failed to get component info\");\n+\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\tif (params-\u003ecomponent) {\n+\t\tu8 type = pdsc_name_to_fw_type(params-\u003ecomponent);\n+\n+\t\tif (!type || !pdsc_component_type_exists(pdsc, type)) {\n+\t\t\tNL_SET_ERR_MSG_MOD(extack, \"Unknown component name\");\n+\t\t\treturn -ENOENT;\n+\t\t}\n+\t}\n+\n+\tINIT_LIST_HEAD(\u0026priv.components);\n+\tpriv.context.ops = \u0026pdsc_pldmfw_ops;\n+\tpriv.context.dev = pdsc-\u003edev;\n+\tpriv.params = params;\n+\tpriv.extack = extack;\n+\tpriv.pdsc = pdsc;\n+\n+\terr = pldmfw_flash_image(\u0026priv.context, fw);\n+\tif (!err \u0026\u0026 params-\u003ecomponent \u0026\u0026 !priv.component_found) {\n+\t\tNL_SET_ERR_MSG_MOD(extack,\n+\t\t\t\t   \"Requested component not present in firmware package\");\n+\t\terr = -ENOENT;\n+\t}\n+\tpdsc_free_fwu_priv(\u0026priv);\n+\n+\treturn err;\n+}\n+\n+int pdsc_firmware_update(struct pdsc *pdsc,\n+\t\t\t struct devlink_flash_update_params *params,\n+\t\t\t struct netlink_ext_ack *extack)\n+{\n+\tint err;\n+\n+\tif (pdsc-\u003edev_ident.version \u003e= PDS_CORE_IDENTITY_VERSION_2 \u0026\u0026\n+\t    pdsc-\u003edev_ident.capabilities \u0026\n+\t\tcpu_to_le64(PDS_CORE_DEV_CAP_PLDM_FW_UPDATE))\n+\t\terr = pdsc_pldm_firmware_update(pdsc, params, extack,\n+\t\t\t\t\t\tparams-\u003efw);\n+\telse\n+\t\terr = pdsc_legacy_firmware_update(pdsc, params, extack);\n+\n+\t/* Invalidate cached component info so next info_get refreshes */\n+\tpdsc_fw_components_invalidate(pdsc);\n+\n+\treturn err;\n+}\ndiff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c\nindex 8d94a4d70395e..5e1b07850163b 100644\n--- a/drivers/net/ethernet/amd/pds_core/main.c\n+++ b/drivers/net/ethernet/amd/pds_core/main.c\n@@ -21,6 +21,8 @@ static const struct pci_device_id pdsc_id_table[] = {\n };\n MODULE_DEVICE_TABLE(pci, pdsc_id_table);\n \n+static void pdsc_stop_health_thread(struct pdsc *pdsc);\n+\n static void pdsc_wdtimer_cb(struct timer_list *t)\n {\n \tstruct pdsc *pdsc = timer_container_of(pdsc, t, wdtimer);\n@@ -315,6 +317,7 @@ static int pdsc_init_pf(struct pdsc *pdsc)\n \t\tdestroy_workqueue(pdsc-\u003ewq);\n \tmutex_destroy(\u0026pdsc-\u003econfig_lock);\n \tmutex_destroy(\u0026pdsc-\u003edevcmd_lock);\n+\tpdsc_deferred_dma_free(pdsc);\n \tpci_free_irq_vectors(pdsc-\u003epdev);\n err_out_unmap_bars:\n \tpdsc_unmap_bars(pdsc);\n@@ -353,6 +356,8 @@ static int pdsc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)\n \tpdsc-\u003epdev = pdev;\n \tpdsc-\u003edev = \u0026pdev-\u003edev;\n \tset_bit(PDSC_S_INITING_DRIVER, \u0026pdsc-\u003estate);\n+\tINIT_LIST_HEAD(\u0026pdsc-\u003edeferred_dma_list);\n+\tspin_lock_init(\u0026pdsc-\u003edeferred_dma_lock);\n \tpci_set_drvdata(pdev, pdsc);\n \tpdsc_debugfs_add_dev(pdsc);\n \n@@ -458,6 +463,7 @@ static void pdsc_remove(struct pci_dev *pdev)\n \t}\n \n \tpci_disable_device(pdev);\n+\tpdsc_deferred_dma_free(pdsc);\n \n \tida_free(\u0026pdsc_ida, pdsc-\u003euid);\n \tpdsc_debugfs_del_dev(pdsc);\n@@ -505,6 +511,7 @@ static void pdsc_reset_prepare(struct pci_dev *pdev)\n \tpci_release_regions(pdev);\n \tif (pci_is_enabled(pdev))\n \t\tpci_disable_device(pdev);\n+\tpdsc_deferred_dma_free(pdsc);\n }\n \n static void pdsc_reset_done(struct pci_dev *pdev)\ndiff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h\nindex 17a87c1a55d7c..901e1e628f890 100644\n--- a/include/linux/pds/pds_core_if.h\n+++ b/include/linux/pds/pds_core_if.h\n@@ -40,6 +40,14 @@ enum pds_core_cmd_opcode {\n \tPDS_CORE_CMD_FW_DOWNLOAD\t= 4,\n \tPDS_CORE_CMD_FW_CONTROL\t\t= 5,\n \n+\tPDS_CORE_CMD_GET_COMPONENT_INFO\t= 6,\n+\tPDS_CORE_CMD_SEND_PKG_DATA\t= 7,\n+\tPDS_CORE_CMD_SEND_COMPONENT_TBL\t= 8,\n+\tPDS_CORE_CMD_SEND_COMPONENT\t= 9,\n+\tPDS_CORE_CMD_FINALIZE_UPDATE\t= 10,\n+\tPDS_CORE_CMD_MATCH_RECORD_DESC\t= 11,\n+\tPDS_CORE_CMD_HOST_MEM\t\t= 12,\n+\n \t/* SR/IOV commands */\n \tPDS_CORE_CMD_VF_GETATTR\t\t= 60,\n \tPDS_CORE_CMD_VF_SETATTR\t\t= 61,\n@@ -100,6 +108,16 @@ struct pds_core_drv_identity {\n \tchar   driver_ver_str[32];\n };\n \n+/**\n+ * enum pds_core_dev_capability - Device capabilities\n+ * @PDS_CORE_DEV_CAP_PLDM_FW_UPDATE: Device only supports FW update via PLDM\n+ * @PDS_CORE_DEV_CAP_HOST_MEM: Device supports host memory for fw use\n+ */\n+enum pds_core_dev_capability {\n+\tPDS_CORE_DEV_CAP_PLDM_FW_UPDATE = BIT(0),\n+\tPDS_CORE_DEV_CAP_HOST_MEM = BIT(1),\n+};\n+\n #define PDS_DEV_TYPE_MAX\t16\n /**\n  * struct pds_core_dev_identity - Device identity information\n@@ -119,6 +137,11 @@ struct pds_core_drv_identity {\n  *\t\t      value in usecs to device units using:\n  *\t\t      device units = usecs * mult / div\n  * @vif_types:        How many of each VIF device type is supported\n+ * @max_fw_slots:     Number of firmware components reported by device\n+ *\t\t      only supported on version \u003e= PDS_CORE_IDENTITY_VERSION_2\n+ * @rsvd2:\t      Word boundary padding\n+ * @capabilities:     Device capabilities\n+ *\t\t      only supported on version \u003e= PDS_CORE_IDENTITY_VERSION_2\n  */\n struct pds_core_dev_identity {\n \tu8     version;\n@@ -131,9 +154,13 @@ struct pds_core_dev_identity {\n \t__le32 intr_coal_mult;\n \t__le32 intr_coal_div;\n \t__le16 vif_types[PDS_DEV_TYPE_MAX];\n+\t__le16 max_fw_slots;\n+\tu8     rsvd2[6];\n+\t__le64 capabilities;\n };\n \n #define PDS_CORE_IDENTITY_VERSION_1\t1\n+#define PDS_CORE_IDENTITY_VERSION_2\t2\n \n /**\n  * struct pds_core_dev_identify_cmd - Driver/device identify command\n@@ -275,11 +302,20 @@ enum pds_core_fw_control_oper {\n \tPDS_CORE_FW_GET_LIST               = 7,\n };\n \n+/**\n+ * enum pds_core_fw_slot - Firmware slot identifiers\n+ * @PDS_CORE_FW_SLOT_INVALID: Let firmware select slot based on package metadata\n+ * @PDS_CORE_FW_SLOT_A:       Primary firmware slot A\n+ * @PDS_CORE_FW_SLOT_B:       Primary firmware slot B\n+ * @PDS_CORE_FW_SLOT_GOLD:    Gold/recovery firmware slot\n+ * @PDS_CORE_FW_SLOT_MAX:     Sentinel value indicating no slot resolved\n+ */\n enum pds_core_fw_slot {\n \tPDS_CORE_FW_SLOT_INVALID    = 0,\n \tPDS_CORE_FW_SLOT_A\t    = 1,\n \tPDS_CORE_FW_SLOT_B          = 2,\n \tPDS_CORE_FW_SLOT_GOLD       = 3,\n+\tPDS_CORE_FW_SLOT_MAX        = 0xff,\n };\n \n /**\n@@ -446,6 +482,423 @@ struct pds_core_vf_ctrl_comp {\n \tu8\tstatus;\n };\n \n+/**\n+ * struct pds_core_send_pkg_data_cmd - Send package data command\n+ * @opcode: Opcode PDS_CORE_CMD_SEND_PKG_DATA\n+ * @ver: Driver's max support version of this command\n+ * @total_len: Total length of the package data\n+ * @offset: Offset in the package data, non-zero if multiple commands are\n+ *\t    needed for sending the package data\n+ * @data_len: Length of data stored at data_pa\n+ * @data_pa: Data physical address for DMA to device\n+ *\n+ * The package data may be too large to store in a single buffer, so multiple\n+ * PDS_CORE_CMD_SEND_PKG_DATA devcmds may be needed.\n+ */\n+struct pds_core_send_pkg_data_cmd {\n+\tu8 opcode;\n+\tu8 ver;\n+\t__le16 total_len;\n+\t__le16 offset;\n+\t__le16 data_len;\n+\t__le64 data_pa;\n+};\n+\n+/**\n+ * struct pds_core_send_pkg_data_comp - Send package data completion\n+ * @status: Status of the command (enum pds_core_status_code)\n+ * @ver: Device's max supported version of this command\n+ * @rsvd: Word boundary padding\n+ */\n+struct pds_core_send_pkg_data_comp {\n+\tu8 status;\n+\tu8 ver;\n+\tu8 rsvd[2];\n+};\n+\n+/**\n+ * struct pds_core_component_tbl - Component table details\n+ * @comparison_stamp: Comparison stamp used for component version checks\n+ * @classification: Vendor specific classification info\n+ * @identifier: Component's ID\n+ * @transfer_flag: Part of the component table this request represents\n+ * @version_str_type: The types of strings used\n+ * @version_str_len: Length of @version_str\n+ * @version_str: Component version information\n+ */\n+struct pds_core_component_tbl {\n+\t__le32 comparison_stamp;\n+\t__le16 classification;\n+\t__le16 identifier;\n+\tu8     transfer_flag;\n+\tu8     version_str_type;\n+\tu8     version_str_len;\n+\tu8     version_str[];\n+};\n+\n+/**\n+ * struct pds_core_send_component_tbl_cmd - Send component table command\n+ * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT_TBL\n+ * @ver: Driver's max support version of this command\n+ * @slot_id: enum pds_core_fw_slot\n+ * @rsvd: Word boundary padding\n+ *\n+ * Expects to find component table info (struct pds_core_component_tbl)\n+ * in cmd_regs-\u003edata.  Driver should keep the devcmd interface locked\n+ * while preparing the component table info.\n+ */\n+struct pds_core_send_component_tbl_cmd {\n+\tu8 opcode;\n+\tu8 ver;\n+\tu8 slot_id;\n+\tu8 rsvd;\n+};\n+\n+enum pds_core_component_resp_code {\n+\tPDS_CORE_COMPONENT_VALID = 0x0,\n+\tPDS_CORE_COMPONENT_STAMP_IDENTICAL = 0x1,\n+\tPDS_CORE_COMPONENT_STAMP_LOWER = 0x2,\n+\tPDS_CORE_COMPONENT_STAMP_OR_VERSION_INVALID = 0x3,\n+\tPDS_CORE_COMPONENT_CONFLICT = 0x4,\n+\tPDS_CORE_COMPONENT_PREREQS_NOT_MET = 0x5,\n+\tPDS_CORE_COMPONENT_NOT_SUPPORTED = 0x6,\n+\tPDS_CORE_COMPONENT_FW_TYPE_INVALID = 0xd0,\n+};\n+\n+/**\n+ * struct pds_core_send_component_tbl_comp - Send component table completion\n+ * @status: Status of the command (enum pds_core_status_code)\n+ * @ver: Device's max supported version of this command\n+ * @completion_code: Component completion code\n+ * @response: Component response\n+ * @response_code: Component response code\n+ * @slot_id: Actual slot_id of the component (enum pds_core_fw_slot)\n+ * @rsvd: Word boundary padding\n+ */\n+struct pds_core_send_component_tbl_comp {\n+\tu8 status;\n+\tu8 ver;\n+\tu8 completion_code;\n+\tu8 response;\n+\tu8 response_code;\n+\tu8 slot_id;\n+\tu8 rsvd[2];\n+};\n+\n+/**\n+ * enum pds_core_send_component_op - PDS_CORE_CMD_SEND_COMPONENT operation\n+ * @PDS_CORE_SEND_COMPONENT_START: Initial operation to start transfer\n+ * @PDS_CORE_SEND_COMPONENT_STATUS: Subsequent calls to check on status\n+ */\n+enum pds_core_send_component_op {\n+\tPDS_CORE_SEND_COMPONENT_START = 0,\n+\tPDS_CORE_SEND_COMPONENT_STATUS = 1,\n+};\n+\n+#define PDS_CORE_FW_COMPONENT_ID_INVALID 0xFFFF\n+/**\n+ * struct pds_core_flash_component - Component details\n+ * @comparison_stamp: Comparison stamp used for component version checks\n+ * @image_size: Component image size\n+ * @classification: Vendor specific classification info\n+ * @identifier: Component's ID\n+ * @options: Component options\n+ * @rsvd: Word boundary padding\n+ * @version_str_type: The types of strings used\n+ * @version_str_len: Length of @version_str\n+ * @version_str: Component version information\n+ */\n+struct pds_core_flash_component {\n+\t__le32 comparison_stamp;\n+\t__le32 image_size;\n+\t__le16 classification;\n+\t__le16 identifier;\n+\t__le16 options;\n+\tu8 rsvd[3];\n+\tu8 version_str_type;\n+\tu8 version_str_len;\n+\tu8 version_str[];\n+};\n+\n+/**\n+ * struct pds_core_send_component_cmd - Send component command\n+ * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT\n+ * @ver: Driver's max supported version of this command\n+ * @slot_id: enum pds_core_fw_slot\n+ * @operation: enum pds_core_send_component_op\n+ * @offset: Offset into the component, non-zero if multiple commands\n+ *\t    are needed for a single component\n+ * @data_len: Length of this part of the component stored at @data_pa\n+ * @rsvd: Word boundary padding\n+ * @data_pa: DMA address of the component\n+ *\n+ * A component may be too large to store in a single buffer, so multiple\n+ * PDS_CORE_CMD_SEND_COMPONENT devcmds may be needed.\n+ *\n+ * Expects to find flash component info (struct pds_core_flash_component)\n+ * in cmd_regs-\u003edata. Driver should keep the devcmd interface locked\n+ * while preparing and sending the flash component info.\n+ */\n+struct pds_core_send_component_cmd {\n+\tu8 opcode;\n+\tu8 ver;\n+\tu8 slot_id;\n+\tu8 operation;\n+\t__le32 offset;\n+\t__le32 data_len;\n+\tu8 rsvd[4];\n+\t__le64 data_pa;\n+};\n+\n+/**\n+ * struct pds_core_send_component_comp - Send component completion\n+ * @status: Status of the command (enum pds_core_status_code)\n+ * @ver: Device's max supported version of this command\n+ * @completion_code: Completion code\n+ * @compat_response: Compatibility response (0 = Component can be updated)\n+ * @compat_response_code: Compatibility response code\n+ * @rsvd: Word boundary padding\n+ */\n+struct pds_core_send_component_comp {\n+\tu8 status;\n+\tu8 ver;\n+\tu8 completion_code;\n+\tu8 compat_response;\n+\tu8 compat_response_code;\n+\tu8 rsvd[3];\n+};\n+\n+/**\n+ * enum pds_core_fw_component_type - Firmware component type\n+ * @PDS_CORE_FW_TYPE_UNKNOWN: Unknown component type\n+ * @PDS_CORE_FW_TYPE_MAIN: Main firmware\n+ * @PDS_CORE_FW_TYPE_BOOT: Boot loader\n+ * @PDS_CORE_FW_TYPE_CPLD: CPLD firmware\n+ * @PDS_CORE_FW_TYPE_SECURE: Secure firmware\n+ * @PDS_CORE_FW_TYPE_FPGA: FPGA configuration\n+ * @PDS_CORE_FW_TYPE_SUC_MAIN: System Unit Controller firmware\n+ * @PDS_CORE_FW_TYPE_SUC_BOOT: System Unit Controller bootloader\n+ * @PDS_CORE_FW_TYPE_UBOOT: U-Boot bootloader\n+ *\n+ * Gold/recovery variants are identified by slot_id == PDS_CORE_FW_SLOT_GOLD\n+ * and reported with a \".gold\" suffix (e.g., fw.gold).\n+ */\n+enum pds_core_fw_component_type {\n+\tPDS_CORE_FW_TYPE_UNKNOWN   = 0,\n+\tPDS_CORE_FW_TYPE_MAIN      = 1,\n+\tPDS_CORE_FW_TYPE_BOOT      = 2,\n+\tPDS_CORE_FW_TYPE_CPLD      = 3,\n+\tPDS_CORE_FW_TYPE_SECURE    = 4,\n+\tPDS_CORE_FW_TYPE_FPGA      = 5,\n+\tPDS_CORE_FW_TYPE_SUC_MAIN  = 6,\n+\tPDS_CORE_FW_TYPE_SUC_BOOT  = 7,\n+\tPDS_CORE_FW_TYPE_UBOOT     = 8,\n+};\n+\n+/**\n+ * enum pds_core_component_info_flags - Component info flags\n+ * @PDS_CORE_FW_COMPONENT_INFO_F_RUNNING: Component is currently running\n+ * @PDS_CORE_FW_COMPONENT_INFO_F_STARTUP: Component version on next FW boot\n+ * @PDS_CORE_FW_COMPONENT_INFO_F_FIXED: Component is fixed and cannot be updated\n+ * @PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME: Component can be updated\n+ *\tby name\n+ */\n+enum pds_core_component_info_flags {\n+\tPDS_CORE_FW_COMPONENT_INFO_F_RUNNING = BIT(0),\n+\tPDS_CORE_FW_COMPONENT_INFO_F_STARTUP = BIT(1),\n+\tPDS_CORE_FW_COMPONENT_INFO_F_FIXED = BIT(2),\n+\tPDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME = BIT(3),\n+};\n+\n+/**\n+ * struct pds_core_fw_component_info - GET_COMPONENT_INFO entry\n+ * @name: Component's name\n+ * @component_type: enum pds_core_fw_component_type\n+ * @rsvd: Word boundary padding\n+ * @flags: enum pds_core_component_info_flags\n+ * @identifier: Component's identifier\n+ * @slot_id: Component's slot identifier\n+ * @version: Component's version\n+ */\n+struct pds_core_fw_component_info {\n+#define PDS_CORE_FW_COMPONENT_NAME_BUFLEN 24\n+\tchar name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN];\n+\tu8 component_type;\n+\tu8 rsvd[3];\n+\t__le16 flags;\n+\tu8 identifier;\n+\tu8 slot_id;\n+#define PDS_CORE_FW_COMPONENT_VER_BUFLEN 32\n+\tchar version[PDS_CORE_FW_COMPONENT_VER_BUFLEN];\n+};\n+\n+#define PDS_CORE_FW_COMPONENT_LIST_LEN\t((PDS_PAGE_SIZE - 8) / \\\n+\t\tsizeof(struct pds_core_fw_component_info))\n+\n+/**\n+ * struct pds_core_component_list_info - GET_COMPONENT_INFO completion data\n+ * @num_components: Number of valid components\n+ * @rsvd: Word boundary padding\n+ * @info: List of valid components\n+ */\n+struct pds_core_component_list_info {\n+\tu8 num_components;\n+\tu8 rsvd[7];\n+\tstruct pds_core_fw_component_info info[PDS_CORE_FW_COMPONENT_LIST_LEN];\n+};\n+\n+/**\n+ * struct pds_core_get_component_info_cmd - GET_COMPONENT_INFO command\n+ * @opcode: PDS_CORE_CMD_GET_COMPONENT_INFO\n+ * @ver: Driver's max supported version of this command\n+ * @data_len: Length of data at data_pa\n+ * @rsvd: Word boundary padding\n+ * @data_pa: DMA address of data\n+ *\n+ * FW populates struct pds_core_component_list_info pointed to by @data_pa\n+ */\n+struct pds_core_get_component_info_cmd {\n+\tu8 opcode;\n+\tu8 ver;\n+\t__le16 data_len;\n+\tu8 rsvd[4];\n+\t__le64 data_pa;\n+};\n+\n+/**\n+ * struct pds_core_get_component_info_comp - GET_COMPONENT_INFO completion\n+ * @status: enum pds_core_status_code\n+ * @ver: Device's max supported version of this command\n+ * @rsvd: Word boundary padding\n+ */\n+struct pds_core_get_component_info_comp {\n+\tu8 status;\n+\tu8 ver;\n+\tu8 rsvd[2];\n+};\n+\n+/**\n+ * struct pds_core_finalize_update_cmd - FINALIZE_UPDATE command\n+ * @opcode: PDS_CORE_CMD_FINALIZE_UPDATE\n+ * @ver: Driver's max support version of this command\n+ * @rsvd: Word boundary padding\n+ *\n+ * Driver sends at the end of updating all components to finalize the update\n+ */\n+struct pds_core_finalize_update_cmd {\n+\tu8 opcode;\n+\tu8 ver;\n+\tu8 rsvd[2];\n+};\n+\n+/**\n+ * struct pds_core_finalize_update_comp - FINALIZE_UPDATE completion\n+ * @status: enum pds_core_status_code\n+ * @ver: Device's max supported version of this command\n+ * @rsvd: Word boundary padding\n+ */\n+struct pds_core_finalize_update_comp {\n+\tu8 status;\n+\tu8 ver;\n+\tu8 rsvd[2];\n+};\n+\n+/**\n+ * struct pds_core_match_record_desc_cmd - MATCH_RECORD_DESC command\n+ * @opcode: PDS_CORE_CMD_MATCH_RECORD_DESC\n+ * @ver: Driver's max supported version of this command\n+ * @type: PLDM Descriptor Identifier Type\n+ * @size: Length of the Descriptor Identifier Value\n+ * @rsvd: Word boundary padding\n+ *\n+ * Expects to find the Descriptor Identifier Data in cmd_regs-\u003edata. Driver\n+ * should keep the devcmd interface locked while preparing and sending this\n+ * command.\n+ */\n+struct pds_core_match_record_desc_cmd {\n+\tu8 opcode;\n+\tu8 ver;\n+\t__le16 type;\n+\t__le16 size;\n+\tu8 rsvd[2];\n+};\n+\n+/**\n+ * struct pds_core_match_record_desc_comp - MATCH_RECORD_DESC completion\n+ * @status: enum pds_core_status_code\n+ * @ver: Device's max supported version of this command\n+ * @match: Whether or not the Record Descriptor matches the device\n+ * @rsvd: Word boundary padding\n+ *\n+ * When status is PDS_RC_SUCCESS, then @match is valid, otherwise it's\n+ * undefined.\n+ */\n+struct pds_core_match_record_desc_comp {\n+\tu8 status;\n+\tu8 ver;\n+\tu8 match;\n+\tu8 rsvd;\n+};\n+\n+/**\n+ * enum pds_core_host_mem_oper - HOST_MEM sub-operations\n+ * @PDS_CORE_HOST_MEM_GET_COUNT: Query number of memory requests\n+ * @PDS_CORE_HOST_MEM_QUERY:     Query details of a memory request\n+ * @PDS_CORE_HOST_MEM_ADD:       Provide allocated memory to firmware\n+ * @PDS_CORE_HOST_MEM_DEL:       Notify firmware of memory deallocation\n+ */\n+enum pds_core_host_mem_oper {\n+\tPDS_CORE_HOST_MEM_GET_COUNT\t= 0,\n+\tPDS_CORE_HOST_MEM_QUERY\t\t= 1,\n+\tPDS_CORE_HOST_MEM_ADD\t\t= 2,\n+\tPDS_CORE_HOST_MEM_DEL\t\t= 3,\n+};\n+\n+/**\n+ * struct pds_core_host_mem_cmd - HOST_MEM command\n+ * @opcode:     Opcode PDS_CORE_CMD_HOST_MEM\n+ * @oper:       Operation (enum pds_core_host_mem_oper)\n+ * @index:      Memory request index (GET_COUNT: max_count, QUERY: index)\n+ * @tag:        Tag for this memory request (ADD/DEL)\n+ * @reason:     Reason for deletion (DEL only)\n+ * @rsvd:       Reserved\n+ * @max_contig: Maximum contiguous memory size (GET_COUNT only)\n+ * @size:       Size of memory in bytes (ADD only)\n+ * @buf_pa:     DMA address of memory (ADD only)\n+ *\n+ * Unified command for all host memory operations. Fields are reused\n+ * across operations to minimize opcode space usage.\n+ */\n+struct pds_core_host_mem_cmd {\n+\tu8     opcode;\n+\tu8     oper;\n+\t__le16 index;\n+\t__le16 tag;\n+\tu8     reason;\n+\tu8     rsvd;\n+\t__le32 max_contig;\n+\t__le32 size;\n+\t__le64 buf_pa;\n+};\n+\n+/**\n+ * struct pds_core_host_mem_comp - HOST_MEM completion\n+ * @status:       Status of the command (enum pds_core_status_code)\n+ * @oper:         Operation that was performed\n+ * @count:        Number of memory requests (GET_COUNT)\n+ * @size:         Size of memory request in bytes (QUERY)\n+ * @tag:          Tag for this memory request (QUERY/DEL)\n+ * @rsvd:         Reserved\n+ */\n+struct pds_core_host_mem_comp {\n+\tu8     status;\n+\tu8     oper;\n+\t__le16 count;\n+\t__le32 size;\n+\t__le16 tag;\n+\tu8     rsvd[6];\n+};\n+\n /*\n  * union pds_core_dev_cmd - Overlay of core device command structures\n  */\n@@ -462,6 +915,14 @@ union pds_core_dev_cmd {\n \tstruct pds_core_vf_setattr_cmd   vf_setattr;\n \tstruct pds_core_vf_getattr_cmd   vf_getattr;\n \tstruct pds_core_vf_ctrl_cmd      vf_ctrl;\n+\n+\tstruct pds_core_get_component_info_cmd get_component_info;\n+\tstruct pds_core_send_pkg_data_cmd      send_pkg_data;\n+\tstruct pds_core_send_component_tbl_cmd send_component_tbl;\n+\tstruct pds_core_send_component_cmd     send_component;\n+\tstruct pds_core_finalize_update_cmd    finalize_update;\n+\tstruct pds_core_match_record_desc_cmd  match_record_desc;\n+\tstruct pds_core_host_mem_cmd           host_mem;\n };\n \n /*\n@@ -480,6 +941,14 @@ union pds_core_dev_comp {\n \tstruct pds_core_vf_setattr_comp   vf_setattr;\n \tstruct pds_core_vf_getattr_comp   vf_getattr;\n \tstruct pds_core_vf_ctrl_comp      vf_ctrl;\n+\n+\tstruct pds_core_get_component_info_comp get_component_info;\n+\tstruct pds_core_send_pkg_data_comp      send_pkg_data;\n+\tstruct pds_core_send_component_tbl_comp send_component_tbl;\n+\tstruct pds_core_send_component_comp     send_component;\n+\tstruct pds_core_finalize_update_comp    finalize_update;\n+\tstruct pds_core_match_record_desc_comp  match_record_desc;\n+\tstruct pds_core_host_mem_comp           host_mem;\n };\n \n /**\n"
}

2/1 2026/07/31 13:30 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the pds_core driver, which is a vendor-specific SmartNIC driver that requires physical PCIe hardware not emulated in standard QEMU.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 1dabafcb2e9b03b4e0967c943e5434c59ed2a7df
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Jul 31 13:30:32 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst
index 9e8a16c441029..49487a35c1632 100644
--- a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst
+++ b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst
@@ -102,6 +102,95 @@ currently in use, and that bank will used for the next boot::
   # devlink dev flash pci/0000:b5:00.0 \
             file pensando/dsc_fw_1.63.0-22.tar
 
+Firmware Management (PLDM)
+==========================
+
+Firmware that supports PLDM can be updated using the devlink flash command
+with a PLDM firmware package. The entire package can be updated at once::
+
+  # devlink dev flash pci/0000:b5:00.0 file firmware.pldmfw
+
+Individual components can also be updated by specifying the component name::
+
+  # devlink dev flash pci/0000:b5:00.0 \
+            file firmware.pldmfw component fw.cpld
+
+Per-component update uses driver-defined component names (fw, fw.cpld,
+etc.). Not all components support per-component update -
+devlink will reject the request if the specified component cannot
+be updated.
+
+Gold (recovery) components can be updated by specifying the base component
+name (e.g., ``fw`` for ``fw.gold``) with a goldfw package file when the
+device supports per-component update. The ``.gold`` suffix in devlink info
+output indicates the gold slot version, not a flash target.
+
+Info versions (PLDM)
+====================
+
+Firmware that supports PLDM reports component versions using driver-defined
+names. The driver reports the following component versions:
+
+.. list-table:: devlink info versions for PLDM-capable firmware
+   :widths: 5 5 90
+
+   * - Name
+     - Type
+     - Description
+   * - ``fw``
+     - running, stored
+     - Main firmware
+   * - ``fw.gold``
+     - stored
+     - Gold (recovery) firmware
+   * - ``fw.bootloader``
+     - running, stored
+     - Boot loader
+   * - ``fw.cpld``
+     - running, stored
+     - CPLD
+   * - ``fw.secure``
+     - running, stored
+     - Secure boot firmware
+   * - ``fw.fpga``
+     - running, stored
+     - FPGA configuration
+   * - ``fw.suc``
+     - running, stored
+     - System Unit Controller firmware
+   * - ``fw.suc.bootloader``
+     - running, stored
+     - System Unit Controller bootloader
+   * - ``fw.uboot``
+     - running, stored
+     - U-Boot bootloader
+   * - ``asic.id``
+     - fixed
+     - The ASIC type for this device
+   * - ``asic.rev``
+     - fixed
+     - The revision of the ASIC for this device
+
+Example output::
+
+  $ devlink dev info pci/0000:00:05.0
+  pci/0000:00:05.0:
+    driver pds_core
+    serial_number FLM18420073
+    versions:
+        fixed:
+          asic.id 0x0
+          asic.rev 0x0
+        running:
+          fw.bootloader 1.2.3
+          fw 1.3.0
+          fw.cpld 3.18
+        stored:
+          fw.bootloader 1.2.3
+          fw.gold 1.2.0
+          fw 1.3.0
+          fw.cpld 3.18
+
 Health Reporters
 ================
 
diff --git a/drivers/net/ethernet/amd/Kconfig b/drivers/net/ethernet/amd/Kconfig
index e35991141a1a2..743e3d4b6b94e 100644
--- a/drivers/net/ethernet/amd/Kconfig
+++ b/drivers/net/ethernet/amd/Kconfig
@@ -171,6 +171,7 @@ config PDS_CORE
 	depends on 64BIT && PCI
 	select AUXILIARY_BUS
 	select NET_DEVLINK
+	select PLDMFW
 	help
 	  This enables the support for the AMD/Pensando Core device family of
 	  adapters.  More specific information on this driver can be
diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
index 04ec2569c61c0..922e3ec8af1b9 100644
--- a/drivers/net/ethernet/amd/pds_core/core.c
+++ b/drivers/net/ethernet/amd/pds_core/core.c
@@ -489,6 +489,15 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing)
 		pdsc_devcmd_reset(pdsc);
 
 	pci_clear_master(pdsc->pdev);
+	if (!pdsc->pdev->is_virtfn) {
+		u16 val;
+
+		/* Flush any in-flight DMA before freeing buffers.
+		 * A config read completion cannot return until all prior
+		 * device-initiated memory writes have completed.
+		 */
+		pci_read_config_word(pdsc->pdev, PCI_VENDOR_ID, &val);
+	}
 
 	pdsc_core_uninit(pdsc);
 
@@ -497,6 +506,8 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing)
 		pdsc->viftype_status = NULL;
 	}
 
+	pdsc_debugfs_del_host_mem(pdsc);
+	pdsc_host_mem_free(pdsc);
 	pdsc_dev_uninit(pdsc);
 
 	set_bit(PDSC_S_FW_DEAD, &pdsc->state);
@@ -506,6 +517,8 @@ int pdsc_start(struct pdsc *pdsc)
 {
 	pds_core_intr_mask(&pdsc->intr_ctrl[pdsc->adminqcq.intx],
 			   PDS_CORE_INTR_MASK_CLEAR);
+	pdsc_host_mem_add(pdsc);
+	pdsc_debugfs_add_host_mem(pdsc);
 
 	return 0;
 }
@@ -587,6 +600,8 @@ void pdsc_fw_up(struct pdsc *pdsc)
 		return;
 	}
 
+	pdsc_fw_components_invalidate(pdsc);
+
 	err = pdsc_setup(pdsc, PDSC_SETUP_RECOVERY);
 	if (err)
 		goto err_out;
@@ -670,3 +685,161 @@ void pdsc_health_thread(struct work_struct *work)
 out_unlock:
 	mutex_unlock(&pdsc->config_lock);
 }
+
+static void pdsc_host_mem_del_one(struct pdsc *pdsc, u16 tag, u8 reason)
+{
+	union pds_core_dev_comp comp = {};
+	union pds_core_dev_cmd cmd = {
+		.host_mem.opcode = PDS_CORE_CMD_HOST_MEM,
+		.host_mem.oper = PDS_CORE_HOST_MEM_DEL,
+		.host_mem.tag = cpu_to_le16(tag),
+		.host_mem.reason = reason,
+	};
+
+	dev_dbg(pdsc->dev, "Sending devcmd for mem del tag %d\n", tag);
+	pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+}
+
+static int pdsc_host_mem_add_one(struct pdsc *pdsc, int index)
+{
+	struct pdsc_host_mem *hm = &pdsc->host_mem_reqs[index];
+	union pds_core_dev_comp comp = {};
+	union pds_core_dev_cmd cmd = {};
+	int err;
+
+	cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;
+	cmd.host_mem.oper = PDS_CORE_HOST_MEM_QUERY;
+	cmd.host_mem.index = cpu_to_le16(index);
+	dev_dbg(pdsc->dev, "Sending devcmd for mem query index %d\n", index);
+	err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+	if (err || comp.status != PDS_RC_SUCCESS) {
+		dev_err(pdsc->dev, "mem query failed err %d status %d\n",
+			err, comp.status);
+		return err ? err : -EIO;
+	}
+	hm->size = le32_to_cpu(comp.host_mem.size);
+	hm->tag = le16_to_cpu(comp.host_mem.tag);
+	dev_dbg(pdsc->dev, "mem query returned size %d tag %d\n",
+		hm->size, hm->tag);
+
+	if (!hm->size || hm->size > PDSC_HOST_MEM_MAX_CONTIG) {
+		dev_err(pdsc->dev, "invalid size %d for tag %d\n",
+			hm->size, hm->tag);
+		err = -EINVAL;
+		goto err_del;
+	}
+
+	hm->order = get_order(hm->size);
+	hm->pg = alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN, hm->order);
+	if (!hm->pg) {
+		dev_warn(pdsc->dev, "alloc order %d failed for tag %d\n",
+			 hm->order, hm->tag);
+		err = -ENOMEM;
+		goto err_del;
+	}
+
+	hm->pa = dma_map_page(pdsc->dev, hm->pg, 0, hm->size,
+			      DMA_BIDIRECTIONAL);
+	if (dma_mapping_error(pdsc->dev, hm->pa)) {
+		dev_err(pdsc->dev, "dma map failed for tag %d size %d\n",
+			hm->tag, hm->size);
+		__free_pages(hm->pg, hm->order);
+		hm->pg = NULL;
+		err = -EIO;
+		goto err_del;
+	}
+
+	/* Track this allocation so pdsc_host_mem_free() can clean it up */
+	pdsc->num_host_mem_reqs++;
+
+	memset(&cmd, 0, sizeof(cmd));
+	memset(&comp, 0, sizeof(comp));
+	cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;
+	cmd.host_mem.oper = PDS_CORE_HOST_MEM_ADD;
+	cmd.host_mem.tag = cpu_to_le16(hm->tag);
+	cmd.host_mem.size = cpu_to_le32(hm->size);
+	cmd.host_mem.buf_pa = cpu_to_le64(hm->pa);
+
+	dev_dbg(pdsc->dev, "Sending devcmd for mem add tag %d size %d pa %pad\n",
+		hm->tag, hm->size, &hm->pa);
+	err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+	if (err || comp.status != PDS_RC_SUCCESS) {
+		dev_err(pdsc->dev, "mem add failed err %d status %d for tag %d\n",
+			err, comp.status, hm->tag);
+		err = err ? err : -EIO;
+		goto err_del;
+	}
+	dev_dbg(pdsc->dev, "mem add completed for tag %d\n", hm->tag);
+
+	return 0;
+
+err_del:
+	/* After MEM_QUERY succeeds, firmware expects MEM_ADD or MEM_DEL */
+	pdsc_host_mem_del_one(pdsc, hm->tag, PDS_RC_ENOMEM);
+	return err;
+}
+
+void pdsc_host_mem_add(struct pdsc *pdsc)
+{
+	union pds_core_dev_comp comp = {};
+	union pds_core_dev_cmd cmd = {};
+	u16 count;
+	int err;
+	int i;
+
+	if (!(pdsc->dev_ident.capabilities &
+	     cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM)))
+		return;
+
+	cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;
+	cmd.host_mem.oper = PDS_CORE_HOST_MEM_GET_COUNT;
+	cmd.host_mem.index = cpu_to_le16(PDSC_HOST_MEM_MAX_COUNT);
+	cmd.host_mem.max_contig = cpu_to_le32(PDSC_HOST_MEM_MAX_CONTIG);
+	dev_dbg(pdsc->dev, "Sending devcmd for mem get count max_contig %u\n",
+		PDSC_HOST_MEM_MAX_CONTIG);
+	err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+	if (err || comp.status != PDS_RC_SUCCESS) {
+		dev_err(pdsc->dev, "mem get count failed err %d status %d\n",
+			err, comp.status);
+		return;
+	}
+
+	count = min(le16_to_cpu(comp.host_mem.count),
+		    PDSC_HOST_MEM_MAX_COUNT);
+	dev_dbg(pdsc->dev, "mem get count returned count %d\n", count);
+	if (count == 0)
+		return;
+
+	pdsc->host_mem_reqs = kzalloc_objs(*pdsc->host_mem_reqs, count,
+					   GFP_KERNEL);
+	if (!pdsc->host_mem_reqs) {
+		dev_err(pdsc->dev, "failed to alloc host_mem_reqs array\n");
+		return;
+	}
+
+	for (i = 0; i < count; i++) {
+		err = pdsc_host_mem_add_one(pdsc, i);
+		if (err)
+			break;
+	}
+}
+
+void pdsc_host_mem_free(struct pdsc *pdsc)
+{
+	int i;
+
+	if (!pdsc->host_mem_reqs)
+		return;
+
+	for (i = 0; i < pdsc->num_host_mem_reqs; i++) {
+		dma_unmap_page(pdsc->dev, pdsc->host_mem_reqs[i].pa,
+			       pdsc->host_mem_reqs[i].size,
+			       DMA_BIDIRECTIONAL);
+		__free_pages(pdsc->host_mem_reqs[i].pg,
+			     pdsc->host_mem_reqs[i].order);
+	}
+
+	kfree(pdsc->host_mem_reqs);
+	pdsc->host_mem_reqs = NULL;
+	pdsc->num_host_mem_reqs = 0;
+}
diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h
index b7fe9ad733498..791756a50870e 100644
--- a/drivers/net/ethernet/amd/pds_core/core.h
+++ b/drivers/net/ethernet/amd/pds_core/core.h
@@ -5,6 +5,7 @@
 #define _PDSC_H_
 
 #include <linux/debugfs.h>
+#include <linux/mmzone.h>
 #include <net/devlink.h>
 
 #include <linux/pds/pds_common.h>
@@ -23,6 +24,20 @@
 #define PDSC_SETUP_RECOVERY	false
 #define PDSC_SETUP_INIT		true
 
+/* Use fixed 4MB instead of PAGE_SIZE << MAX_PAGE_ORDER to avoid
+ * cpu_to_le32() truncation on large-page configs
+ */
+#define PDSC_HOST_MEM_MAX_CONTIG (4 * 1024 * 1024)
+#define PDSC_HOST_MEM_MAX_COUNT  256
+
+struct pdsc_deferred_dma {
+	struct list_head list;
+	dma_addr_t dma_addr;
+	void *va;
+	size_t size;
+	enum dma_data_direction dir;
+};
+
 struct pdsc_dev_bar {
 	void __iomem *vaddr;
 	phys_addr_t bus_addr;
@@ -141,6 +156,14 @@ struct pdsc_viftype {
 	struct pds_auxiliary_dev *padev;
 };
 
+struct pdsc_host_mem {
+	u32 size;
+	u16 tag;
+	u8 order;
+	struct page *pg;
+	dma_addr_t pa;
+};
+
 /* No state flags set means we are in a steady running state */
 enum pdsc_state_flags {
 	PDSC_S_FW_DEAD,		    /* stopped, wait on startup or recovery */
@@ -185,6 +208,8 @@ struct pdsc {
 	struct mutex devcmd_lock;	/* lock for dev_cmd operations */
 	struct mutex config_lock;	/* lock for configuration operations */
 	spinlock_t adminq_lock;		/* lock for adminq operations */
+	struct list_head deferred_dma_list;
+	spinlock_t deferred_dma_lock;	/* lock for deferred DMA list */
 	refcount_t adminq_refcnt;
 	struct pds_core_dev_info_regs __iomem *info_regs;
 	struct pds_core_dev_cmd_regs __iomem *cmd_regs;
@@ -199,6 +224,11 @@ struct pdsc {
 	u64 last_eid;
 	struct pdsc_viftype *viftype_status;
 	struct work_struct pci_reset_work;
+
+	struct pdsc_host_mem *host_mem_reqs;
+	u16 num_host_mem_reqs;
+
+	struct pds_core_component_list_info fw_components;
 };
 
 /** enum pds_core_dbell_bits - bitwise composition of dbell values.
@@ -275,14 +305,24 @@ void pdsc_debugfs_add_viftype(struct pdsc *pdsc);
 void pdsc_debugfs_add_irqs(struct pdsc *pdsc);
 void pdsc_debugfs_add_qcq(struct pdsc *pdsc, struct pdsc_qcq *qcq);
 void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq);
+void pdsc_debugfs_add_host_mem(struct pdsc *pdsc);
+void pdsc_debugfs_del_host_mem(struct pdsc *pdsc);
 
 int pdsc_err_to_errno(enum pds_core_status_code code);
 bool pdsc_is_fw_running(struct pdsc *pdsc);
 bool pdsc_is_fw_good(struct pdsc *pdsc);
 int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
 		union pds_core_dev_comp *comp, int max_seconds);
+int pdsc_devcmd_with_data(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+			  const void *data, size_t data_len,
+			  union pds_core_dev_comp *comp, int max_seconds);
+int pdsc_devcmd_with_data_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+				const void *data, size_t data_len,
+				union pds_core_dev_comp *comp, int max_seconds);
 int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
 		       union pds_core_dev_comp *comp, int max_seconds);
+int pdsc_devcmd_locked_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+			     union pds_core_dev_comp *comp, int max_seconds);
 int pdsc_devcmd_init(struct pdsc *pdsc);
 int pdsc_devcmd_reset(struct pdsc *pdsc);
 int pdsc_dev_init(struct pdsc *pdsc);
@@ -315,11 +355,23 @@ void pdsc_process_adminq(struct pdsc_qcq *qcq);
 void pdsc_work_thread(struct work_struct *work);
 irqreturn_t pdsc_adminq_isr(int irq, void *data);
 
-int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
+int pdsc_firmware_update(struct pdsc *pdsc,
+			 struct devlink_flash_update_params *params,
 			 struct netlink_ext_ack *extack);
+int pdsc_get_component_info(struct pdsc *pdsc);
+const char *pdsc_fw_type_to_name(u8 type);
+void pdsc_fw_components_invalidate(struct pdsc *pdsc);
 
 void pdsc_fw_down(struct pdsc *pdsc);
 void pdsc_fw_up(struct pdsc *pdsc);
 void pdsc_pci_reset_thread(struct work_struct *work);
 
+void pdsc_host_mem_add(struct pdsc *pdsc);
+void pdsc_host_mem_free(struct pdsc *pdsc);
+
+void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry,
+			   dma_addr_t dma_addr, void *va, size_t size,
+			   enum dma_data_direction dir);
+void pdsc_deferred_dma_free(struct pdsc *pdsc);
+
 #endif /* _PDSC_H_ */
diff --git a/drivers/net/ethernet/amd/pds_core/debugfs.c b/drivers/net/ethernet/amd/pds_core/debugfs.c
index 810a0cd9bcac8..ef0a1b7d159b5 100644
--- a/drivers/net/ethernet/amd/pds_core/debugfs.c
+++ b/drivers/net/ethernet/amd/pds_core/debugfs.c
@@ -178,3 +178,48 @@ void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq)
 	debugfs_remove_recursive(qcq->dentry);
 	qcq->dentry = NULL;
 }
+
+static int host_mem_show(struct seq_file *seq, void *v)
+{
+	struct pdsc *pdsc = seq->private;
+	struct pdsc_host_mem *hm;
+	int i;
+
+	if (!pdsc->host_mem_reqs || pdsc->num_host_mem_reqs == 0) {
+		seq_puts(seq, "No host memory allocated\n");
+		return 0;
+	}
+
+	seq_printf(seq, "Host memory requests: %u\n\n",
+		   pdsc->num_host_mem_reqs);
+	seq_puts(seq, "Tag    Size         Order  PA\n");
+	seq_puts(seq, "---    ----         -----  --\n");
+
+	for (i = 0; i < pdsc->num_host_mem_reqs; i++) {
+		hm = &pdsc->host_mem_reqs[i];
+
+		if (!hm->pg)
+			continue;
+
+		seq_printf(seq, "%-6u %-12u %-6u %pad\n",
+			   hm->tag, hm->size, hm->order, &hm->pa);
+	}
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(host_mem);
+
+void pdsc_debugfs_add_host_mem(struct pdsc *pdsc)
+{
+	if (!(pdsc->dev_ident.capabilities &
+	     cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM)))
+		return;
+
+	debugfs_create_file("host_mem", 0400, pdsc->dentry,
+			    pdsc, &host_mem_fops);
+}
+
+void pdsc_debugfs_del_host_mem(struct pdsc *pdsc)
+{
+	debugfs_lookup_and_remove("host_mem", pdsc->dentry);
+}
diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
index bded6b33289ce..c50778175e2f3 100644
--- a/drivers/net/ethernet/amd/pds_core/dev.c
+++ b/drivers/net/ethernet/amd/pds_core/dev.c
@@ -126,7 +126,8 @@ static const char *pdsc_devcmd_str(int opcode)
 	}
 }
 
-static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)
+static int __pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds,
+			      const bool do_msg)
 {
 	struct device *dev = pdsc->dev;
 	unsigned long start_time;
@@ -179,7 +180,7 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)
 
 	status = pdsc_devcmd_status(pdsc);
 	err = pdsc_err_to_errno(status);
-	if (err && err != -EAGAIN)
+	if (do_msg && err && err != -EAGAIN)
 		dev_err(dev, "DEVCMD %d %s failed, status=%d err %d %pe\n",
 			opcode, pdsc_devcmd_str(opcode), status, err,
 			ERR_PTR(err));
@@ -187,8 +188,9 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)
 	return err;
 }
 
-int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
-		       union pds_core_dev_comp *comp, int max_seconds)
+static int __pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+				union pds_core_dev_comp *comp, int max_seconds,
+				const bool do_msg)
 {
 	int err;
 
@@ -197,16 +199,63 @@ int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
 
 	memcpy_toio(&pdsc->cmd_regs->cmd, cmd, sizeof(*cmd));
 	pdsc_devcmd_dbell(pdsc);
-	err = pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds);
+	err = __pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds, do_msg);
 
 	if ((err == -ENXIO || err == -ETIMEDOUT) && pdsc->wq)
 		queue_work(pdsc->wq, &pdsc->health_work);
 	else
 		memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp));
 
+	if (err != -ETIMEDOUT && err != -EAGAIN)
+		pdsc_deferred_dma_free(pdsc);
+
 	return err;
 }
 
+void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry,
+			   dma_addr_t dma_addr, void *va, size_t size,
+			   enum dma_data_direction dir)
+{
+	entry->dma_addr = dma_addr;
+	entry->va = va;
+	entry->size = size;
+	entry->dir = dir;
+
+	spin_lock(&pdsc->deferred_dma_lock);
+	list_add_tail(&entry->list, &pdsc->deferred_dma_list);
+	spin_unlock(&pdsc->deferred_dma_lock);
+}
+
+void pdsc_deferred_dma_free(struct pdsc *pdsc)
+{
+	struct pdsc_deferred_dma *entry, *tmp;
+	LIST_HEAD(local_list);
+
+	spin_lock(&pdsc->deferred_dma_lock);
+	list_splice_init(&pdsc->deferred_dma_list, &local_list);
+	spin_unlock(&pdsc->deferred_dma_lock);
+
+	list_for_each_entry_safe(entry, tmp, &local_list, list) {
+		dma_unmap_single(pdsc->dev, entry->dma_addr,
+				 entry->size, entry->dir);
+		kfree(entry->va);
+		list_del(&entry->list);
+		kfree(entry);
+	}
+}
+
+int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+		       union pds_core_dev_comp *comp, int max_seconds)
+{
+	return __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, true);
+}
+
+int pdsc_devcmd_locked_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+			     union pds_core_dev_comp *comp, int max_seconds)
+{
+	return __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, false);
+}
+
 int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
 		union pds_core_dev_comp *comp, int max_seconds)
 {
@@ -219,6 +268,47 @@ int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
 	return err;
 }
 
+static int __pdsc_devcmd_with_data(struct pdsc *pdsc,
+				   union pds_core_dev_cmd *cmd,
+				   const void *data, size_t data_len,
+				   union pds_core_dev_comp *comp,
+				   int max_seconds, bool do_msg)
+{
+	int err;
+
+	mutex_lock(&pdsc->devcmd_lock);
+	if (!pdsc->cmd_regs) {
+		err = -ENXIO;
+		goto unlock;
+	}
+	if (data_len > sizeof(pdsc->cmd_regs->data)) {
+		err = -ENOSPC;
+		goto unlock;
+	}
+	memcpy_toio(&pdsc->cmd_regs->data, data, data_len);
+	err = __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, do_msg);
+unlock:
+	mutex_unlock(&pdsc->devcmd_lock);
+
+	return err;
+}
+
+int pdsc_devcmd_with_data(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+			  const void *data, size_t data_len,
+			  union pds_core_dev_comp *comp, int max_seconds)
+{
+	return __pdsc_devcmd_with_data(pdsc, cmd, data, data_len,
+				       comp, max_seconds, true);
+}
+
+int pdsc_devcmd_with_data_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+				const void *data, size_t data_len,
+				union pds_core_dev_comp *comp, int max_seconds)
+{
+	return __pdsc_devcmd_with_data(pdsc, cmd, data, data_len,
+				       comp, max_seconds, false);
+}
+
 int pdsc_devcmd_init(struct pdsc *pdsc)
 {
 	union pds_core_dev_comp comp = {};
@@ -242,15 +332,17 @@ int pdsc_devcmd_reset(struct pdsc *pdsc)
 	return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
 }
 
-static int pdsc_devcmd_identify_locked(struct pdsc *pdsc)
+static int pdsc_devcmd_identify_locked(struct pdsc *pdsc, u8 drv_ident_ver,
+				       bool do_msg)
 {
 	union pds_core_dev_comp comp = {};
 	union pds_core_dev_cmd cmd = {
 		.identify.opcode = PDS_CORE_CMD_IDENTIFY,
-		.identify.ver = PDS_CORE_IDENTITY_VERSION_1,
+		.identify.ver = drv_ident_ver,
 	};
 
-	return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+	return __pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout,
+				    do_msg);
 }
 
 static void pdsc_init_devinfo(struct pdsc *pdsc)
@@ -273,8 +365,9 @@ static void pdsc_init_devinfo(struct pdsc *pdsc)
 	dev_dbg(pdsc->dev, "fw_version %s\n", pdsc->dev_info.fw_version);
 }
 
-static int pdsc_identify(struct pdsc *pdsc)
+static int pdsc_identify_ver(struct pdsc *pdsc, u8 drv_ident_ver)
 {
+	bool do_msg = drv_ident_ver == PDS_CORE_IDENTITY_VERSION_1;
 	struct pds_core_drv_identity drv = {};
 	size_t sz;
 	int err;
@@ -297,19 +390,22 @@ static int pdsc_identify(struct pdsc *pdsc)
 	sz = min_t(size_t, sizeof(drv), sizeof(pdsc->cmd_regs->data));
 	memcpy_toio(&pdsc->cmd_regs->data, &drv, sz);
 
-	err = pdsc_devcmd_identify_locked(pdsc);
+	err = pdsc_devcmd_identify_locked(pdsc, drv_ident_ver, do_msg);
 	if (!err) {
 		sz = min_t(size_t, sizeof(pdsc->dev_ident),
 			   sizeof(pdsc->cmd_regs->data));
 		memcpy_fromio(&pdsc->dev_ident, &pdsc->cmd_regs->data, sz);
+
+		/* V1 firmware doesn't set capabilities, so the field may
+		 * contain garbage from the outgoing driver identity.
+		 */
+		if (pdsc->dev_ident.version < PDS_CORE_IDENTITY_VERSION_2)
+			pdsc->dev_ident.capabilities = 0;
 	}
 	mutex_unlock(&pdsc->devcmd_lock);
 
-	if (err) {
-		dev_err(pdsc->dev, "Cannot identify device: %pe\n",
-			ERR_PTR(err));
+	if (err)
 		return err;
-	}
 
 	if (isprint(pdsc->dev_info.fw_version[0]) &&
 	    isascii(pdsc->dev_info.fw_version[0]))
@@ -326,6 +422,26 @@ static int pdsc_identify(struct pdsc *pdsc)
 	return 0;
 }
 
+static int pdsc_identify(struct pdsc *pdsc)
+{
+	int err;
+
+	/* Older firmware rejects anything but PDS_CORE_IDENTITY_VERSION_1
+	 * with PDS_RC_EVERSION (-EINVAL), so retry with V1 on version
+	 * rejection. Don't retry on other errors like -ENXIO/-ETIMEDOUT
+	 * which indicate firmware is not running or hung.
+	 */
+	err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_2);
+	if (err == -EINVAL)
+		err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_1);
+
+	if (err)
+		dev_err(pdsc->dev, "Cannot identify device: %pe\n",
+			ERR_PTR(err));
+
+	return err;
+}
+
 void pdsc_dev_uninit(struct pdsc *pdsc)
 {
 	if (pdsc->intr_info) {
diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c
index 8adae7b18898d..63fe45e91f710 100644
--- a/drivers/net/ethernet/amd/pds_core/devlink.c
+++ b/drivers/net/ethernet/amd/pds_core/devlink.c
@@ -90,13 +90,112 @@ int pdsc_dl_flash_update(struct devlink *dl,
 {
 	struct pdsc *pdsc = devlink_priv(dl);
 
-	if (params->component) {
-		NL_SET_ERR_MSG_MOD(extack,
-				   "Component update not supported by this device");
-		return -EOPNOTSUPP;
+	return pdsc_firmware_update(pdsc, params, extack);
+}
+
+static int pdsc_dl_report_component(struct devlink_info_req *req,
+				    struct pds_core_fw_component_info *info)
+{
+	enum devlink_info_version_type ver_type;
+	u16 flags = le16_to_cpu(info->flags);
+	char *ver = info->version;
+	const char *name;
+	char buf[32];
+
+	/* Main firmware is reported as generic "fw" */
+	if (info->component_type == PDS_CORE_FW_TYPE_MAIN) {
+		if (info->slot_id == PDS_CORE_FW_SLOT_GOLD)
+			snprintf(buf, sizeof(buf), "fw.gold");
+		else
+			snprintf(buf, sizeof(buf), "fw");
+	} else {
+		name = pdsc_fw_type_to_name(info->component_type);
+		if (!name)
+			return 0;
+
+		if (info->slot_id == PDS_CORE_FW_SLOT_GOLD)
+			snprintf(buf, sizeof(buf), "fw.%s.gold", name);
+		else
+			snprintf(buf, sizeof(buf), "fw.%s", name);
+	}
+
+	ver_type = DEVLINK_INFO_VERSION_TYPE_NONE;
+	if (flags & PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME)
+		ver_type = DEVLINK_INFO_VERSION_TYPE_COMPONENT;
+
+	if (flags & PDS_CORE_FW_COMPONENT_INFO_F_FIXED) {
+		int err;
+
+		err = devlink_info_version_fixed_put(req, buf, ver);
+		if (err)
+			return err;
+	}
+
+	if (flags & PDS_CORE_FW_COMPONENT_INFO_F_RUNNING) {
+		int err;
+
+		err = devlink_info_version_running_put_ext(req, buf,
+							   ver, ver_type);
+		if (err)
+			return err;
 	}
 
-	return pdsc_firmware_update(pdsc, params->fw, extack);
+	if (flags & PDS_CORE_FW_COMPONENT_INFO_F_STARTUP) {
+		int err;
+
+		err = devlink_info_version_stored_put_ext(req, buf,
+							  ver, ver_type);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int pdsc_dl_report_fw_ver(struct devlink_info_req *req, char *fw_ver)
+{
+	return devlink_info_version_running_put(req,
+						DEVLINK_INFO_VERSION_GENERIC_FW,
+						fw_ver);
+}
+
+static int pdsc_dl_component_info_get(struct devlink *dl,
+				      struct devlink_info_req *req,
+				      struct netlink_ext_ack *extack)
+{
+	struct pdsc *pdsc = devlink_priv(dl);
+	u8 num_components;
+	int err;
+	int i;
+
+	/* Pairs with WRITE_ONCE in pdsc_fw_components_invalidate().
+	 * Use READ_ONCE to get a consistent snapshot of num_components.
+	 * pdsc_fw_components_invalidate() can zero it concurrently during
+	 * firmware recovery; using the local copy avoids iterating zero
+	 * times when we already decided the cache was valid.
+	 */
+	num_components = READ_ONCE(pdsc->fw_components.num_components);
+	if (!num_components) {
+		err = pdsc_get_component_info(pdsc);
+		if (err)
+			return pdsc_dl_report_fw_ver(req,
+						    pdsc->dev_info.fw_version);
+		num_components = READ_ONCE(pdsc->fw_components.num_components);
+		if (!num_components)
+			return pdsc_dl_report_fw_ver(req,
+						    pdsc->dev_info.fw_version);
+	}
+
+	num_components = min_t(u16, num_components,
+			       le16_to_cpu(pdsc->dev_ident.max_fw_slots));
+	for (i = 0; i < num_components; i++) {
+		err = pdsc_dl_report_component(req,
+					       &pdsc->fw_components.info[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
 }
 
 static char *fw_slotnames[] = {
@@ -105,8 +204,9 @@ static char *fw_slotnames[] = {
 	"fw.mainfwb",
 };
 
-int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,
-		     struct netlink_ext_ack *extack)
+static int pdsc_dl_fw_list_info_get(struct devlink *dl,
+				    struct devlink_info_req *req,
+				    struct netlink_ext_ack *extack)
 {
 	union pds_core_dev_cmd cmd = {
 		.fw_control.opcode = PDS_CORE_CMD_FW_CONTROL,
@@ -140,12 +240,41 @@ int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,
 			return err;
 	}
 
-	err = devlink_info_version_running_put(req,
-					       DEVLINK_INFO_VERSION_GENERIC_FW,
-					       pdsc->dev_info.fw_version);
+	return 0;
+}
+
+static int pdsc_dl_info_get_v1(struct devlink *dl,
+			       struct devlink_info_req *req,
+			       struct netlink_ext_ack *extack)
+{
+	struct pdsc *pdsc = devlink_priv(dl);
+	int err;
+
+	err = pdsc_dl_fw_list_info_get(dl, req, extack);
 	if (err)
 		return err;
 
+	/* Version 1: report fw from dev_info (running only) */
+	return pdsc_dl_report_fw_ver(req, pdsc->dev_info.fw_version);
+}
+
+int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,
+		     struct netlink_ext_ack *extack)
+{
+	struct pdsc *pdsc = devlink_priv(dl);
+	char buf[32];
+	int err;
+
+	if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2) {
+		err = pdsc_dl_component_info_get(dl, req, extack);
+		if (err)
+			return err;
+	} else {
+		err = pdsc_dl_info_get_v1(dl, req, extack);
+		if (err)
+			return err;
+	}
+
 	snprintf(buf, sizeof(buf), "0x%x", pdsc->dev_info.asic_type);
 	err = devlink_info_version_fixed_put(req,
 					     DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c
index fa626719e68d1..5ccf017f6af47 100644
--- a/drivers/net/ethernet/amd/pds_core/fw.c
+++ b/drivers/net/ethernet/amd/pds_core/fw.c
@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright(c) 2023 Advanced Micro Devices, Inc */
 
+#include <linux/pldmfw.h>
+#include <linux/vmalloc.h>
+
 #include "core.h"
 
 /* The worst case wait for the install activity is about 25 minutes when
@@ -14,6 +17,58 @@
 /* Number of periodic log updates during fw file download */
 #define PDSC_FW_INTERVAL_FRACTION	32
 
+#define PDSC_FW_COMPONENT_PREFIX		"fw."
+#define PDSC_FW_COMPONENT_FULL_NAME_BUFLEN \
+	(sizeof(PDSC_FW_COMPONENT_PREFIX) + PDS_CORE_FW_COMPONENT_NAME_BUFLEN)
+
+/* Driver-defined component type to name mapping.
+ * PDS_CORE_FW_TYPE_MAIN is NULL - handled specially as "fw" without prefix.
+ */
+static const char * const pdsc_fw_type_names[] = {
+	[PDS_CORE_FW_TYPE_MAIN]      = NULL,
+	[PDS_CORE_FW_TYPE_BOOT]      = "bootloader",
+	[PDS_CORE_FW_TYPE_CPLD]      = "cpld",
+	[PDS_CORE_FW_TYPE_SECURE]    = "secure",
+	[PDS_CORE_FW_TYPE_FPGA]      = "fpga",
+	[PDS_CORE_FW_TYPE_SUC_MAIN]  = "suc",
+	[PDS_CORE_FW_TYPE_SUC_BOOT]  = "suc.bootloader",
+	[PDS_CORE_FW_TYPE_UBOOT]     = "uboot",
+};
+
+const char *pdsc_fw_type_to_name(u8 type)
+{
+	if (type < ARRAY_SIZE(pdsc_fw_type_names) && pdsc_fw_type_names[type])
+		return pdsc_fw_type_names[type];
+	return NULL;
+}
+
+void pdsc_fw_components_invalidate(struct pdsc *pdsc)
+{
+	/* Pairs with READ_ONCE in pdsc_dl_component_info_get() */
+	WRITE_ONCE(pdsc->fw_components.num_components, 0);
+}
+
+static u8 pdsc_name_to_fw_type(const char *name)
+{
+	size_t prefix_len;
+	int i;
+
+	/* "fw" without suffix maps to main firmware */
+	if (!strcmp(name, "fw"))
+		return PDS_CORE_FW_TYPE_MAIN;
+
+	prefix_len = str_has_prefix(name, PDSC_FW_COMPONENT_PREFIX);
+	if (prefix_len)
+		name += prefix_len;
+
+	for (i = 1; i < ARRAY_SIZE(pdsc_fw_type_names); i++) {
+		if (pdsc_fw_type_names[i] &&
+		    !strcmp(name, pdsc_fw_type_names[i]))
+			return i;
+	}
+	return 0;
+}
+
 static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr,
 					  u32 offset, u32 length)
 {
@@ -23,7 +78,7 @@ static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr,
 		.fw_download.addr = cpu_to_le64(addr),
 		.fw_download.length = cpu_to_le32(length),
 	};
-	union pds_core_dev_comp comp;
+	union pds_core_dev_comp comp = {};
 
 	return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
 }
@@ -95,9 +150,12 @@ static int pdsc_fw_status_long_wait(struct pdsc *pdsc,
 	return err;
 }
 
-int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
-			 struct netlink_ext_ack *extack)
+static int
+pdsc_legacy_firmware_update(struct pdsc *pdsc,
+			    struct devlink_flash_update_params *params,
+			    struct netlink_ext_ack *extack)
 {
+	const struct firmware *fw = params->fw;
 	u32 buf_sz, copy_sz, offset;
 	struct devlink *dl;
 	int next_interval;
@@ -105,6 +163,12 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
 	int err = 0;
 	int fw_slot;
 
+	if (params->component) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "Component update not supported by this device");
+		return -EOPNOTSUPP;
+	}
+
 	dev_info(pdsc->dev, "Installing firmware\n");
 
 	if (!pdsc->cmd_regs)
@@ -195,3 +259,729 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
 						   NULL, 0, 0);
 	return err;
 }
+
+struct pdsc_component_priv {
+	u16 component_id;
+	bool skip;
+	struct list_head list_entry;
+};
+
+struct pds_core_fwu_priv {
+	struct pldmfw context;
+	struct devlink_flash_update_params *params;
+	struct netlink_ext_ack *extack;
+	struct pdsc *pdsc;
+	struct list_head components;
+	bool component_found;
+};
+
+static void pdsc_free_fwu_priv(struct pds_core_fwu_priv *priv)
+{
+	struct pdsc_component_priv *component_priv, *tmp;
+
+	list_for_each_entry_safe(component_priv, tmp, &priv->components,
+				 list_entry) {
+		list_del(&component_priv->list_entry);
+		kfree(component_priv);
+	}
+}
+
+static int pdsc_devcmd_match_record_desc(struct pdsc *pdsc, u16 desc_type,
+					 u16 desc_size, const u8 *desc_data,
+					 u8 *match)
+{
+	union pds_core_dev_cmd cmd = {
+		.match_record_desc.opcode = PDS_CORE_CMD_MATCH_RECORD_DESC,
+		.match_record_desc.ver = 1,
+		.match_record_desc.type = cpu_to_le16(desc_type),
+		.match_record_desc.size = cpu_to_le16(desc_size),
+	};
+	union pds_core_dev_comp comp = {};
+	int err;
+
+	err = pdsc_devcmd_with_data(pdsc, &cmd, desc_data, desc_size,
+				    &comp, pdsc->devcmd_timeout);
+	*match = comp.match_record_desc.match;
+
+	return err;
+}
+
+static bool pdsc_match_record_descs(struct pldmfw *context,
+				    struct pldmfw_record *record)
+{
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	struct pdsc *pdsc = priv->pdsc;
+	struct pldmfw_desc_tlv *desc;
+
+	if (!pldmfw_op_pci_match_record(context, record))
+		return false;
+
+	list_for_each_entry(desc, &record->descs, entry) {
+		u8 match;
+		int err;
+
+		switch (desc->type) {
+		/* skip types checked in pldmfw_op_pci_match_record */
+		case PLDM_DESC_ID_PCI_VENDOR_ID:
+		case PLDM_DESC_ID_PCI_DEVICE_ID:
+		case PLDM_DESC_ID_PCI_SUBVENDOR_ID:
+		case PLDM_DESC_ID_PCI_SUBDEV_ID:
+			continue;
+		}
+
+		if (!desc->size)
+			return false;
+
+		err = pdsc_devcmd_match_record_desc(pdsc, desc->type,
+						    desc->size, desc->data,
+						    &match);
+		if (err) {
+			dev_err(pdsc->dev,
+				"match_record_desc failed type: 0x%04x size: %u, err %d\n",
+				desc->type, desc->size, err);
+			return false;
+		}
+		/* all record descriptors must match */
+		if (!match)
+			return false;
+	}
+
+	return true;
+}
+
+static int pdsc_devcmd_send_package_data(struct pdsc *pdsc, u64 addr,
+					 u16 length, u16 offset, u16 total_len)
+{
+	union pds_core_dev_cmd cmd = {
+		.send_pkg_data.opcode = PDS_CORE_CMD_SEND_PKG_DATA,
+		.send_pkg_data.ver = 1,
+		.send_pkg_data.data_pa = cpu_to_le64(addr),
+		.send_pkg_data.data_len = cpu_to_le16(length),
+		.send_pkg_data.offset = cpu_to_le16(offset),
+		.send_pkg_data.total_len = cpu_to_le16(total_len),
+	};
+	union pds_core_dev_comp comp = {};
+
+	return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+}
+
+static int pdsc_send_package_data(struct pldmfw *context, const u8 *data,
+				  u16 length)
+{
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	struct pdsc_deferred_dma *deferred;
+	struct device *dev = context->dev;
+	struct pdsc *pdsc = priv->pdsc;
+	dma_addr_t dma_addr;
+	u8 *package_data;
+	u32 offset;
+	int err;
+
+	if (!length)
+		return 0;
+
+	deferred = kmalloc_obj(*deferred, GFP_KERNEL);
+	if (!deferred)
+		return -ENOMEM;
+
+	package_data = kmemdup(data, length, GFP_KERNEL);
+	if (!package_data) {
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	dma_addr = dma_map_single(dev, package_data, length, DMA_TO_DEVICE);
+	if (dma_mapping_error(dev, dma_addr)) {
+		dev_err(dev, "Failed to dma_map package_data length 0x%x\n",
+			length);
+		kfree(package_data);
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	for (offset = 0; offset < length; offset += PDS_PAGE_SIZE) {
+		u32 copy_sz;
+
+		copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, length - offset);
+		err = pdsc_devcmd_send_package_data(pdsc, dma_addr + offset,
+						    copy_sz, offset, length);
+		if (err) {
+			NL_SET_ERR_MSG_MOD(priv->extack,
+					   "Failed to send package data");
+			break;
+		}
+	}
+
+	if (err == -ETIMEDOUT || err == -EAGAIN) {
+		pdsc_deferred_dma_add(pdsc, deferred, dma_addr,
+				      package_data, length, DMA_TO_DEVICE);
+		return err;
+	}
+
+	kfree(deferred);
+	dma_unmap_single(dev, dma_addr, length, DMA_TO_DEVICE);
+	kfree(package_data);
+	return err;
+}
+
+static bool pdsc_component_type_exists(struct pdsc *pdsc, u8 type)
+{
+	int i;
+
+	for (i = 0; i < pdsc->fw_components.num_components; i++) {
+		if (pdsc->fw_components.info[i].component_type == type)
+			return true;
+	}
+	return false;
+}
+
+static bool pdsc_component_id_matches_type(struct pdsc *pdsc,
+					   u8 component_id, u8 type)
+{
+	int i;
+
+	for (i = 0; i < pdsc->fw_components.num_components; i++) {
+		struct pds_core_fw_component_info *info =
+			&pdsc->fw_components.info[i];
+
+		if (info->identifier == component_id &&
+		    info->component_type == type)
+			return true;
+	}
+	return false;
+}
+
+static u8 pdsc_get_component_type_by_id(struct pdsc *pdsc, u16 component_id)
+{
+	int i;
+
+	for (i = 0; i < pdsc->fw_components.num_components; i++) {
+		struct pds_core_fw_component_info *info =
+			&pdsc->fw_components.info[i];
+
+		if (info->identifier == component_id)
+			return info->component_type;
+	}
+	return 0;
+}
+
+static bool pdsc_skip_component(struct pds_core_fwu_priv *priv,
+				u16 component_id)
+{
+	struct pdsc_component_priv *component_priv;
+
+	list_for_each_entry(component_priv, &priv->components, list_entry) {
+		if (component_priv->component_id == component_id)
+			return component_priv->skip;
+	}
+
+	return false;
+}
+
+static int pdsc_send_component_table(struct pldmfw *context,
+				     struct pldmfw_component *component,
+				     u8 transfer_flag)
+{
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	struct pds_core_component_tbl *component_tbl;
+	struct pdsc_component_priv *component_priv;
+	struct device *dev = context->dev;
+	union pds_core_dev_comp comp = {};
+	union pds_core_dev_cmd cmd = {};
+	struct pdsc *pdsc = priv->pdsc;
+	bool skip_component = false;
+	u8 requested_type = 0;
+	u16 buf_sz, tbl_sz;
+	int err = 0;
+
+	dev_dbg(dev,
+		"component name %s classification %u id %u activation_method %u ver_len %d ver_str %.*s index %u size %u transfer_flag 0x%02x\n",
+		priv->params->component, component->classification,
+		component->identifier, component->activation_method,
+		component->version_len, component->version_len,
+		component->version_string, component->index,
+		component->component_size, transfer_flag);
+
+	component_priv = kzalloc_obj(*component_priv, GFP_KERNEL);
+	if (!component_priv)
+		return -ENOMEM;
+
+	if (priv->params->component) {
+		requested_type = pdsc_name_to_fw_type(priv->params->component);
+		if (component->identifier > U8_MAX ||
+		    !pdsc_component_id_matches_type(pdsc,
+						    component->identifier,
+						    requested_type)) {
+			skip_component = true;
+			goto add_component_priv;
+		}
+		priv->component_found = true;
+	}
+
+	buf_sz = sizeof(pdsc->cmd_regs->data);
+	tbl_sz = struct_size(component_tbl, version_str,
+			     component->version_len);
+	if (tbl_sz > buf_sz) {
+		dev_err(dev, "component_tbl size %d too big, max size: %d\n",
+			tbl_sz, buf_sz);
+		err = -ENOSPC;
+		goto free_component_priv;
+	}
+	component_tbl = kzalloc(tbl_sz, GFP_KERNEL);
+	if (!component_tbl) {
+		err = -ENOMEM;
+		goto free_component_priv;
+	}
+
+	component_tbl->comparison_stamp =
+		cpu_to_le32(component->comparison_stamp);
+	component_tbl->classification = cpu_to_le16(component->classification);
+	component_tbl->identifier = cpu_to_le16(component->identifier);
+	component_tbl->transfer_flag = transfer_flag;
+	component_tbl->version_str_type = component->version_type;
+	component_tbl->version_str_len = component->version_len;
+	memcpy(component_tbl->version_str, component->version_string,
+	       component->version_len);
+
+	cmd.send_component_tbl.opcode = PDS_CORE_CMD_SEND_COMPONENT_TBL;
+	cmd.send_component_tbl.ver = 1;
+	cmd.send_component_tbl.slot_id = PDS_CORE_FW_SLOT_INVALID;
+
+	err = pdsc_devcmd_with_data(pdsc, &cmd, component_tbl, tbl_sz,
+				    &comp, pdsc->devcmd_timeout);
+	kfree(component_tbl);
+	if (err) {
+		dev_err(dev, "Failed sending component table: %pe\n",
+			ERR_PTR(err));
+		goto free_component_priv;
+	}
+
+	skip_component = comp.send_component_tbl.response == 1;
+
+add_component_priv:
+	component_priv->skip = skip_component;
+	component_priv->component_id = component->identifier;
+	list_add(&component_priv->list_entry, &priv->components);
+
+	return 0;
+
+free_component_priv:
+	kfree(component_priv);
+	return err;
+}
+
+int pdsc_get_component_info(struct pdsc *pdsc)
+{
+	union pds_core_dev_cmd cmd = {
+		.get_component_info.opcode = PDS_CORE_CMD_GET_COMPONENT_INFO,
+		.get_component_info.ver = 1,
+	};
+	struct pds_core_component_list_info *list_info;
+	struct pdsc_deferred_dma *deferred;
+	union pds_core_dev_comp comp = {};
+	dma_addr_t dma_addr;
+	u8 num_components;
+	int err, i;
+
+	deferred = kmalloc_obj(*deferred);
+	if (!deferred)
+		return -ENOMEM;
+
+	list_info = kzalloc(PDS_PAGE_SIZE, GFP_KERNEL);
+	if (!list_info) {
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	dma_addr = dma_map_single(pdsc->dev, list_info, PDS_PAGE_SIZE,
+				  DMA_FROM_DEVICE);
+	if (dma_mapping_error(pdsc->dev, dma_addr)) {
+		dev_err(pdsc->dev,
+			"Failed to dma_map component_list_info length %d\n",
+			PDS_PAGE_SIZE);
+		kfree(list_info);
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	cmd.get_component_info.data_len = cpu_to_le16(PDS_PAGE_SIZE);
+	cmd.get_component_info.data_pa = cpu_to_le64(dma_addr);
+
+	err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout * 2);
+	if (err == -ETIMEDOUT || err == -EAGAIN) {
+		pdsc_deferred_dma_add(pdsc, deferred, dma_addr, list_info,
+				      PDS_PAGE_SIZE, DMA_FROM_DEVICE);
+		return err;
+	}
+
+	kfree(deferred);
+	dma_unmap_single(pdsc->dev, dma_addr, PDS_PAGE_SIZE, DMA_FROM_DEVICE);
+	if (err)
+		goto out;
+
+	if (comp.get_component_info.ver == 0) {
+		/* Don't support backward compatibility as version 0 has
+		 * alignment issues, so give a hint to users to update
+		 * their firmware
+		 */
+		dev_warn_once(pdsc->dev,
+			      "Incompatible get_component_info version %u reported by firmware\n",
+			      comp.get_component_info.ver);
+		err = 0;
+		goto out;
+	}
+
+	num_components = list_info->num_components;
+	if (num_components > PDS_CORE_FW_COMPONENT_LIST_LEN) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	pdsc->fw_components.num_components = num_components;
+	for (i = 0; i < num_components; i++) {
+		struct pds_core_fw_component_info *info =
+			&pdsc->fw_components.info[i];
+
+		memcpy(info, &list_info->info[i], sizeof(*info));
+		info->version[PDS_CORE_FW_COMPONENT_VER_BUFLEN - 1] = 0;
+		info->name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN - 1] = 0;
+	}
+
+out:
+	kfree(list_info);
+	return err;
+}
+
+static int pdsc_devcmd_send_component(struct pdsc *pdsc,
+				      struct pds_core_flash_component *info,
+				      u16 info_sz, dma_addr_t addr, u32 length,
+				      u32 offset, u16 slot_id,
+				      union pds_core_dev_comp *comp)
+{
+	union pds_core_dev_cmd cmd = {
+		.send_component.opcode = PDS_CORE_CMD_SEND_COMPONENT,
+		.send_component.ver = 1,
+		.send_component.operation = PDS_CORE_SEND_COMPONENT_START,
+		.send_component.data_pa = cpu_to_le64(addr),
+		.send_component.data_len = cpu_to_le32(length),
+		.send_component.offset = cpu_to_le32(offset),
+		.send_component.slot_id = slot_id,
+	};
+	unsigned long timeout = 300 * HZ;
+	unsigned long start_time;
+	unsigned long end_time;
+	int err;
+
+	start_time = jiffies;
+	end_time = start_time + timeout;
+	do {
+		/* prevent noisy/benign devcmd failures */
+		err = pdsc_devcmd_with_data_nomsg(pdsc, &cmd, info, info_sz,
+						  comp, 60);
+		if (err != -EAGAIN)
+			break;
+
+		/* if required, subsequent commands check status of
+		 * PDS_CORE_CMD_SEND_COMPONENT command, which returns
+		 * EAGAIN while the command is still running,
+		 * else we get the final command status.
+		 */
+		cmd.send_component.operation = PDS_CORE_SEND_COMPONENT_STATUS;
+		msleep(20);
+	} while (time_before(jiffies, end_time));
+
+	if (err == -EAGAIN || err == -ETIMEDOUT)
+		dev_err(pdsc->dev, "PDS_CORE_CMD_SEND_COMPONENT timed out\n");
+
+	return err;
+}
+
+static int pdsc_flash_component_chunk(struct pdsc *pdsc, struct device *dev,
+				      struct pds_core_flash_component *info,
+				      u16 info_sz, const u8 *data, u16 copy_sz,
+				      u32 offset, u8 slot_id,
+				      union pds_core_dev_comp *comp)
+{
+	struct pdsc_deferred_dma *deferred;
+	dma_addr_t dma_addr;
+	u8 *component_data;
+	int err;
+
+	deferred = kmalloc_obj(*deferred, GFP_KERNEL);
+	if (!deferred)
+		return -ENOMEM;
+
+	component_data = kmemdup(data, copy_sz, GFP_KERNEL);
+	if (!component_data) {
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	dma_addr = dma_map_single(dev, component_data, copy_sz, DMA_TO_DEVICE);
+	if (dma_mapping_error(dev, dma_addr)) {
+		dev_err(dev,
+			"Failed to dma_map component_data at offset 0x%x copy_sz 0x%x\n",
+			offset, copy_sz);
+		kfree(component_data);
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	err = pdsc_devcmd_send_component(pdsc, info, info_sz, dma_addr,
+					 copy_sz, offset, slot_id, comp);
+	if (err == -ETIMEDOUT || err == -EAGAIN) {
+		pdsc_deferred_dma_add(pdsc, deferred, dma_addr,
+				      component_data, copy_sz, DMA_TO_DEVICE);
+		return err;
+	}
+
+	kfree(deferred);
+	dma_unmap_single(dev, dma_addr, copy_sz, DMA_TO_DEVICE);
+	kfree(component_data);
+
+	return err;
+}
+
+static int pdsc_flash_component(struct pldmfw *context,
+				struct pldmfw_component *component)
+{
+	char component_name_buf[PDSC_FW_COMPONENT_FULL_NAME_BUFLEN];
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	struct pds_core_flash_component *component_info;
+	const char *component_name = NULL;
+	struct device *dev = context->dev;
+	struct pdsc *pdsc = priv->pdsc;
+	u16 buf_sz, info_sz;
+	struct devlink *dl;
+	u8 component_type;
+	u32 total_len;
+	u32 offset;
+	int err;
+
+	component_type = pdsc_get_component_type_by_id(pdsc,
+						       component->identifier);
+	if (component_type) {
+		const char *type_name = pdsc_fw_type_to_name(component_type);
+
+		if (component_type == PDS_CORE_FW_TYPE_MAIN) {
+			component_name = "fw";
+		} else if (type_name) {
+			snprintf(component_name_buf, sizeof(component_name_buf),
+				 "%s%s", PDSC_FW_COMPONENT_PREFIX, type_name);
+			component_name = component_name_buf;
+		}
+	}
+
+	dl = priv_to_devlink(pdsc);
+
+	if (pdsc_skip_component(priv, component->identifier)) {
+		devlink_flash_update_status_notify(dl, "Skipped",
+						   component_name, 0, 0);
+		return 0;
+	}
+
+	total_len = component->component_size;
+	dev_dbg(dev,
+		"component name %s class %u id %u act_meth %u ver_str %.*s index %u size %u\n",
+		component_name ?: "(unknown)", component->classification,
+		component->identifier, component->activation_method,
+		component->version_len, component->version_string,
+		component->index, component->component_size);
+
+	buf_sz = sizeof(pdsc->cmd_regs->data);
+	info_sz = struct_size(component_info, version_str,
+			      component->version_len);
+	if (info_sz > buf_sz) {
+		dev_err(dev, "component_info size %d too big, max size: %d\n",
+			info_sz, buf_sz);
+		return -ENOSPC;
+	}
+	component_info = vzalloc(info_sz);
+	if (!component_info)
+		return -ENOMEM;
+
+	component_info->comparison_stamp =
+		cpu_to_le32(component->comparison_stamp);
+	component_info->image_size = cpu_to_le32(total_len);
+	component_info->classification = cpu_to_le16(component->classification);
+	component_info->identifier = cpu_to_le16(component->identifier);
+	component_info->options = cpu_to_le16(component->options);
+	component_info->version_str_type = component->version_type;
+	component_info->version_str_len = component->version_len;
+	memcpy(component_info->version_str, component->version_string,
+	       component->version_len);
+
+	offset = 0;
+	while (offset < total_len) {
+		union pds_core_dev_comp comp = {};
+		u16 copy_sz;
+
+		copy_sz = min_t(unsigned int, PDS_PAGE_SIZE,
+				total_len - offset);
+
+		err = pdsc_flash_component_chunk(pdsc, dev, component_info,
+						 info_sz,
+						 component->component_data +
+						 offset, copy_sz, offset,
+						 PDS_CORE_FW_SLOT_INVALID,
+						 &comp);
+		if (err &&
+		    comp.send_component.compat_response &&
+		    (comp.send_component.compat_response_code ==
+		     PDS_CORE_COMPONENT_STAMP_IDENTICAL ||
+		     comp.send_component.compat_response_code ==
+		     PDS_CORE_COMPONENT_STAMP_LOWER)) {
+			err = 0;
+			devlink_flash_update_status_notify(dl, "Skipped",
+							   component_name,
+							   0, 0);
+			goto skip_component;
+		}
+
+		if (err) {
+			NL_SET_ERR_MSG_MOD(priv->extack,
+					   "Failed to flash component");
+			goto err_out;
+		}
+
+		offset += copy_sz;
+		devlink_flash_update_status_notify(dl,
+						   "Erasing/Flashing",
+						   component_name, offset,
+						   total_len);
+	}
+
+	vfree(component_info);
+	return 0;
+
+err_out:
+	devlink_flash_update_status_notify(dl,
+					   "Erasing/Flashing Component Failed",
+					   component_name, 0, 0);
+skip_component:
+	vfree(component_info);
+	return err;
+}
+
+static int pdsc_devcmd_finalize_update(struct pdsc *pdsc)
+{
+	union pds_core_dev_cmd cmd = {
+		.finalize_update.opcode = PDS_CORE_CMD_FINALIZE_UPDATE,
+		.finalize_update.ver = 1,
+	};
+	union pds_core_dev_comp comp = {};
+
+	return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+}
+
+static int pdsc_finalize_update(struct pldmfw *context)
+{
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	const char *component_name = priv->params->component;
+	unsigned long start_time, end_time;
+	struct device *dev = context->dev;
+	struct pdsc *pdsc = priv->pdsc;
+	struct devlink *dl;
+	int err;
+
+	dl = priv_to_devlink(pdsc);
+
+	start_time = jiffies;
+	end_time = start_time + (PDSC_FW_INSTALL_TIMEOUT * HZ);
+	do {
+		err = pdsc_devcmd_finalize_update(pdsc);
+		if (err != -EAGAIN)
+			break;
+
+		dev_dbg(dev, "retrying finalize_update: %pe\n", ERR_PTR(err));
+		msleep(20);
+	} while (time_before(jiffies, end_time) && err == -EAGAIN);
+
+	if (err) {
+		devlink_flash_update_status_notify(dl, "Finalize Update Failed",
+						   component_name, 0, 0);
+		NL_SET_ERR_MSG_MOD(priv->extack, "Finalize update failed");
+		return err;
+	}
+
+	devlink_flash_update_status_notify(dl, "Finalized Update",
+					   component_name, 0, 0);
+	return 0;
+}
+
+static const struct pldmfw_ops pdsc_pldmfw_ops = {
+	.match_record = pdsc_match_record_descs,
+	.send_package_data = pdsc_send_package_data,
+	.send_component_table = pdsc_send_component_table,
+	.flash_component = pdsc_flash_component,
+	.finalize_update = pdsc_finalize_update
+};
+
+static int pdsc_pldm_firmware_update(struct pdsc *pdsc,
+				     struct devlink_flash_update_params *params,
+				     struct netlink_ext_ack *extack,
+				     const struct firmware *fw)
+{
+	struct pds_core_fwu_priv priv = {};
+	int err;
+
+	if (!pdsc->fw_components.num_components) {
+		err = pdsc_get_component_info(pdsc);
+		if (err) {
+			NL_SET_ERR_MSG_MOD(extack,
+					   "Failed to get component info");
+			return err;
+		}
+	}
+
+	if (params->component) {
+		u8 type = pdsc_name_to_fw_type(params->component);
+
+		if (!type || !pdsc_component_type_exists(pdsc, type)) {
+			NL_SET_ERR_MSG_MOD(extack, "Unknown component name");
+			return -ENOENT;
+		}
+	}
+
+	INIT_LIST_HEAD(&priv.components);
+	priv.context.ops = &pdsc_pldmfw_ops;
+	priv.context.dev = pdsc->dev;
+	priv.params = params;
+	priv.extack = extack;
+	priv.pdsc = pdsc;
+
+	err = pldmfw_flash_image(&priv.context, fw);
+	if (!err && params->component && !priv.component_found) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "Requested component not present in firmware package");
+		err = -ENOENT;
+	}
+	pdsc_free_fwu_priv(&priv);
+
+	return err;
+}
+
+int pdsc_firmware_update(struct pdsc *pdsc,
+			 struct devlink_flash_update_params *params,
+			 struct netlink_ext_ack *extack)
+{
+	int err;
+
+	if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2 &&
+	    pdsc->dev_ident.capabilities &
+		cpu_to_le64(PDS_CORE_DEV_CAP_PLDM_FW_UPDATE))
+		err = pdsc_pldm_firmware_update(pdsc, params, extack,
+						params->fw);
+	else
+		err = pdsc_legacy_firmware_update(pdsc, params, extack);
+
+	/* Invalidate cached component info so next info_get refreshes */
+	pdsc_fw_components_invalidate(pdsc);
+
+	return err;
+}
diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c
index 8d94a4d70395e..5e1b07850163b 100644
--- a/drivers/net/ethernet/amd/pds_core/main.c
+++ b/drivers/net/ethernet/amd/pds_core/main.c
@@ -21,6 +21,8 @@ static const struct pci_device_id pdsc_id_table[] = {
 };
 MODULE_DEVICE_TABLE(pci, pdsc_id_table);
 
+static void pdsc_stop_health_thread(struct pdsc *pdsc);
+
 static void pdsc_wdtimer_cb(struct timer_list *t)
 {
 	struct pdsc *pdsc = timer_container_of(pdsc, t, wdtimer);
@@ -315,6 +317,7 @@ static int pdsc_init_pf(struct pdsc *pdsc)
 		destroy_workqueue(pdsc->wq);
 	mutex_destroy(&pdsc->config_lock);
 	mutex_destroy(&pdsc->devcmd_lock);
+	pdsc_deferred_dma_free(pdsc);
 	pci_free_irq_vectors(pdsc->pdev);
 err_out_unmap_bars:
 	pdsc_unmap_bars(pdsc);
@@ -353,6 +356,8 @@ static int pdsc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 	pdsc->pdev = pdev;
 	pdsc->dev = &pdev->dev;
 	set_bit(PDSC_S_INITING_DRIVER, &pdsc->state);
+	INIT_LIST_HEAD(&pdsc->deferred_dma_list);
+	spin_lock_init(&pdsc->deferred_dma_lock);
 	pci_set_drvdata(pdev, pdsc);
 	pdsc_debugfs_add_dev(pdsc);
 
@@ -458,6 +463,7 @@ static void pdsc_remove(struct pci_dev *pdev)
 	}
 
 	pci_disable_device(pdev);
+	pdsc_deferred_dma_free(pdsc);
 
 	ida_free(&pdsc_ida, pdsc->uid);
 	pdsc_debugfs_del_dev(pdsc);
@@ -505,6 +511,7 @@ static void pdsc_reset_prepare(struct pci_dev *pdev)
 	pci_release_regions(pdev);
 	if (pci_is_enabled(pdev))
 		pci_disable_device(pdev);
+	pdsc_deferred_dma_free(pdsc);
 }
 
 static void pdsc_reset_done(struct pci_dev *pdev)
diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h
index 17a87c1a55d7c..901e1e628f890 100644
--- a/include/linux/pds/pds_core_if.h
+++ b/include/linux/pds/pds_core_if.h
@@ -40,6 +40,14 @@ enum pds_core_cmd_opcode {
 	PDS_CORE_CMD_FW_DOWNLOAD	= 4,
 	PDS_CORE_CMD_FW_CONTROL		= 5,
 
+	PDS_CORE_CMD_GET_COMPONENT_INFO	= 6,
+	PDS_CORE_CMD_SEND_PKG_DATA	= 7,
+	PDS_CORE_CMD_SEND_COMPONENT_TBL	= 8,
+	PDS_CORE_CMD_SEND_COMPONENT	= 9,
+	PDS_CORE_CMD_FINALIZE_UPDATE	= 10,
+	PDS_CORE_CMD_MATCH_RECORD_DESC	= 11,
+	PDS_CORE_CMD_HOST_MEM		= 12,
+
 	/* SR/IOV commands */
 	PDS_CORE_CMD_VF_GETATTR		= 60,
 	PDS_CORE_CMD_VF_SETATTR		= 61,
@@ -100,6 +108,16 @@ struct pds_core_drv_identity {
 	char   driver_ver_str[32];
 };
 
+/**
+ * enum pds_core_dev_capability - Device capabilities
+ * @PDS_CORE_DEV_CAP_PLDM_FW_UPDATE: Device only supports FW update via PLDM
+ * @PDS_CORE_DEV_CAP_HOST_MEM: Device supports host memory for fw use
+ */
+enum pds_core_dev_capability {
+	PDS_CORE_DEV_CAP_PLDM_FW_UPDATE = BIT(0),
+	PDS_CORE_DEV_CAP_HOST_MEM = BIT(1),
+};
+
 #define PDS_DEV_TYPE_MAX	16
 /**
  * struct pds_core_dev_identity - Device identity information
@@ -119,6 +137,11 @@ struct pds_core_drv_identity {
  *		      value in usecs to device units using:
  *		      device units = usecs * mult / div
  * @vif_types:        How many of each VIF device type is supported
+ * @max_fw_slots:     Number of firmware components reported by device
+ *		      only supported on version >= PDS_CORE_IDENTITY_VERSION_2
+ * @rsvd2:	      Word boundary padding
+ * @capabilities:     Device capabilities
+ *		      only supported on version >= PDS_CORE_IDENTITY_VERSION_2
  */
 struct pds_core_dev_identity {
 	u8     version;
@@ -131,9 +154,13 @@ struct pds_core_dev_identity {
 	__le32 intr_coal_mult;
 	__le32 intr_coal_div;
 	__le16 vif_types[PDS_DEV_TYPE_MAX];
+	__le16 max_fw_slots;
+	u8     rsvd2[6];
+	__le64 capabilities;
 };
 
 #define PDS_CORE_IDENTITY_VERSION_1	1
+#define PDS_CORE_IDENTITY_VERSION_2	2
 
 /**
  * struct pds_core_dev_identify_cmd - Driver/device identify command
@@ -275,11 +302,20 @@ enum pds_core_fw_control_oper {
 	PDS_CORE_FW_GET_LIST               = 7,
 };
 
+/**
+ * enum pds_core_fw_slot - Firmware slot identifiers
+ * @PDS_CORE_FW_SLOT_INVALID: Let firmware select slot based on package metadata
+ * @PDS_CORE_FW_SLOT_A:       Primary firmware slot A
+ * @PDS_CORE_FW_SLOT_B:       Primary firmware slot B
+ * @PDS_CORE_FW_SLOT_GOLD:    Gold/recovery firmware slot
+ * @PDS_CORE_FW_SLOT_MAX:     Sentinel value indicating no slot resolved
+ */
 enum pds_core_fw_slot {
 	PDS_CORE_FW_SLOT_INVALID    = 0,
 	PDS_CORE_FW_SLOT_A	    = 1,
 	PDS_CORE_FW_SLOT_B          = 2,
 	PDS_CORE_FW_SLOT_GOLD       = 3,
+	PDS_CORE_FW_SLOT_MAX        = 0xff,
 };
 
 /**
@@ -446,6 +482,423 @@ struct pds_core_vf_ctrl_comp {
 	u8	status;
 };
 
+/**
+ * struct pds_core_send_pkg_data_cmd - Send package data command
+ * @opcode: Opcode PDS_CORE_CMD_SEND_PKG_DATA
+ * @ver: Driver's max support version of this command
+ * @total_len: Total length of the package data
+ * @offset: Offset in the package data, non-zero if multiple commands are
+ *	    needed for sending the package data
+ * @data_len: Length of data stored at data_pa
+ * @data_pa: Data physical address for DMA to device
+ *
+ * The package data may be too large to store in a single buffer, so multiple
+ * PDS_CORE_CMD_SEND_PKG_DATA devcmds may be needed.
+ */
+struct pds_core_send_pkg_data_cmd {
+	u8 opcode;
+	u8 ver;
+	__le16 total_len;
+	__le16 offset;
+	__le16 data_len;
+	__le64 data_pa;
+};
+
+/**
+ * struct pds_core_send_pkg_data_comp - Send package data completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @ver: Device's max supported version of this command
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_send_pkg_data_comp {
+	u8 status;
+	u8 ver;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_component_tbl - Component table details
+ * @comparison_stamp: Comparison stamp used for component version checks
+ * @classification: Vendor specific classification info
+ * @identifier: Component's ID
+ * @transfer_flag: Part of the component table this request represents
+ * @version_str_type: The types of strings used
+ * @version_str_len: Length of @version_str
+ * @version_str: Component version information
+ */
+struct pds_core_component_tbl {
+	__le32 comparison_stamp;
+	__le16 classification;
+	__le16 identifier;
+	u8     transfer_flag;
+	u8     version_str_type;
+	u8     version_str_len;
+	u8     version_str[];
+};
+
+/**
+ * struct pds_core_send_component_tbl_cmd - Send component table command
+ * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT_TBL
+ * @ver: Driver's max support version of this command
+ * @slot_id: enum pds_core_fw_slot
+ * @rsvd: Word boundary padding
+ *
+ * Expects to find component table info (struct pds_core_component_tbl)
+ * in cmd_regs->data.  Driver should keep the devcmd interface locked
+ * while preparing the component table info.
+ */
+struct pds_core_send_component_tbl_cmd {
+	u8 opcode;
+	u8 ver;
+	u8 slot_id;
+	u8 rsvd;
+};
+
+enum pds_core_component_resp_code {
+	PDS_CORE_COMPONENT_VALID = 0x0,
+	PDS_CORE_COMPONENT_STAMP_IDENTICAL = 0x1,
+	PDS_CORE_COMPONENT_STAMP_LOWER = 0x2,
+	PDS_CORE_COMPONENT_STAMP_OR_VERSION_INVALID = 0x3,
+	PDS_CORE_COMPONENT_CONFLICT = 0x4,
+	PDS_CORE_COMPONENT_PREREQS_NOT_MET = 0x5,
+	PDS_CORE_COMPONENT_NOT_SUPPORTED = 0x6,
+	PDS_CORE_COMPONENT_FW_TYPE_INVALID = 0xd0,
+};
+
+/**
+ * struct pds_core_send_component_tbl_comp - Send component table completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @ver: Device's max supported version of this command
+ * @completion_code: Component completion code
+ * @response: Component response
+ * @response_code: Component response code
+ * @slot_id: Actual slot_id of the component (enum pds_core_fw_slot)
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_send_component_tbl_comp {
+	u8 status;
+	u8 ver;
+	u8 completion_code;
+	u8 response;
+	u8 response_code;
+	u8 slot_id;
+	u8 rsvd[2];
+};
+
+/**
+ * enum pds_core_send_component_op - PDS_CORE_CMD_SEND_COMPONENT operation
+ * @PDS_CORE_SEND_COMPONENT_START: Initial operation to start transfer
+ * @PDS_CORE_SEND_COMPONENT_STATUS: Subsequent calls to check on status
+ */
+enum pds_core_send_component_op {
+	PDS_CORE_SEND_COMPONENT_START = 0,
+	PDS_CORE_SEND_COMPONENT_STATUS = 1,
+};
+
+#define PDS_CORE_FW_COMPONENT_ID_INVALID 0xFFFF
+/**
+ * struct pds_core_flash_component - Component details
+ * @comparison_stamp: Comparison stamp used for component version checks
+ * @image_size: Component image size
+ * @classification: Vendor specific classification info
+ * @identifier: Component's ID
+ * @options: Component options
+ * @rsvd: Word boundary padding
+ * @version_str_type: The types of strings used
+ * @version_str_len: Length of @version_str
+ * @version_str: Component version information
+ */
+struct pds_core_flash_component {
+	__le32 comparison_stamp;
+	__le32 image_size;
+	__le16 classification;
+	__le16 identifier;
+	__le16 options;
+	u8 rsvd[3];
+	u8 version_str_type;
+	u8 version_str_len;
+	u8 version_str[];
+};
+
+/**
+ * struct pds_core_send_component_cmd - Send component command
+ * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT
+ * @ver: Driver's max supported version of this command
+ * @slot_id: enum pds_core_fw_slot
+ * @operation: enum pds_core_send_component_op
+ * @offset: Offset into the component, non-zero if multiple commands
+ *	    are needed for a single component
+ * @data_len: Length of this part of the component stored at @data_pa
+ * @rsvd: Word boundary padding
+ * @data_pa: DMA address of the component
+ *
+ * A component may be too large to store in a single buffer, so multiple
+ * PDS_CORE_CMD_SEND_COMPONENT devcmds may be needed.
+ *
+ * Expects to find flash component info (struct pds_core_flash_component)
+ * in cmd_regs->data. Driver should keep the devcmd interface locked
+ * while preparing and sending the flash component info.
+ */
+struct pds_core_send_component_cmd {
+	u8 opcode;
+	u8 ver;
+	u8 slot_id;
+	u8 operation;
+	__le32 offset;
+	__le32 data_len;
+	u8 rsvd[4];
+	__le64 data_pa;
+};
+
+/**
+ * struct pds_core_send_component_comp - Send component completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @ver: Device's max supported version of this command
+ * @completion_code: Completion code
+ * @compat_response: Compatibility response (0 = Component can be updated)
+ * @compat_response_code: Compatibility response code
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_send_component_comp {
+	u8 status;
+	u8 ver;
+	u8 completion_code;
+	u8 compat_response;
+	u8 compat_response_code;
+	u8 rsvd[3];
+};
+
+/**
+ * enum pds_core_fw_component_type - Firmware component type
+ * @PDS_CORE_FW_TYPE_UNKNOWN: Unknown component type
+ * @PDS_CORE_FW_TYPE_MAIN: Main firmware
+ * @PDS_CORE_FW_TYPE_BOOT: Boot loader
+ * @PDS_CORE_FW_TYPE_CPLD: CPLD firmware
+ * @PDS_CORE_FW_TYPE_SECURE: Secure firmware
+ * @PDS_CORE_FW_TYPE_FPGA: FPGA configuration
+ * @PDS_CORE_FW_TYPE_SUC_MAIN: System Unit Controller firmware
+ * @PDS_CORE_FW_TYPE_SUC_BOOT: System Unit Controller bootloader
+ * @PDS_CORE_FW_TYPE_UBOOT: U-Boot bootloader
+ *
+ * Gold/recovery variants are identified by slot_id == PDS_CORE_FW_SLOT_GOLD
+ * and reported with a ".gold" suffix (e.g., fw.gold).
+ */
+enum pds_core_fw_component_type {
+	PDS_CORE_FW_TYPE_UNKNOWN   = 0,
+	PDS_CORE_FW_TYPE_MAIN      = 1,
+	PDS_CORE_FW_TYPE_BOOT      = 2,
+	PDS_CORE_FW_TYPE_CPLD      = 3,
+	PDS_CORE_FW_TYPE_SECURE    = 4,
+	PDS_CORE_FW_TYPE_FPGA      = 5,
+	PDS_CORE_FW_TYPE_SUC_MAIN  = 6,
+	PDS_CORE_FW_TYPE_SUC_BOOT  = 7,
+	PDS_CORE_FW_TYPE_UBOOT     = 8,
+};
+
+/**
+ * enum pds_core_component_info_flags - Component info flags
+ * @PDS_CORE_FW_COMPONENT_INFO_F_RUNNING: Component is currently running
+ * @PDS_CORE_FW_COMPONENT_INFO_F_STARTUP: Component version on next FW boot
+ * @PDS_CORE_FW_COMPONENT_INFO_F_FIXED: Component is fixed and cannot be updated
+ * @PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME: Component can be updated
+ *	by name
+ */
+enum pds_core_component_info_flags {
+	PDS_CORE_FW_COMPONENT_INFO_F_RUNNING = BIT(0),
+	PDS_CORE_FW_COMPONENT_INFO_F_STARTUP = BIT(1),
+	PDS_CORE_FW_COMPONENT_INFO_F_FIXED = BIT(2),
+	PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME = BIT(3),
+};
+
+/**
+ * struct pds_core_fw_component_info - GET_COMPONENT_INFO entry
+ * @name: Component's name
+ * @component_type: enum pds_core_fw_component_type
+ * @rsvd: Word boundary padding
+ * @flags: enum pds_core_component_info_flags
+ * @identifier: Component's identifier
+ * @slot_id: Component's slot identifier
+ * @version: Component's version
+ */
+struct pds_core_fw_component_info {
+#define PDS_CORE_FW_COMPONENT_NAME_BUFLEN 24
+	char name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN];
+	u8 component_type;
+	u8 rsvd[3];
+	__le16 flags;
+	u8 identifier;
+	u8 slot_id;
+#define PDS_CORE_FW_COMPONENT_VER_BUFLEN 32
+	char version[PDS_CORE_FW_COMPONENT_VER_BUFLEN];
+};
+
+#define PDS_CORE_FW_COMPONENT_LIST_LEN	((PDS_PAGE_SIZE - 8) / \
+		sizeof(struct pds_core_fw_component_info))
+
+/**
+ * struct pds_core_component_list_info - GET_COMPONENT_INFO completion data
+ * @num_components: Number of valid components
+ * @rsvd: Word boundary padding
+ * @info: List of valid components
+ */
+struct pds_core_component_list_info {
+	u8 num_components;
+	u8 rsvd[7];
+	struct pds_core_fw_component_info info[PDS_CORE_FW_COMPONENT_LIST_LEN];
+};
+
+/**
+ * struct pds_core_get_component_info_cmd - GET_COMPONENT_INFO command
+ * @opcode: PDS_CORE_CMD_GET_COMPONENT_INFO
+ * @ver: Driver's max supported version of this command
+ * @data_len: Length of data at data_pa
+ * @rsvd: Word boundary padding
+ * @data_pa: DMA address of data
+ *
+ * FW populates struct pds_core_component_list_info pointed to by @data_pa
+ */
+struct pds_core_get_component_info_cmd {
+	u8 opcode;
+	u8 ver;
+	__le16 data_len;
+	u8 rsvd[4];
+	__le64 data_pa;
+};
+
+/**
+ * struct pds_core_get_component_info_comp - GET_COMPONENT_INFO completion
+ * @status: enum pds_core_status_code
+ * @ver: Device's max supported version of this command
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_get_component_info_comp {
+	u8 status;
+	u8 ver;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_finalize_update_cmd - FINALIZE_UPDATE command
+ * @opcode: PDS_CORE_CMD_FINALIZE_UPDATE
+ * @ver: Driver's max support version of this command
+ * @rsvd: Word boundary padding
+ *
+ * Driver sends at the end of updating all components to finalize the update
+ */
+struct pds_core_finalize_update_cmd {
+	u8 opcode;
+	u8 ver;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_finalize_update_comp - FINALIZE_UPDATE completion
+ * @status: enum pds_core_status_code
+ * @ver: Device's max supported version of this command
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_finalize_update_comp {
+	u8 status;
+	u8 ver;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_match_record_desc_cmd - MATCH_RECORD_DESC command
+ * @opcode: PDS_CORE_CMD_MATCH_RECORD_DESC
+ * @ver: Driver's max supported version of this command
+ * @type: PLDM Descriptor Identifier Type
+ * @size: Length of the Descriptor Identifier Value
+ * @rsvd: Word boundary padding
+ *
+ * Expects to find the Descriptor Identifier Data in cmd_regs->data. Driver
+ * should keep the devcmd interface locked while preparing and sending this
+ * command.
+ */
+struct pds_core_match_record_desc_cmd {
+	u8 opcode;
+	u8 ver;
+	__le16 type;
+	__le16 size;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_match_record_desc_comp - MATCH_RECORD_DESC completion
+ * @status: enum pds_core_status_code
+ * @ver: Device's max supported version of this command
+ * @match: Whether or not the Record Descriptor matches the device
+ * @rsvd: Word boundary padding
+ *
+ * When status is PDS_RC_SUCCESS, then @match is valid, otherwise it's
+ * undefined.
+ */
+struct pds_core_match_record_desc_comp {
+	u8 status;
+	u8 ver;
+	u8 match;
+	u8 rsvd;
+};
+
+/**
+ * enum pds_core_host_mem_oper - HOST_MEM sub-operations
+ * @PDS_CORE_HOST_MEM_GET_COUNT: Query number of memory requests
+ * @PDS_CORE_HOST_MEM_QUERY:     Query details of a memory request
+ * @PDS_CORE_HOST_MEM_ADD:       Provide allocated memory to firmware
+ * @PDS_CORE_HOST_MEM_DEL:       Notify firmware of memory deallocation
+ */
+enum pds_core_host_mem_oper {
+	PDS_CORE_HOST_MEM_GET_COUNT	= 0,
+	PDS_CORE_HOST_MEM_QUERY		= 1,
+	PDS_CORE_HOST_MEM_ADD		= 2,
+	PDS_CORE_HOST_MEM_DEL		= 3,
+};
+
+/**
+ * struct pds_core_host_mem_cmd - HOST_MEM command
+ * @opcode:     Opcode PDS_CORE_CMD_HOST_MEM
+ * @oper:       Operation (enum pds_core_host_mem_oper)
+ * @index:      Memory request index (GET_COUNT: max_count, QUERY: index)
+ * @tag:        Tag for this memory request (ADD/DEL)
+ * @reason:     Reason for deletion (DEL only)
+ * @rsvd:       Reserved
+ * @max_contig: Maximum contiguous memory size (GET_COUNT only)
+ * @size:       Size of memory in bytes (ADD only)
+ * @buf_pa:     DMA address of memory (ADD only)
+ *
+ * Unified command for all host memory operations. Fields are reused
+ * across operations to minimize opcode space usage.
+ */
+struct pds_core_host_mem_cmd {
+	u8     opcode;
+	u8     oper;
+	__le16 index;
+	__le16 tag;
+	u8     reason;
+	u8     rsvd;
+	__le32 max_contig;
+	__le32 size;
+	__le64 buf_pa;
+};
+
+/**
+ * struct pds_core_host_mem_comp - HOST_MEM completion
+ * @status:       Status of the command (enum pds_core_status_code)
+ * @oper:         Operation that was performed
+ * @count:        Number of memory requests (GET_COUNT)
+ * @size:         Size of memory request in bytes (QUERY)
+ * @tag:          Tag for this memory request (QUERY/DEL)
+ * @rsvd:         Reserved
+ */
+struct pds_core_host_mem_comp {
+	u8     status;
+	u8     oper;
+	__le16 count;
+	__le32 size;
+	__le16 tag;
+	u8     rsvd[6];
+};
+
 /*
  * union pds_core_dev_cmd - Overlay of core device command structures
  */
@@ -462,6 +915,14 @@ union pds_core_dev_cmd {
 	struct pds_core_vf_setattr_cmd   vf_setattr;
 	struct pds_core_vf_getattr_cmd   vf_getattr;
 	struct pds_core_vf_ctrl_cmd      vf_ctrl;
+
+	struct pds_core_get_component_info_cmd get_component_info;
+	struct pds_core_send_pkg_data_cmd      send_pkg_data;
+	struct pds_core_send_component_tbl_cmd send_component_tbl;
+	struct pds_core_send_component_cmd     send_component;
+	struct pds_core_finalize_update_cmd    finalize_update;
+	struct pds_core_match_record_desc_cmd  match_record_desc;
+	struct pds_core_host_mem_cmd           host_mem;
 };
 
 /*
@@ -480,6 +941,14 @@ union pds_core_dev_comp {
 	struct pds_core_vf_setattr_comp   vf_setattr;
 	struct pds_core_vf_getattr_comp   vf_getattr;
 	struct pds_core_vf_ctrl_comp      vf_ctrl;
+
+	struct pds_core_get_component_info_comp get_component_info;
+	struct pds_core_send_pkg_data_comp      send_pkg_data;
+	struct pds_core_send_component_tbl_comp send_component_tbl;
+	struct pds_core_send_component_comp     send_component;
+	struct pds_core_finalize_update_comp    finalize_update;
+	struct pds_core_match_record_desc_comp  match_record_desc;
+	struct pds_core_host_mem_comp           host_mem;
 };
 
 /**

3/2 2026/07/31 13:30 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31061
							output: 64
							thoughts: 404
						

Thoughts:
**WorthFuzzing=false: Quick Decision**

Okay, so I have a patch touching the `pds_core` driver, and that immediately triggers a specific condition in my evaluation process. I remember the prompt; it explicitly calls out `pds_core` as an example of vendor-specific drivers for PCIe devices, SmartNICs specifically, that can't be meaningfully fuzzed in a standard QEMU environment.

My initial thought was to dig deeper into the patch to fully understand the changes, but I can see right away it is modifying `drivers/net/ethernet/amd/pds_core/`. The patch adds PLDM firmware update support and host memory allocation for the firmware, but that doesn't matter here.

The crucial part is the driver itself. Let me just confirm that the file path is correct (`drivers/net/ethernet/amd/pds_core/`) and it is indeed a device driver for AMD/Pensando Core devices and uses PCI. The prompt is very clear: "vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU". And, the prompt **explicitly** uses `pds_core` as an example.

Given this direct match, and the need for PCI hardware, I'm confident in returning `WorthFuzzing=false`. No need for a lengthy analysis here, because it is explicitly called out. The patch is touching a driver that relies on a specific piece of hardware for which we cannot test its changes and, therefore, it's not suitable for this particular fuzzing process.



4/2 2026/07/31 13:30 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the pds_core driver, which is a vendor-specific SmartNIC driver that requires physical PCIe hardware not emulated in standard QEMU.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies the pds_core driver, which is a vendor-specific SmartNIC driver that requires physical PCIe hardware not emulated in standard QEMU.",
  "WorthFuzzing": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)