summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-08-05 17:59:32 -0700
committerJakub Kicinski <kuba@kernel.org>2026-08-05 17:59:33 -0700
commit90359b528888e1275b4fe014b31ee23bc7eaf7be (patch)
tree8fba5f5ab5d2093ae4c03183503e511f53b81f62 /drivers
parent2bb824660ef8116ca74bbbc265bfc2d716cd4708 (diff)
parent0b5091b64ad6f870c2484211cd02f96056e64515 (diff)
downloadlinux-90359b528888e1275b4fe014b31ee23bc7eaf7be.tar.gz
linux-90359b528888e1275b4fe014b31ee23bc7eaf7be.zip
Merge branch 'pds_core-add-pldm-firmware-update-and-host-backed-memory-support'
Nikhil P. Rao says: ==================== pds_core: Add PLDM firmware update and host backed memory support This series adds PLDM-based firmware update support to the pds_core driver. PLDM (Platform Level Data Model) is a DMTF standard for firmware management that provides a vendor-neutral interface for firmware updates. The implementation uses the kernel's pldmfw library for package parsing and component matching. Users can update entire firmware packages or individual components via devlink flash. Component information is displayed via devlink info, showing firmware versions and update status for each component. The series also adds host backed memory support, allowing firmware to request memory pages from the host for its operations. ==================== Link: https://patch.msgid.link/20260730-upstream_v8-v12-0-136cd174ee85@amd.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/ethernet/amd/Kconfig1
-rw-r--r--drivers/net/ethernet/amd/pds_core/core.c173
-rw-r--r--drivers/net/ethernet/amd/pds_core/core.h54
-rw-r--r--drivers/net/ethernet/amd/pds_core/debugfs.c45
-rw-r--r--drivers/net/ethernet/amd/pds_core/dev.c144
-rw-r--r--drivers/net/ethernet/amd/pds_core/devlink.c149
-rw-r--r--drivers/net/ethernet/amd/pds_core/fw.c796
-rw-r--r--drivers/net/ethernet/amd/pds_core/main.c7
8 files changed, 1341 insertions, 28 deletions
diff --git a/drivers/net/ethernet/amd/Kconfig b/drivers/net/ethernet/amd/Kconfig
index e35991141a1a..743e3d4b6b94 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 04ec2569c61c..922e3ec8af1b 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 b7fe9ad73349..791756a50870 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 810a0cd9bcac..ef0a1b7d159b 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 bded6b33289c..c50778175e2f 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 8adae7b18898..63fe45e91f71 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 fa626719e68d..5ccf017f6af4 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 @@ err_out:
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 8d94a4d70395..5e1b07850163 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 @@ err_out_shutdown_timer:
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)