* [PATCH 0/6] pds_core: Add PLDM firmware update and host backed memory support
@ 2026-04-29 7:58 Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 7:58 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-hardening, Nikhil P. Rao, eric.joyner,
Vamsi Atluri
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.
Signed-off-by: Nikhil P. Rao <nikhil.rao@amd.com>
---
Brett Creeley (4):
pds_core: add support for quiet devcmd failures
pds_core: add support for identity version 2
pds_core: add PLDM firmware update support via devlink flash
pds_core: add PLDM component info display
Vamsi Atluri (2):
pds_core: add host backed memory support for firmware
pds_core: add debugfs support for host backed memory
drivers/net/ethernet/amd/Kconfig | 1 +
drivers/net/ethernet/amd/pds_core/core.c | 166 +++++++
drivers/net/ethernet/amd/pds_core/core.h | 33 +-
drivers/net/ethernet/amd/pds_core/debugfs.c | 43 ++
drivers/net/ethernet/amd/pds_core/dev.c | 86 +++-
drivers/net/ethernet/amd/pds_core/devlink.c | 77 ++-
drivers/net/ethernet/amd/pds_core/fw.c | 699 +++++++++++++++++++++++++++-
drivers/net/ethernet/amd/pds_core/main.c | 7 +-
include/linux/pds/pds_adminq.h | 132 ++++++
include/linux/pds/pds_core_if.h | 381 +++++++++++++++
10 files changed, 1603 insertions(+), 22 deletions(-)
---
base-commit: 1f5ffc672165ff851063a5fd044b727ab2517ae3
change-id: 20260429-b4-pldm-b4-b36169e986e6
Best regards,
--
Nikhil P. Rao <nikhil.rao@amd.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/6] pds_core: add support for quiet devcmd failures
2026-04-29 7:58 [PATCH 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
@ 2026-04-29 7:58 ` Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 7:58 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-hardening, Nikhil P. Rao, eric.joyner
From: Brett Creeley <brett.creeley@amd.com>
Currently there aren't any use-cases that require special handling
on whether or not to print devcmd failures. Specifically
non-generic failures, i.e. not supported failures. Add support to
allow these messages to be suppressed. This will be used when
adding support to negotiate PDS_CORE_IDENTITY_VERSION_2.
Signed-off-by: Brett Creeley <brett.creeley@amd.com>
---
drivers/net/ethernet/amd/pds_core/dev.c | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
index 2e1d0d01d03a..5b86d6cd0ac3 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;
@@ -172,7 +173,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));
@@ -180,8 +181,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;
@@ -190,7 +192,7 @@ 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);
@@ -200,6 +202,12 @@ int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
return err;
}
+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(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
union pds_core_dev_comp *comp, int max_seconds)
{
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/6] pds_core: add support for identity version 2
2026-04-29 7:58 [PATCH 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
@ 2026-04-29 7:58 ` Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 7:58 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-hardening, Nikhil P. Rao, eric.joyner
From: Brett Creeley <brett.creeley@amd.com>
Add a new capabilities field in struct pds_core_drv_identity,
which requires bumping the identity version to 2, i.e.
PDS_CORE_IDENTITY_VERSION_2. If version 2 negotiation fails,
then quietly fall back to version 1. If version 1 negotiation
fails, then driver load will fail.
Another patch in the series will make use of the capabilities
field.
Signed-off-by: Brett Creeley <brett.creeley@amd.com>
---
drivers/net/ethernet/amd/pds_core/dev.c | 28 +++++++++++++++++++++++-----
include/linux/pds/pds_core_if.h | 4 ++++
2 files changed, 27 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
index 5b86d6cd0ac3..f77bd5e48b92 100644
--- a/drivers/net/ethernet/amd/pds_core/dev.c
+++ b/drivers/net/ethernet/amd/pds_core/dev.c
@@ -243,15 +243,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)
@@ -274,8 +276,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;
@@ -298,7 +301,7 @@ 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));
@@ -327,6 +330,21 @@ 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_IDENTIFY_VERSION_1
+ * instead of returning the max supported identify version, so retry if
+ * firmware doesn't support PDS_CORE_IDENTIFY_VERSION_2
+ */
+ err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_2);
+ if (err)
+ err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_1);
+
+ return err;
+}
+
void pdsc_dev_uninit(struct pdsc *pdsc)
{
if (pdsc->intr_info) {
diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h
index 17a87c1a55d7..619186f26b5b 100644
--- a/include/linux/pds/pds_core_if.h
+++ b/include/linux/pds/pds_core_if.h
@@ -119,6 +119,8 @@ 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
+ * @capabilities: Device capabilities
+ * only supported on version >= PDS_CORE_IDENTITY_VERSION_2
*/
struct pds_core_dev_identity {
u8 version;
@@ -131,9 +133,11 @@ struct pds_core_dev_identity {
__le32 intr_coal_mult;
__le32 intr_coal_div;
__le16 vif_types[PDS_DEV_TYPE_MAX];
+ __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
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/6] pds_core: add PLDM firmware update support via devlink flash
2026-04-29 7:58 [PATCH 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
@ 2026-04-29 7:58 ` Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 4/6] pds_core: add PLDM component info display Nikhil P. Rao
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 7:58 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-hardening, Nikhil P. Rao, eric.joyner
From: Brett Creeley <brett.creeley@amd.com>
Implement PLDM FW Update in the pds_core driver using the upstream
pldmfw API. This allows an entire PLDM FW package to be updated
and/or specific components if they aren't fixed.
Flash the entire image:
devlink dev flash pci/0000:b5:00.0 file firmware.pldmfw
Flash individual components from the PLDM FW package:
devlink dev flash pci/0000:b5:00.0 \
file firmware.pldmfw component fw.mainfwa
devlink dev flash pci/0000:b5:00.0 \
file firmware.pldmfw component fw.mainfwb
devlink dev flash pci/0000:b5:00.0 \
file firmware.pldmfw component fw.goldfw
Assisted-by: Claude:claude-opus-4.6
Signed-off-by: Brett Creeley <brett.creeley@amd.com>
Signed-off-by: Nikhil P. Rao <nikhil.rao@amd.com>
---
drivers/net/ethernet/amd/Kconfig | 1 +
drivers/net/ethernet/amd/pds_core/core.h | 14 +-
drivers/net/ethernet/amd/pds_core/dev.c | 42 +-
drivers/net/ethernet/amd/pds_core/devlink.c | 2 +-
drivers/net/ethernet/amd/pds_core/fw.c | 699 +++++++++++++++++++++++++++-
drivers/net/ethernet/amd/pds_core/main.c | 4 +-
include/linux/pds/pds_core_if.h | 375 +++++++++++++++
7 files changed, 1130 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/amd/Kconfig b/drivers/net/ethernet/amd/Kconfig
index 45e8d698781c..e7346837dad6 100644
--- a/drivers/net/ethernet/amd/Kconfig
+++ b/drivers/net/ethernet/amd/Kconfig
@@ -192,6 +192,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.h b/drivers/net/ethernet/amd/pds_core/core.h
index 4a6b35c84dab..c9ba63878927 100644
--- a/drivers/net/ethernet/amd/pds_core/core.h
+++ b/drivers/net/ethernet/amd/pds_core/core.h
@@ -199,6 +199,8 @@ struct pdsc {
u64 last_eid;
struct pdsc_viftype *viftype_status;
struct work_struct pci_reset_work;
+
+ struct pds_core_component_list_info fw_components;
};
/** enum pds_core_dbell_bits - bitwise composition of dbell values.
@@ -281,8 +283,16 @@ 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,8 +325,10 @@ 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);
void pdsc_fw_down(struct pdsc *pdsc);
void pdsc_fw_up(struct pdsc *pdsc);
diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
index f77bd5e48b92..4bbf299a88dc 100644
--- a/drivers/net/ethernet/amd/pds_core/dev.c
+++ b/drivers/net/ethernet/amd/pds_core/dev.c
@@ -127,7 +127,7 @@ static const char *pdsc_devcmd_str(int opcode)
}
static int __pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds,
- const bool do_msg)
+ bool do_msg)
{
struct device *dev = pdsc->dev;
unsigned long start_time;
@@ -208,6 +208,12 @@ int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
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)
{
@@ -220,6 +226,40 @@ int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
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)
+{
+ int err;
+
+ if (data_len > sizeof(pdsc->cmd_regs->data))
+ return -ENOSPC;
+
+ mutex_lock(&pdsc->devcmd_lock);
+ memcpy_toio(&pdsc->cmd_regs->data, data, data_len);
+ err = pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds);
+ mutex_unlock(&pdsc->devcmd_lock);
+
+ return err;
+}
+
+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 err;
+
+ if (data_len > sizeof(pdsc->cmd_regs->data))
+ return -ENOSPC;
+
+ mutex_lock(&pdsc->devcmd_lock);
+ memcpy_toio(&pdsc->cmd_regs->data, data, data_len);
+ err = pdsc_devcmd_locked_nomsg(pdsc, cmd, comp, max_seconds);
+ mutex_unlock(&pdsc->devcmd_lock);
+
+ return err;
+}
+
int pdsc_devcmd_init(struct pdsc *pdsc)
{
union pds_core_dev_comp comp = {};
diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c
index b576be626a29..7f44e1a8d4fd 100644
--- a/drivers/net/ethernet/amd/pds_core/devlink.c
+++ b/drivers/net/ethernet/amd/pds_core/devlink.c
@@ -90,7 +90,7 @@ int pdsc_dl_flash_update(struct devlink *dl,
{
struct pdsc *pdsc = devlink_priv(dl);
- return pdsc_firmware_update(pdsc, params->fw, extack);
+ return pdsc_firmware_update(pdsc, params, extack);
}
static char *fw_slotnames[] = {
diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c
index fa626719e68d..4ccf90f25f75 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,10 @@
/* 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)
+
static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr,
u32 offset, u32 length)
{
@@ -23,7 +30,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,8 +102,9 @@ 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,
+ const struct firmware *fw,
+ struct netlink_ext_ack *extack)
{
u32 buf_sz, copy_sz, offset;
struct devlink *dl;
@@ -195,3 +203,688 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
NULL, 0, 0);
return err;
}
+
+struct pdsc_component_priv {
+ char component_name[PDSC_FW_COMPONENT_FULL_NAME_BUFLEN];
+ 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;
+};
+
+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 device *dev = context->dev;
+ struct pdsc *pdsc = priv->pdsc;
+ u8 *package_data;
+ u32 offset;
+ int err;
+
+ if (!length)
+ return 0;
+
+ package_data = kmemdup(data, length, GFP_KERNEL);
+ if (!package_data)
+ return -ENOMEM;
+
+ offset = 0;
+ while (offset < length) {
+ dma_addr_t dma_addr;
+ u32 copy_sz;
+
+ copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, length - offset);
+ dma_addr = dma_map_single(dev, package_data + offset, copy_sz,
+ DMA_TO_DEVICE);
+ err = dma_mapping_error(dev, dma_addr);
+ if (err) {
+ dev_err(dev, "Failed to dma_map package_data at offset 0x%x copy_sz 0x%x: %pe\n",
+ offset, copy_sz, ERR_PTR(err));
+ goto out;
+ }
+
+ err = pdsc_devcmd_send_package_data(pdsc, dma_addr, copy_sz, offset,
+ length);
+ if (err)
+ dev_err(dev, "send_package_data failed offset 0x%x addr 0x%llx len 0x%x: %pe\n",
+ offset, dma_addr, copy_sz, ERR_PTR(err));
+
+ dma_unmap_single(dev, dma_addr, copy_sz, DMA_TO_DEVICE);
+ if (err)
+ goto out;
+
+ offset += copy_sz;
+ }
+
+out:
+ kfree(package_data);
+ return err;
+}
+
+static void pdsc_set_component_name(struct pdsc *pdsc, u16 component_id,
+ u8 slot_id, char *component_name)
+{
+ 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 (component_id == info->identifier &&
+ slot_id == info->slot_id) {
+ snprintf(component_name,
+ PDSC_FW_COMPONENT_FULL_NAME_BUFLEN,
+ "fw.%s", info->name);
+ return;
+ }
+ }
+}
+
+static const char *pdsc_get_component_priv_name(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)
+ continue;
+
+ return component_priv->component_name;
+ }
+
+ return NULL;
+}
+
+static struct pds_core_fw_component_info *
+pdsc_find_component_by_name(struct pdsc *pdsc, const char *component_name)
+{
+ struct pds_core_fw_component_info *info;
+ size_t prefix_len;
+ int i;
+
+ prefix_len = str_has_prefix(component_name, PDSC_FW_COMPONENT_PREFIX);
+ if (!prefix_len)
+ return NULL;
+
+ component_name += prefix_len; /* Skip "fw." prefix */
+
+ for (i = 0; i < pdsc->fw_components.num_components; i++) {
+ info = &pdsc->fw_components.info[i];
+
+ if (!strncmp(component_name, info->name,
+ PDS_CORE_FW_COMPONENT_NAME_BUFLEN))
+ return info;
+ }
+
+ return NULL;
+}
+
+static u8 pdsc_get_slot_id(struct pdsc *pdsc, const char *component_name)
+{
+ struct pds_core_fw_component_info *info;
+
+ info = pdsc_find_component_by_name(pdsc, component_name);
+ return info ? info->slot_id : PDS_CORE_FW_SLOT_MAX;
+}
+
+static bool pdsc_skip_component(struct pds_core_fwu_priv *priv,
+ u16 component_id, const char *component_name)
+{
+ struct pdsc_component_priv *component_priv;
+
+ list_for_each_entry(component_priv, &priv->components, list_entry) {
+ if (component_priv->component_id != component_id)
+ continue;
+
+ if (component_priv->skip)
+ return true;
+
+ if (component_name &&
+ strncmp(component_priv->component_name, component_name,
+ PDSC_FW_COMPONENT_FULL_NAME_BUFLEN))
+ return true;
+ }
+
+ return false;
+}
+
+static bool pdsc_match_component_name_to_ids(struct pdsc *pdsc,
+ const char *component_name,
+ u8 component_id,
+ u8 slot_id)
+{
+ struct pds_core_fw_component_info *info;
+
+ info = pdsc_find_component_by_name(pdsc, component_name);
+ if (!info)
+ return false;
+
+ return slot_id == info->slot_id && component_id == info->identifier;
+}
+
+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;
+ u16 buf_sz, tbl_sz;
+ int err = 0;
+ u8 slot_id;
+
+ 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) {
+ slot_id = pdsc_get_slot_id(pdsc, priv->params->component);
+ if (slot_id == PDS_CORE_FW_SLOT_MAX)
+ return -ENOENT;
+
+ if (!pdsc_match_component_name_to_ids(pdsc,
+ priv->params->component,
+ component->identifier,
+ slot_id)) {
+ skip_component = true;
+ goto add_component_priv;
+ }
+ } else {
+ slot_id = PDS_CORE_FW_SLOT_INVALID;
+ }
+
+ 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 = slot_id;
+
+ err = pdsc_devcmd_with_data(pdsc, &cmd, component_tbl, tbl_sz,
+ &comp, pdsc->devcmd_timeout);
+ if (err)
+ dev_err(dev, "Failed sending component table: %pe\n",
+ ERR_PTR(err));
+ kfree(component_tbl);
+ if (err)
+ goto free_component_priv;
+
+ if (comp.send_component_tbl.response == 1 &&
+ comp.send_component_tbl.response_code == PDS_CORE_COMPONENT_PREREQS_NOT_MET)
+ skip_component = true;
+ else
+ pdsc_set_component_name(pdsc, component->identifier,
+ comp.send_component_tbl.slot_id,
+ component_priv->component_name);
+
+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;
+ union pds_core_dev_comp comp = {};
+ dma_addr_t dma_addr;
+ u8 num_components;
+ int err, i;
+
+ list_info = kzalloc(PDS_PAGE_SIZE, GFP_KERNEL);
+ if (!list_info)
+ return -ENOMEM;
+
+ dma_addr = dma_map_single(pdsc->dev, list_info, PDS_PAGE_SIZE, DMA_FROM_DEVICE);
+ err = dma_mapping_error(pdsc->dev, dma_addr);
+ if (err) {
+ dev_err(pdsc->dev, "Failed to dma_map component_list_info length %d: %pe\n",
+ PDS_PAGE_SIZE, ERR_PTR(err));
+ goto out;
+ }
+
+ 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);
+ 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(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/ETIMEDOUT 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)
+ dev_err(pdsc->dev, "PDS_CORE_CMD_SEND_COMPONENT timed out\n");
+
+ return err;
+}
+
+static int pdsc_flash_component(struct pldmfw *context,
+ struct pldmfw_component *component)
+{
+ struct pds_core_fwu_priv *priv =
+ container_of(context, struct pds_core_fwu_priv, context);
+ const char *component_name = priv->params->component;
+ struct pds_core_flash_component *component_info;
+ struct device *dev = context->dev;
+ struct pdsc *pdsc = priv->pdsc;
+ u16 buf_sz, info_sz;
+ struct devlink *dl;
+ u32 total_len;
+ u32 offset;
+ u8 slot_id;
+ int err;
+
+ if (pdsc_skip_component(priv, component->identifier, component_name))
+ return 0;
+
+ if (component_name) {
+ slot_id = pdsc_get_slot_id(pdsc, component_name);
+ if (slot_id == PDS_CORE_FW_SLOT_MAX)
+ return 0;
+ } else {
+ component_name = pdsc_get_component_priv_name(priv, component->identifier);
+ slot_id = PDS_CORE_FW_SLOT_INVALID;
+ }
+
+ 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, 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);
+
+ dl = priv_to_devlink(pdsc);
+
+ offset = 0;
+ while (offset < total_len) {
+ union pds_core_dev_comp comp = {};
+ dma_addr_t dma_addr;
+ u8 *component_data;
+ u16 copy_sz;
+
+ copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, total_len - offset);
+ component_data = kmemdup(component->component_data + offset,
+ copy_sz, GFP_KERNEL);
+ if (!component_data) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ dma_addr = dma_map_single(dev, component_data, copy_sz,
+ DMA_TO_DEVICE);
+ err = dma_mapping_error(pdsc->dev, dma_addr);
+ if (err) {
+ dev_err(dev, "Failed to dma_map component_data at offset 0x%x copy_sz 0x%x: %pe\n",
+ offset, copy_sz, ERR_PTR(err));
+ kfree(component_data);
+ goto err_out;
+ }
+
+ err = pdsc_devcmd_send_component(pdsc, component_info, info_sz,
+ dma_addr, copy_sz, offset,
+ slot_id, &comp);
+ dma_unmap_single(dev, dma_addr, copy_sz, DMA_TO_DEVICE);
+ kfree(component_data);
+ if (err && err != -EAGAIN &&
+ 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) {
+ dev_err(dev,
+ "send_component failed offset 0x%x addr 0x%llx len 0x%x: %pe\n",
+ offset, dma_addr, copy_sz, ERR_PTR(err));
+ goto err_out;
+ }
+
+ offset += copy_sz;
+ devlink_flash_update_status_notify(dl,
+ "Erasing/Flashing",
+ component_name, offset,
+ total_len);
+ }
+
+ 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_locked(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 || 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);
+ dev_err(dev, "finalize_update failed: %pe\n", ERR_PTR(err));
+ 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 no component filter specified, devlink core didn't refresh cache,
+ * so we must refresh to handle stale cache from previous updates.
+ */
+ if (!params->component) {
+ err = pdsc_get_component_info(pdsc);
+ if (err) {
+ dev_err(pdsc->dev, "Failed to get component info: %pe\n", ERR_PTR(err));
+ return err;
+ }
+ }
+
+ INIT_LIST_HEAD(&priv.components);
+ priv.context.ops = &pdsc_pldmfw_ops;
+ priv.context.dev = pdsc->dev;
+ priv.params = params;
+ priv.pdsc = pdsc;
+
+ err = pldmfw_flash_image(&priv.context, fw);
+ 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)
+{
+ if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2 &&
+ pdsc->dev_ident.capabilities & cpu_to_le64(PDS_CORE_DEV_CAP_PLDM_FW_UPDATE))
+ return pdsc_pldm_firmware_update(pdsc, params, extack, params->fw);
+
+ return pdsc_legacy_firmware_update(pdsc, params->fw, extack);
+}
diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c
index 22db78343eb0..f0d0993f9d91 100644
--- a/drivers/net/ethernet/amd/pds_core/main.c
+++ b/drivers/net/ethernet/amd/pds_core/main.c
@@ -340,7 +340,9 @@ static int pdsc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
is_pf = !pdev->is_virtfn;
ops = is_pf ? &pdsc_dl_ops : &pdsc_dl_vf_ops;
- dl = devlink_alloc(ops, sizeof(struct pdsc), dev);
+ dl = devlink_alloc(ops, sizeof(struct pdsc) +
+ PDS_CORE_FW_COMPONENT_LIST_LEN *
+ sizeof(struct pds_core_fw_component_info), dev);
if (!dl)
return -ENOMEM;
pdsc = devlink_priv(dl);
diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h
index 619186f26b5b..b8052985dddf 100644
--- a/include/linux/pds/pds_core_if.h
+++ b/include/linux/pds/pds_core_if.h
@@ -40,6 +40,13 @@ 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,
+
/* SR/IOV commands */
PDS_CORE_CMD_VF_GETATTR = 60,
PDS_CORE_CMD_VF_SETATTR = 61,
@@ -100,6 +107,14 @@ 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
+ */
+enum pds_core_dev_capability {
+ PDS_CORE_DEV_CAP_PLDM_FW_UPDATE = BIT(0),
+};
+
#define PDS_DEV_TYPE_MAX 16
/**
* struct pds_core_dev_identity - Device identity information
@@ -119,6 +134,8 @@ 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: Maximum number of fw slots/components
+ * only supported on version >= PDS_CORE_IDENTITY_VERSION_2
* @capabilities: Device capabilities
* only supported on version >= PDS_CORE_IDENTITY_VERSION_2
*/
@@ -133,6 +150,7 @@ struct pds_core_dev_identity {
__le32 intr_coal_mult;
__le32 intr_coal_div;
__le16 vif_types[PDS_DEV_TYPE_MAX];
+ __le16 max_fw_slots;
__le64 capabilities;
};
@@ -284,6 +302,7 @@ enum pds_core_fw_slot {
PDS_CORE_FW_SLOT_A = 1,
PDS_CORE_FW_SLOT_B = 2,
PDS_CORE_FW_SLOT_GOLD = 3,
+ PDS_CORE_FW_SLOT_MAX = 0xff,
};
/**
@@ -450,6 +469,348 @@ struct pds_core_vf_ctrl_comp {
u8 status;
};
+/**
+ * struct pds_core_send_pkg_data_cmd
+ * @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
+ * @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
+ * @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)
+ *
+ * When alternate firmware is requested via PDS_CORE_FW_SLOT_INVALID, the
+ * completion's slot_id will match the actual slot_id that will be flashed
+ * on success. When specific components are flashed, then the completion's
+ * slot_id will match the command's slot_id.
+ *
+ * On failure the slot_id will be set to PDS_CORE_FW_SLOT_MAX.
+ * On success the slot_id will be PDS_CORE_FW_SLOT_A, PDS_CORE_FW_SLOT_B, or
+ * PDS_CORE_FW_SLOT_GOLD.
+ *
+ * @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
+ * PDS_CORE_CMD_SEND_COMPONENT
+ */
+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
+ * @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
+ * @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_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
+ * @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 rsvd[4];
+ __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 - \
+ sizeof(struct pds_core_component_list_info)) / \
+ sizeof(struct pds_core_fw_component_info))
+
+#if defined(__has_attribute) && !__has_attribute(__counted_by__)
+#define __counted_by(member)
+#endif
+
+/**
+ * struct pds_core_component_list_info - GET_COMPONENT_INFO completion data
+ * @num_components: Number of valid components
+ * @info: List of valid components
+ */
+struct pds_core_component_list_info {
+ u8 num_components;
+ struct pds_core_fw_component_info info[] __counted_by(num_components);
+} __packed;
+
+/**
+ * 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;
+};
+
/*
* union pds_core_dev_cmd - Overlay of core device command structures
*/
@@ -466,6 +827,13 @@ 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;
};
/*
@@ -484,6 +852,13 @@ 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;
};
/**
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 4/6] pds_core: add PLDM component info display
2026-04-29 7:58 [PATCH 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
` (2 preceding siblings ...)
2026-04-29 7:58 ` [PATCH 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
@ 2026-04-29 7:58 ` Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
5 siblings, 0 replies; 7+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 7:58 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-hardening, Nikhil P. Rao, eric.joyner
From: Brett Creeley <brett.creeley@amd.com>
Add detailed component information display. This allows users to see
individual firmware components, their versions, and update status via
devlink info. Components are marked as fixed, running, or stored based
on their flags.
Example output:
$ devlink dev info pci/0000:b5:00.0
...
versions:
running:
fw.goldfw 1.2.3
fw.mainfwa 1.2.4
fw.mainfwb 1.2.3
Signed-off-by: Brett Creeley <brett.creeley@amd.com>
---
drivers/net/ethernet/amd/pds_core/devlink.c | 75 ++++++++++++++++++++++++++---
1 file changed, 69 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c
index 7f44e1a8d4fd..c519fde45d71 100644
--- a/drivers/net/ethernet/amd/pds_core/devlink.c
+++ b/drivers/net/ethernet/amd/pds_core/devlink.c
@@ -93,14 +93,61 @@ int pdsc_dl_flash_update(struct devlink *dl,
return pdsc_firmware_update(pdsc, params, extack);
}
+static int pdsc_dl_component_info_get(struct devlink *dl,
+ struct devlink_info_req *req,
+ struct netlink_ext_ack *extack)
+{
+ struct pds_core_component_list_info *list_info;
+ struct pdsc *pdsc = devlink_priv(dl);
+ u8 num_components;
+ char buf[32];
+ int err;
+ int i;
+
+ err = pdsc_get_component_info(pdsc);
+ if (err) {
+ dev_err(pdsc->dev, "Failed to get component_info %pe", ERR_PTR(err));
+ return err;
+ }
+
+ list_info = &pdsc->fw_components;
+ num_components = min_t(u8, list_info->num_components,
+ le16_to_cpu(pdsc->dev_ident.max_fw_slots));
+ for (i = 0; i < num_components; i++) {
+ enum devlink_info_version_type dl_ver_type = DEVLINK_INFO_VERSION_TYPE_NONE;
+ struct pds_core_fw_component_info *info = &list_info->info[i];
+ u16 flags = le16_to_cpu(info->flags);
+
+ snprintf(buf, sizeof(buf), "fw.%s", info->name);
+ if (flags & PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME)
+ dl_ver_type = DEVLINK_INFO_VERSION_TYPE_COMPONENT;
+
+ if (flags & PDS_CORE_FW_COMPONENT_INFO_F_FIXED)
+ err = devlink_info_version_fixed_put(req, buf,
+ info->version);
+ else if (flags & PDS_CORE_FW_COMPONENT_INFO_F_RUNNING)
+ err = devlink_info_version_running_put_ext(req, buf,
+ info->version, dl_ver_type);
+ else
+ err = devlink_info_version_stored_put_ext(req, buf,
+ info->version, dl_ver_type);
+
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static char *fw_slotnames[] = {
"fw.goldfw",
"fw.mainfwa",
"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,
@@ -132,11 +179,27 @@ 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);
- if (err)
+ return devlink_info_version_running_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_FW,
+ 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);
+ else
+ err = pdsc_dl_fw_list_info_get(dl, req, extack);
+ if (err) {
+ dev_err(pdsc->dev, "Failed to get devlink info for identity version %u: %pe\n",
+ pdsc->dev_ident.version, ERR_PTR(err));
return err;
+ }
snprintf(buf, sizeof(buf), "0x%x", pdsc->dev_info.asic_type);
err = devlink_info_version_fixed_put(req,
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 5/6] pds_core: add host backed memory support for firmware
2026-04-29 7:58 [PATCH 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
` (3 preceding siblings ...)
2026-04-29 7:58 ` [PATCH 4/6] pds_core: add PLDM component info display Nikhil P. Rao
@ 2026-04-29 7:58 ` Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
5 siblings, 0 replies; 7+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 7:58 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-hardening, Nikhil P. Rao, eric.joyner,
Vamsi Atluri
From: Vamsi Atluri <Vamsi.Atluri@amd.com>
Some newer AMD/Pensando cards have minimal memory and there are cases
where components, specifically in the control plane, need more memory.
This series adds support for host backed DMA memory that can be used
by the firmware for the previously mentioned cases.
Assisted-by: Claude:claude-opus-4.6
Signed-off-by: Vamsi Atluri <Vamsi.Atluri@amd.com>
Signed-off-by: Nikhil P. Rao <nikhil.rao@amd.com>
---
drivers/net/ethernet/amd/pds_core/core.c | 166 +++++++++++++++++++++++++++++++
drivers/net/ethernet/amd/pds_core/core.h | 19 ++++
drivers/net/ethernet/amd/pds_core/main.c | 1 +
include/linux/pds/pds_adminq.h | 132 ++++++++++++++++++++++++
include/linux/pds/pds_core_if.h | 2 +
5 files changed, 320 insertions(+)
diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
index 705cab7b0727..e94fea06c6cc 100644
--- a/drivers/net/ethernet/amd/pds_core/core.c
+++ b/drivers/net/ethernet/amd/pds_core/core.c
@@ -487,6 +487,7 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing)
pdsc->viftype_status = NULL;
}
+ pdsc_host_mem_free(pdsc);
pdsc_dev_uninit(pdsc);
set_bit(PDSC_S_FW_DEAD, &pdsc->state);
@@ -496,6 +497,7 @@ 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);
return 0;
}
@@ -658,3 +660,167 @@ 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_adminq_comp comp = {};
+ union pds_core_adminq_cmd cmd = {
+ .mem_del.opcode = PDS_AQ_CMD_MEM_DEL,
+ .mem_del.tag = cpu_to_le16(tag),
+ .mem_del.reason = reason,
+ };
+
+ dev_dbg(pdsc->dev, "Sending aq cmd for mem del tag %d\n", tag);
+ pdsc_adminq_post(pdsc, &cmd, &comp, false);
+}
+
+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_adminq_comp comp = {};
+ union pds_core_adminq_cmd cmd = {};
+ int err;
+
+ memset(hm, 0, sizeof(*hm));
+ cmd.mem_query.opcode = PDS_AQ_CMD_MEM_QUERY;
+ dev_dbg(pdsc->dev, "Sending aq cmd for mem query index %d\n", index);
+ err = pdsc_adminq_post(pdsc, &cmd, &comp, false);
+ 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.mem_query.size);
+ hm->tag = le16_to_cpu(comp.mem_query.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 = max(ilog2(hm->size), PAGE_SHIFT) - PAGE_SHIFT;
+ hm->pg = alloc_pages(GFP_KERNEL, hm->order);
+ if (!hm->pg) {
+ dev_err(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);
+ err = -EIO;
+ goto err_del;
+ }
+
+ memset(&cmd, 0, sizeof(cmd));
+ memset(&comp, 0, sizeof(comp));
+ cmd.mem_add.opcode = PDS_AQ_CMD_MEM_ADD;
+ cmd.mem_add.tag = cpu_to_le16(hm->tag);
+ cmd.mem_add.size = cpu_to_le32(hm->size);
+ cmd.mem_add.buf_pa = cpu_to_le64(hm->pa);
+
+ dev_dbg(pdsc->dev, "Sending aq cmd for mem add tag %d size %d pa 0x%llx\n",
+ hm->tag, hm->size, hm->pa);
+ err = pdsc_adminq_post(pdsc, &cmd, &comp, false);
+ 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);
+ if (hm->pg) {
+ if (!dma_mapping_error(pdsc->dev, hm->pa))
+ dma_unmap_page(pdsc->dev, hm->pa, hm->size, DMA_BIDIRECTIONAL);
+ __free_pages(hm->pg, hm->order);
+ hm->pg = NULL;
+ }
+ return err;
+}
+
+void pdsc_host_mem_add(struct pdsc *pdsc)
+{
+ union pds_core_adminq_comp comp = {};
+ union pds_core_adminq_cmd cmd = {};
+ u16 count;
+ int err;
+ int i;
+
+ if (!(pdsc->dev_ident.capabilities & cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM)))
+ return;
+
+ cmd.mem_get_count.opcode = PDS_AQ_CMD_MEM_GET_COUNT;
+ cmd.mem_get_count.max_contig = cpu_to_le32(PDSC_HOST_MEM_MAX_CONTIG);
+ dev_dbg(pdsc->dev, "Sending aq cmd for mem get count max_contig %lu\n",
+ PDSC_HOST_MEM_MAX_CONTIG);
+ err = pdsc_adminq_post(pdsc, &cmd, &comp, false);
+ 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 = le16_to_cpu(comp.mem_get_count.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;
+ }
+
+ pdsc->num_host_mem_reqs = i;
+}
+
+void pdsc_host_mem_del(struct pdsc *pdsc)
+{
+ int i;
+
+ if (!pdsc->host_mem_reqs)
+ return;
+
+ for (i = 0; i < pdsc->num_host_mem_reqs; i++)
+ pdsc_host_mem_del_one(pdsc, pdsc->host_mem_reqs[i].tag,
+ PDS_RC_SUCCESS);
+}
+
+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 c9ba63878927..e53edf72a5d5 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,8 @@
#define PDSC_SETUP_RECOVERY false
#define PDSC_SETUP_INIT true
+#define PDSC_HOST_MEM_MAX_CONTIG ((PAGE_SIZE) << (MAX_PAGE_ORDER))
+
struct pdsc_dev_bar {
void __iomem *vaddr;
phys_addr_t bus_addr;
@@ -141,6 +144,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 */
@@ -200,6 +211,9 @@ struct pdsc {
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;
};
@@ -277,6 +291,7 @@ 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);
int pdsc_err_to_errno(enum pds_core_status_code code);
bool pdsc_is_fw_running(struct pdsc *pdsc);
@@ -334,4 +349,8 @@ 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_del(struct pdsc *pdsc);
+void pdsc_host_mem_free(struct pdsc *pdsc);
+
#endif /* _PDSC_H_ */
diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c
index f0d0993f9d91..0a0542bf7cbb 100644
--- a/drivers/net/ethernet/amd/pds_core/main.c
+++ b/drivers/net/ethernet/amd/pds_core/main.c
@@ -437,6 +437,7 @@ static void pdsc_remove(struct pci_dev *pdev)
pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev);
timer_shutdown_sync(&pdsc->wdtimer);
+ pdsc_host_mem_del(pdsc);
if (pdsc->wq)
destroy_workqueue(pdsc->wq);
diff --git a/include/linux/pds/pds_adminq.h b/include/linux/pds/pds_adminq.h
index 40ff0ec2b879..ef46415ab9fd 100644
--- a/include/linux/pds/pds_adminq.h
+++ b/include/linux/pds/pds_adminq.h
@@ -34,6 +34,12 @@ enum pds_core_adminq_opcode {
PDS_AQ_CMD_RX_FILTER_ADD = 31,
PDS_AQ_CMD_RX_FILTER_DEL = 32,
+ /* MEM commands */
+ PDS_AQ_CMD_MEM_GET_COUNT = 10,
+ PDS_AQ_CMD_MEM_QUERY = 11,
+ PDS_AQ_CMD_MEM_ADD = 12,
+ PDS_AQ_CMD_MEM_DEL = 13,
+
/* Queue commands */
PDS_AQ_CMD_Q_IDENTIFY = 39,
PDS_AQ_CMD_Q_INIT = 40,
@@ -207,6 +213,122 @@ struct pds_core_client_request_cmd {
u8 client_cmd[60];
};
+/**
+ * struct pds_core_mem_get_count_cmd - MEM_GET_COUNT command
+ * @opcode: opcode PDS_AQ_CMD_MEM_GET_COUNT
+ * @rsvd: Word boundary padding
+ * @max_contig: Maximum contiguous memory size in bytes
+ *
+ * Query the number of host memory requests needed by firmware.
+ */
+struct pds_core_mem_get_count_cmd {
+ u8 opcode;
+ u8 rsvd[3];
+ __le32 max_contig;
+} __packed;
+
+/**
+ * struct pds_core_mem_get_count_comp - MEM_GET_COUNT completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @rsvd: Word boundary padding
+ * @comp_index: Index in the descriptor ring for which this is the completion
+ * @count: Number of host memory requests
+ * @rsvd2: Word boundary padding
+ * @color: Color bit
+ */
+struct pds_core_mem_get_count_comp {
+ u8 status;
+ u8 rsvd;
+ __le16 comp_index;
+ __le16 count;
+ u8 rsvd2[9];
+ u8 color;
+} __packed;
+
+/**
+ * struct pds_core_mem_query_cmd - MEM_QUERY command
+ * @opcode: opcode PDS_AQ_CMD_MEM_QUERY
+ * @rsvd: Word boundary padding
+ * @index: Memory request index
+ */
+struct pds_core_mem_query_cmd {
+ u8 opcode;
+ u8 rsvd;
+ __le16 index;
+} __packed;
+
+/**
+ * struct pds_core_mem_query_comp - MEM_QUERY completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @rsvd: Word boundary padding
+ * @comp_index: Index in the descriptor ring for which this is the completion
+ * @size: Size of memory request in bytes
+ * @tag: Tag for this memory request
+ */
+struct pds_core_mem_query_comp {
+ u8 status;
+ u8 rsvd;
+ __le16 comp_index;
+ __le32 size;
+ __le16 tag;
+} __packed;
+
+/**
+ * struct pds_core_mem_add_cmd - MEM_ADD command
+ * @opcode: opcode PDS_AQ_CMD_MEM_ADD
+ * @rsvd: Word boundary padding
+ * @tag: Tag for this memory request
+ * @size: Size of memory in bytes
+ * @buf_pa: DMA address of memory
+ */
+struct pds_core_mem_add_cmd {
+ u8 opcode;
+ u8 rsvd;
+ __le16 tag;
+ __le32 size;
+ __le64 buf_pa;
+} __packed;
+
+/**
+ * struct pds_core_mem_add_comp - MEM_ADD command completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @rsvd: padding for natural alignment
+ * @comp_index: Index in the desc ring for which this is the completion
+ */
+struct pds_core_mem_add_comp {
+ u8 status;
+ u8 rsvd;
+ __le16 comp_index;
+} __packed;
+
+/**
+ * struct pds_core_mem_del_cmd - MEM_DEL command
+ * @opcode: opcode PDS_AQ_CMD_MEM_DEL
+ * @rsvd: Word boundary padding
+ * @tag: Tag for this memory request
+ * @reason: Reason for deletion
+ */
+struct pds_core_mem_del_cmd {
+ u8 opcode;
+ u8 rsvd;
+ __le16 tag;
+ u8 reason;
+} __packed;
+
+/**
+ * struct pds_core_mem_del_comp - MEM_DEL command completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @rsvd: Word boundary padding
+ * @comp_index: Index in the desc ring for which this is the completion
+ * @tag: Tag for the memory request
+ */
+struct pds_core_mem_del_comp {
+ u8 status;
+ u8 rsvd;
+ __le16 comp_index;
+ __le16 tag;
+} __packed;
+
#define PDS_CORE_MAX_FRAGS 16
#define PDS_CORE_QCQ_F_INITED BIT(0)
@@ -1454,6 +1576,11 @@ union pds_core_adminq_cmd {
struct pds_core_client_unreg_cmd client_unreg;
struct pds_core_client_request_cmd client_request;
+ struct pds_core_mem_get_count_cmd mem_get_count;
+ struct pds_core_mem_query_cmd mem_query;
+ struct pds_core_mem_add_cmd mem_add;
+ struct pds_core_mem_del_cmd mem_del;
+
struct pds_core_lif_identify_cmd lif_ident;
struct pds_core_lif_init_cmd lif_init;
struct pds_core_lif_reset_cmd lif_reset;
@@ -1502,6 +1629,11 @@ union pds_core_adminq_comp {
struct pds_core_client_reg_comp client_reg;
+ struct pds_core_mem_get_count_comp mem_get_count;
+ struct pds_core_mem_query_comp mem_query;
+ struct pds_core_mem_add_comp mem_add;
+ struct pds_core_mem_del_comp mem_del;
+
struct pds_core_lif_identify_comp lif_ident;
struct pds_core_lif_init_comp lif_init;
struct pds_core_lif_setattr_comp lif_setattr;
diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h
index b8052985dddf..fb489e8d54ef 100644
--- a/include/linux/pds/pds_core_if.h
+++ b/include/linux/pds/pds_core_if.h
@@ -110,9 +110,11 @@ struct pds_core_drv_identity {
/**
* 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
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 6/6] pds_core: add debugfs support for host backed memory
2026-04-29 7:58 [PATCH 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
` (4 preceding siblings ...)
2026-04-29 7:58 ` [PATCH 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
@ 2026-04-29 7:58 ` Nikhil P. Rao
5 siblings, 0 replies; 7+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 7:58 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Kees Cook, Gustavo A. R. Silva
Cc: netdev, linux-kernel, linux-hardening, Nikhil P. Rao, eric.joyner,
Vamsi Atluri
From: Vamsi Atluri <Vamsi.Atluri@amd.com>
Add debugfs file to display host memory allocations including tag,
size, order, and physical address for each memory request.
Signed-off-by: Vamsi Atluri <Vamsi.Atluri@amd.com>
---
drivers/net/ethernet/amd/pds_core/debugfs.c | 43 +++++++++++++++++++++++++++++
drivers/net/ethernet/amd/pds_core/main.c | 2 ++
2 files changed, 45 insertions(+)
diff --git a/drivers/net/ethernet/amd/pds_core/debugfs.c b/drivers/net/ethernet/amd/pds_core/debugfs.c
index 04c5e3abd8d7..79312107f721 100644
--- a/drivers/net/ethernet/amd/pds_core/debugfs.c
+++ b/drivers/net/ethernet/amd/pds_core/debugfs.c
@@ -173,3 +173,46 @@ 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: %d\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, "%-6d %-12u %-6d 0x%llx\n",
+ hm->tag, hm->size, hm->order,
+ (unsigned long long)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;
+
+ /* This file will already exist in the reset flow */
+ if (debugfs_lookup("host_mem", pdsc->dentry))
+ return;
+
+ debugfs_create_file("host_mem", 0400, pdsc->dentry,
+ pdsc, &host_mem_fops);
+}
diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c
index 0a0542bf7cbb..4c14198eaafe 100644
--- a/drivers/net/ethernet/amd/pds_core/main.c
+++ b/drivers/net/ethernet/amd/pds_core/main.c
@@ -264,6 +264,8 @@ static int pdsc_init_pf(struct pdsc *pdsc)
mutex_unlock(&pdsc->config_lock);
+ pdsc_debugfs_add_host_mem(pdsc);
+
err = pdsc_auxbus_dev_add(pdsc, pdsc, PDS_DEV_TYPE_FWCTL, &pdsc->padev);
if (err)
goto err_out_stop;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-04-29 7:58 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-29 7:58 [PATCH 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 4/6] pds_core: add PLDM component info display Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
2026-04-29 7:58 ` [PATCH 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox