* [PATCH net-next 0/6] pds_core: Add PLDM firmware update and host backed memory support
@ 2026-04-29 8:28 Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
` (6 more replies)
0 siblings, 7 replies; 22+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 8:28 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.
Note: Resending with net-next prefix. No code changes from initial submission.
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] 22+ messages in thread
* [PATCH net-next 1/6] pds_core: add support for quiet devcmd failures
2026-04-29 8:28 [PATCH net-next 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
@ 2026-04-29 8:28 ` Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
` (5 subsequent siblings)
6 siblings, 0 replies; 22+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 8:28 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] 22+ messages in thread
* [PATCH net-next 2/6] pds_core: add support for identity version 2
2026-04-29 8:28 [PATCH net-next 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
@ 2026-04-29 8:28 ` Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
` (4 subsequent siblings)
6 siblings, 0 replies; 22+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 8:28 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] 22+ messages in thread
* [PATCH net-next 3/6] pds_core: add PLDM firmware update support via devlink flash
2026-04-29 8:28 [PATCH net-next 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
@ 2026-04-29 8:28 ` Nikhil P. Rao
2026-05-01 1:05 ` Jakub Kicinski
2026-04-29 8:28 ` [PATCH net-next 4/6] pds_core: add PLDM component info display Nikhil P. Rao
` (3 subsequent siblings)
6 siblings, 1 reply; 22+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 8:28 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] 22+ messages in thread
* [PATCH net-next 4/6] pds_core: add PLDM component info display
2026-04-29 8:28 [PATCH net-next 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
` (2 preceding siblings ...)
2026-04-29 8:28 ` [PATCH net-next 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
@ 2026-04-29 8:28 ` Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
` (2 subsequent siblings)
6 siblings, 0 replies; 22+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 8:28 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] 22+ messages in thread
* [PATCH net-next 5/6] pds_core: add host backed memory support for firmware
2026-04-29 8:28 [PATCH net-next 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
` (3 preceding siblings ...)
2026-04-29 8:28 ` [PATCH net-next 4/6] pds_core: add PLDM component info display Nikhil P. Rao
@ 2026-04-29 8:28 ` Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core Nikhil P. Rao
6 siblings, 0 replies; 22+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 8:28 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] 22+ messages in thread
* [PATCH net-next 6/6] pds_core: add debugfs support for host backed memory
2026-04-29 8:28 [PATCH net-next 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
` (4 preceding siblings ...)
2026-04-29 8:28 ` [PATCH net-next 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
@ 2026-04-29 8:28 ` Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core Nikhil P. Rao
6 siblings, 0 replies; 22+ messages in thread
From: Nikhil P. Rao @ 2026-04-29 8:28 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] 22+ messages in thread
* Re: [PATCH net-next 3/6] pds_core: add PLDM firmware update support via devlink flash
2026-04-29 8:28 ` [PATCH net-next 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
@ 2026-05-01 1:05 ` Jakub Kicinski
2026-05-01 20:03 ` Rao, Nikhil
0 siblings, 1 reply; 22+ messages in thread
From: Jakub Kicinski @ 2026-05-01 1:05 UTC (permalink / raw)
To: Nikhil P. Rao
Cc: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Paolo Abeni, Kees Cook, Gustavo A. R. Silva, netdev, linux-kernel,
linux-hardening, eric.joyner
On Wed, 29 Apr 2026 08:28:19 +0000 Nikhil P. Rao wrote:
> +#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
Please don't redefined kernel-level primitives.
It's a huge pain in the rear to deal with when indexing the code.
This patch also adds a bunch of kdoc warnings.
Last but not least Sashiko points out a number of bugs
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH net-next 3/6] pds_core: add PLDM firmware update support via devlink flash
2026-05-01 1:05 ` Jakub Kicinski
@ 2026-05-01 20:03 ` Rao, Nikhil
0 siblings, 0 replies; 22+ messages in thread
From: Rao, Nikhil @ 2026-05-01 20:03 UTC (permalink / raw)
To: Jakub Kicinski, Nikhil P. Rao
Cc: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Paolo Abeni, Kees Cook, Gustavo A. R. Silva, netdev, linux-kernel,
linux-hardening, eric.joyner
On 4/30/2026 6:05 PM, Jakub Kicinski wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
>
>
> On Wed, 29 Apr 2026 08:28:19 +0000 Nikhil P. Rao wrote:
>> +#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
>
> Please don't redefined kernel-level primitives.
> It's a huge pain in the rear to deal with when indexing the code.
>
> This patch also adds a bunch of kdoc warnings.
>
> Last but not least Sashiko points out a number of bugs
Thanks for the review.
We will fix the issues above in v2 and also use Sashiko to review v2 and
future patches before submission.
Nikhil
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core
2026-04-29 8:28 [PATCH net-next 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
` (5 preceding siblings ...)
2026-04-29 8:28 ` [PATCH net-next 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
@ 2026-05-16 2:42 ` Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
` (5 more replies)
6 siblings, 6 replies; 22+ messages in thread
From: Nikhil P. Rao @ 2026-05-16 2:42 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Eric Joyner, Nikhil P. Rao, 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.
Changes since v1:
- Removed redefinition of __counted_by kernel primitive (Jakub Kicinski)
- Fixed kdoc warnings in pds_core_if.h
- Fixed checkpatch warnings
- Fixed bugs identified by sashiko:
Patch 2 (identity version 2):
- Zero data region before firmware commands
- Suppress expected error message during identify probe
Patch 3 (PLDM firmware update):
- Memory leak in pdsc_send_component_image() error path
- Memory leak in pdsc_flash_component() error path
- Missing devcmd_lock in pdsc_devcmd_finalize_update()
- Fixed dma_mapping_error() return value handling (returns boolean, not error code)
- Skip logic for components with index > 255
Patch 4 (component info):
- Added generic fw version display for all identity versions
- Handle components with both RUNNING and STARTUP flags
Patch 5 (host backed memory):
- Race between pdsc_remove and health thread (use-after-free)
- Set missing index field in MEM_QUERY command
- Host memory allocation size and zeroing
- Don't free host memory on MEM_ADD timeout (firmware may still be using it)
Patch 6 (debugfs):
- Fix dentry reference leak in debugfs_lookup (missing dput)
- Improvements:
- Cache component info to avoid repeated firmware queries (patch 4)
Note: The following fix for an existing bug was submitted separately
via net:
- Timeout error overwritten with stale status:
https://lore.kernel.org/netdev/20260515212907.998028-1-nikhil.rao@amd.com/
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 | 167 +++++++
drivers/net/ethernet/amd/pds_core/core.h | 33 +-
drivers/net/ethernet/amd/pds_core/debugfs.c | 50 ++
drivers/net/ethernet/amd/pds_core/dev.c | 94 +++-
drivers/net/ethernet/amd/pds_core/devlink.c | 90 +++-
drivers/net/ethernet/amd/pds_core/fw.c | 727 +++++++++++++++++++++++++++-
drivers/net/ethernet/amd/pds_core/main.c | 7 +-
include/linux/pds/pds_adminq.h | 132 +++++
include/linux/pds/pds_core_if.h | 379 +++++++++++++++
10 files changed, 1660 insertions(+), 20 deletions(-)
---
base-commit: 543bdc1578cd380631558a884318551a0e9fdab2
change-id: 20260516-upstream_v2_clean-33fdeea55b7a
Best regards,
--
Nikhil P. Rao <nikhil.rao@amd.com>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH net-next v2 1/6] pds_core: add support for quiet devcmd failures
2026-05-16 2:42 ` [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core Nikhil P. Rao
@ 2026-05-16 2:42 ` Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
` (4 subsequent siblings)
5 siblings, 0 replies; 22+ messages in thread
From: Nikhil P. Rao @ 2026-05-16 2:42 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Eric Joyner, Nikhil P. Rao
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] 22+ messages in thread
* [PATCH net-next v2 2/6] pds_core: add support for identity version 2
2026-05-16 2:42 ` [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
@ 2026-05-16 2:42 ` Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
2026-05-16 2:42 ` [PATCH net-next v2 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
` (3 subsequent siblings)
5 siblings, 1 reply; 22+ messages in thread
From: Nikhil P. Rao @ 2026-05-16 2:42 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Eric Joyner, Nikhil P. Rao
From: Brett Creeley <brett.creeley@amd.com>
Add a new capabilities field in struct pds_core_dev_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 | 36 ++++++++++++++++++++++++++-------
include/linux/pds/pds_core_if.h | 4 ++++
2 files changed, 33 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
index 5b86d6cd0ac3..c9abea9b2eb1 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;
@@ -295,10 +298,13 @@ static int pdsc_identify(struct pdsc *pdsc)
*/
mutex_lock(&pdsc->devcmd_lock);
+ /* Zero data region so fields not set by older firmware read as zero */
+ memset_io(&pdsc->cmd_regs->data, 0, sizeof(pdsc->cmd_regs->data));
+
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));
@@ -307,8 +313,9 @@ static int pdsc_identify(struct pdsc *pdsc)
mutex_unlock(&pdsc->devcmd_lock);
if (err) {
- dev_err(pdsc->dev, "Cannot identify device: %pe\n",
- ERR_PTR(err));
+ if (do_msg)
+ dev_err(pdsc->dev, "Cannot identify device: %pe\n",
+ ERR_PTR(err));
return err;
}
@@ -327,6 +334,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] 22+ messages in thread
* [PATCH net-next v2 3/6] pds_core: add PLDM firmware update support via devlink flash
2026-05-16 2:42 ` [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
@ 2026-05-16 2:42 ` Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
2026-05-16 2:42 ` [PATCH net-next v2 4/6] pds_core: add PLDM component info display Nikhil P. Rao
` (2 subsequent siblings)
5 siblings, 1 reply; 22+ messages in thread
From: Nikhil P. Rao @ 2026-05-16 2:42 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Eric Joyner, Nikhil P. Rao
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 | 40 ++
drivers/net/ethernet/amd/pds_core/devlink.c | 2 +-
drivers/net/ethernet/amd/pds_core/fw.c | 727 +++++++++++++++++++++++++++-
include/linux/pds/pds_core_if.h | 373 ++++++++++++++
6 files changed, 1152 insertions(+), 5 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.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 c9abea9b2eb1..bb8c01a131ee 100644
--- a/drivers/net/ethernet/amd/pds_core/dev.c
+++ b/drivers/net/ethernet/amd/pds_core/dev.c
@@ -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..f091a753bce9 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,716 @@ 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);
+ if (dma_mapping_error(dev, dma_addr)) {
+ dev_err(dev,
+ "Failed to dma_map package_data at offset 0x%x copy_sz 0x%x\n",
+ offset, copy_sz);
+ err = -ENOMEM;
+ 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) {
+ err = -ENOENT;
+ goto free_component_priv;
+ }
+
+ if (component->identifier > U8_MAX ||
+ !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);
+ 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);
+ err = -ENOMEM;
+ 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 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(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 {
+ u16 id = component->identifier;
+
+ component_name = pdsc_get_component_priv_name(priv, id);
+ 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);
+ if (dma_mapping_error(pdsc->dev, dma_addr)) {
+ dev_err(dev,
+ "Failed to dma_map component_data at offset 0x%x copy_sz 0x%x\n",
+ offset, copy_sz);
+ err = -ENOMEM;
+ 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 && err != -ETIMEDOUT &&
+ 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);
+ }
+
+ 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 || 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 (!pdsc->fw_components.num_components) {
+ 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);
+
+ /* Invalidate cached component info so next info_get refreshes */
+ pdsc->fw_components.num_components = 0;
+
+ 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/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h
index 619186f26b5b..d15ddd1c8ef1 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,346 @@ struct pds_core_vf_ctrl_comp {
u8 status;
};
+/**
+ * struct pds_core_send_pkg_data_cmd - Send package data command
+ * @opcode: Opcode PDS_CORE_CMD_SEND_PKG_DATA
+ * @ver: Driver's max support version of this command
+ * @total_len: Total length of the package data
+ * @offset: Offset in the package data, non-zero if multiple commands are
+ * needed for sending the package data
+ * @data_len: Length of data stored at data_pa
+ * @data_pa: Data physical address for DMA to device
+ *
+ * The package data may be too large to store in a single buffer, so multiple
+ * PDS_CORE_CMD_SEND_PKG_DATA devcmds may be needed.
+ */
+struct pds_core_send_pkg_data_cmd {
+ u8 opcode;
+ u8 ver;
+ __le16 total_len;
+ __le16 offset;
+ __le16 data_len;
+ __le64 data_pa;
+};
+
+/**
+ * struct pds_core_send_pkg_data_comp - Send package data completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @ver: Device's max supported version of this command
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_send_pkg_data_comp {
+ u8 status;
+ u8 ver;
+ u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_component_tbl - Component table details
+ * @comparison_stamp: Comparison stamp used for component version checks
+ * @classification: Vendor specific classification info
+ * @identifier: Component's ID
+ * @transfer_flag: Part of the component table this request represents
+ * @version_str_type: The types of strings used
+ * @version_str_len: Length of @version_str
+ * @version_str: Component version information
+ */
+struct pds_core_component_tbl {
+ __le32 comparison_stamp;
+ __le16 classification;
+ __le16 identifier;
+ u8 transfer_flag;
+ u8 version_str_type;
+ u8 version_str_len;
+ u8 version_str[];
+};
+
+/**
+ * struct pds_core_send_component_tbl_cmd - Send component table command
+ * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT_TBL
+ * @ver: Driver's max support version of this command
+ * @slot_id: enum pds_core_fw_slot
+ * @rsvd: Word boundary padding
+ *
+ * Expects to find component table info (struct pds_core_component_tbl)
+ * in cmd_regs->data. Driver should keep the devcmd interface locked
+ * while preparing the component table info.
+ */
+struct pds_core_send_component_tbl_cmd {
+ u8 opcode;
+ u8 ver;
+ u8 slot_id;
+ u8 rsvd;
+};
+
+enum pds_core_component_resp_code {
+ PDS_CORE_COMPONENT_VALID = 0x0,
+ PDS_CORE_COMPONENT_STAMP_IDENTICAL = 0x1,
+ PDS_CORE_COMPONENT_STAMP_LOWER = 0x2,
+ PDS_CORE_COMPONENT_STAMP_OR_VERSION_INVALID = 0x3,
+ PDS_CORE_COMPONENT_CONFLICT = 0x4,
+ PDS_CORE_COMPONENT_PREREQS_NOT_MET = 0x5,
+ PDS_CORE_COMPONENT_NOT_SUPPORTED = 0x6,
+ PDS_CORE_COMPONENT_FW_TYPE_INVALID = 0xd0,
+};
+
+/**
+ * struct pds_core_send_component_tbl_comp - Send component table completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @ver: Device's max supported version of this command
+ * @completion_code: Component completion code
+ * @response: Component response
+ * @response_code: Component response code
+ * @slot_id: Actual slot_id of the component (enum pds_core_fw_slot)
+ *
+ * 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 - Send component command
+ * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT
+ * @ver: Driver's max supported version of this command
+ * @slot_id: enum pds_core_fw_slot
+ * @operation: enum pds_core_send_component_op
+ * @offset: Offset into the component, non-zero if multiple commands
+ * are needed for a single component
+ * @data_len: Length of this part of the component stored at @data_pa
+ * @rsvd: Word boundary padding
+ * @data_pa: DMA address of the component
+ *
+ * A component may be too large to store in a single buffer, so multiple
+ * PDS_CORE_CMD_SEND_COMPONENT devcmds may be needed.
+ *
+ * Expects to find flash component info (struct pds_core_flash_component)
+ * in cmd_regs->data. Driver should keep the devcmd interface locked
+ * while preparing and sending the flash component info.
+ */
+struct pds_core_send_component_cmd {
+ u8 opcode;
+ u8 ver;
+ u8 slot_id;
+ u8 operation;
+ __le32 offset;
+ __le32 data_len;
+ u8 rsvd[4];
+ __le64 data_pa;
+};
+
+/**
+ * struct pds_core_send_component_comp - Send component completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @ver: Device's max supported version of this command
+ * @completion_code: Completion code
+ * @compat_response: Compatibility response (0 = Component can be updated)
+ * @compat_response_code: Compatibility response code
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_send_component_comp {
+ u8 status;
+ u8 ver;
+ u8 completion_code;
+ u8 compat_response;
+ u8 compat_response_code;
+ u8 rsvd[3];
+};
+
+/**
+ * enum pds_core_component_info_flags - Component info flags
+ * @PDS_CORE_FW_COMPONENT_INFO_F_RUNNING: Component is currently running
+ * @PDS_CORE_FW_COMPONENT_INFO_F_STARTUP: Component version on next FW boot
+ * @PDS_CORE_FW_COMPONENT_INFO_F_FIXED: Component is fixed and cannot be updated
+ * @PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME: Component can be updated
+ * by name
+ */
+enum pds_core_component_info_flags {
+ PDS_CORE_FW_COMPONENT_INFO_F_RUNNING = BIT(0),
+ PDS_CORE_FW_COMPONENT_INFO_F_STARTUP = BIT(1),
+ PDS_CORE_FW_COMPONENT_INFO_F_FIXED = BIT(2),
+ PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME = BIT(3),
+};
+
+/**
+ * struct pds_core_fw_component_info - GET_COMPONENT_INFO entry
+ * @name: Component's name
+ * @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 - 8) / \
+ sizeof(struct pds_core_fw_component_info))
+
+/**
+ * struct pds_core_component_list_info - GET_COMPONENT_INFO completion data
+ * @num_components: Number of valid components
+ * @rsvd: Word boundary padding
+ * @info: List of valid components
+ */
+struct pds_core_component_list_info {
+ u8 num_components;
+ u8 rsvd[7];
+ struct pds_core_fw_component_info info[PDS_CORE_FW_COMPONENT_LIST_LEN];
+};
+
+/**
+ * struct pds_core_get_component_info_cmd - GET_COMPONENT_INFO command
+ * @opcode: PDS_CORE_CMD_GET_COMPONENT_INFO
+ * @ver: Driver's max supported version of this command
+ * @data_len: Length of data at data_pa
+ * @rsvd: Word boundary padding
+ * @data_pa: DMA address of data
+ *
+ * FW populates struct pds_core_component_list_info pointed to by @data_pa
+ */
+struct pds_core_get_component_info_cmd {
+ u8 opcode;
+ u8 ver;
+ __le16 data_len;
+ u8 rsvd[4];
+ __le64 data_pa;
+};
+
+/**
+ * struct pds_core_get_component_info_comp - GET_COMPONENT_INFO completion
+ * @status: enum pds_core_status_code
+ * @ver: Device's max supported version of this command
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_get_component_info_comp {
+ u8 status;
+ u8 ver;
+ u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_finalize_update_cmd - FINALIZE_UPDATE command
+ * @opcode: PDS_CORE_CMD_FINALIZE_UPDATE
+ * @ver: Driver's max support version of this command
+ * @rsvd: Word boundary padding
+ *
+ * Driver sends at the end of updating all components to finalize the update
+ */
+struct pds_core_finalize_update_cmd {
+ u8 opcode;
+ u8 ver;
+ u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_finalize_update_comp - FINALIZE_UPDATE completion
+ * @status: enum pds_core_status_code
+ * @ver: Device's max supported version of this command
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_finalize_update_comp {
+ u8 status;
+ u8 ver;
+ u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_match_record_desc_cmd - MATCH_RECORD_DESC command
+ * @opcode: PDS_CORE_CMD_MATCH_RECORD_DESC
+ * @ver: Driver's max supported version of this command
+ * @type: PLDM Descriptor Identifier Type
+ * @size: Length of the Descriptor Identifier Value
+ * @rsvd: Word boundary padding
+ *
+ * Expects to find the Descriptor Identifier Data in cmd_regs->data. Driver
+ * should keep the devcmd interface locked while preparing and sending this
+ * command.
+ */
+struct pds_core_match_record_desc_cmd {
+ u8 opcode;
+ u8 ver;
+ __le16 type;
+ __le16 size;
+ u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_match_record_desc_comp - MATCH_RECORD_DESC completion
+ * @status: enum pds_core_status_code
+ * @ver: Device's max supported version of this command
+ * @match: Whether or not the Record Descriptor matches the device
+ * @rsvd: Word boundary padding
+ *
+ * When status is PDS_RC_SUCCESS, then @match is valid, otherwise it's
+ * undefined.
+ */
+struct pds_core_match_record_desc_comp {
+ u8 status;
+ u8 ver;
+ u8 match;
+ u8 rsvd;
+};
+
/*
* union pds_core_dev_cmd - Overlay of core device command structures
*/
@@ -466,6 +825,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 +850,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] 22+ messages in thread
* [PATCH net-next v2 4/6] pds_core: add PLDM component info display
2026-05-16 2:42 ` [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core Nikhil P. Rao
` (2 preceding siblings ...)
2026-05-16 2:42 ` [PATCH net-next v2 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
@ 2026-05-16 2:42 ` Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
2026-05-20 23:47 ` Jakub Kicinski
2026-05-16 2:42 ` [PATCH net-next v2 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
5 siblings, 2 replies; 22+ messages in thread
From: Nikhil P. Rao @ 2026-05-16 2:42 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Eric Joyner, Nikhil P. Rao
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 | 88 ++++++++++++++++++++++++++++-
1 file changed, 86 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c
index 7f44e1a8d4fd..95c3d2531ef1 100644
--- a/drivers/net/ethernet/amd/pds_core/devlink.c
+++ b/drivers/net/ethernet/amd/pds_core/devlink.c
@@ -93,14 +93,78 @@ int pdsc_dl_flash_update(struct devlink *dl,
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;
+ char buf[32];
+
+ ver_type = DEVLINK_INFO_VERSION_TYPE_NONE;
+ snprintf(buf, sizeof(buf), "fw.%s", info->name);
+ 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)
+ return devlink_info_version_fixed_put(req, buf, ver);
+
+ 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;
+ }
+
+ if (flags & PDS_CORE_FW_COMPONENT_INFO_F_STARTUP)
+ return devlink_info_version_stored_put_ext(req, buf,
+ ver, ver_type);
+
+ return 0;
+}
+
+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;
+ int err;
+ int i;
+
+ if (!pdsc->fw_components.num_components) {
+ err = pdsc_get_component_info(pdsc);
+ if (err) {
+ dev_err(pdsc->dev, "Failed to get component_info %pe\n",
+ 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++) {
+ err = pdsc_dl_report_component(req, &list_info->info[i]);
+ 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,6 +196,26 @@ int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,
return err;
}
+ return 0;
+}
+
+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;
+ }
+
err = devlink_info_version_running_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW,
pdsc->dev_info.fw_version);
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH net-next v2 5/6] pds_core: add host backed memory support for firmware
2026-05-16 2:42 ` [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core Nikhil P. Rao
` (3 preceding siblings ...)
2026-05-16 2:42 ` [PATCH net-next v2 4/6] pds_core: add PLDM component info display Nikhil P. Rao
@ 2026-05-16 2:42 ` Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
2026-05-16 2:42 ` [PATCH net-next v2 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
5 siblings, 1 reply; 22+ messages in thread
From: Nikhil P. Rao @ 2026-05-16 2:42 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Eric Joyner, Nikhil P. Rao, 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 | 167 +++++++++++++++++++++++++++++++
drivers/net/ethernet/amd/pds_core/core.h | 19 ++++
drivers/net/ethernet/amd/pds_core/main.c | 5 +-
include/linux/pds/pds_adminq.h | 132 ++++++++++++++++++++++++
include/linux/pds/pds_core_if.h | 2 +
5 files changed, 324 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
index 705cab7b0727..215242430b10 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,168 @@ 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;
+ cmd.mem_query.index = cpu_to_le16(index);
+ 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 = get_order(hm->size);
+ hm->pg = alloc_pages(GFP_KERNEL | __GFP_ZERO, 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);
+ __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.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 %pad\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);
+ 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;
+ }
+}
+
+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 22db78343eb0..58b4d77f6eca 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);
@@ -434,7 +436,8 @@ static void pdsc_remove(struct pci_dev *pdev)
pdsc_sriov_configure(pdev, 0);
pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev);
- timer_shutdown_sync(&pdsc->wdtimer);
+ pdsc_stop_health_thread(pdsc);
+ 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 d15ddd1c8ef1..e8d1b299bbbb 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] 22+ messages in thread
* [PATCH net-next v2 6/6] pds_core: add debugfs support for host backed memory
2026-05-16 2:42 ` [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core Nikhil P. Rao
` (4 preceding siblings ...)
2026-05-16 2:42 ` [PATCH net-next v2 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
@ 2026-05-16 2:42 ` Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
5 siblings, 1 reply; 22+ messages in thread
From: Nikhil P. Rao @ 2026-05-16 2:42 UTC (permalink / raw)
To: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Eric Joyner, Nikhil P. Rao, 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 | 50 +++++++++++++++++++++++++++++
drivers/net/ethernet/amd/pds_core/main.c | 2 ++
2 files changed, 52 insertions(+)
diff --git a/drivers/net/ethernet/amd/pds_core/debugfs.c b/drivers/net/ethernet/amd/pds_core/debugfs.c
index 04c5e3abd8d7..058071f6f17e 100644
--- a/drivers/net/ethernet/amd/pds_core/debugfs.c
+++ b/drivers/net/ethernet/amd/pds_core/debugfs.c
@@ -173,3 +173,53 @@ 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)
+{
+ struct dentry *dentry;
+
+ if (!(pdsc->dev_ident.capabilities &
+ cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM)))
+ return;
+
+ /* Check if file already exists (e.g., during reset recovery) */
+ dentry = debugfs_lookup("host_mem", pdsc->dentry);
+ if (!IS_ERR_OR_NULL(dentry)) {
+ dput(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 58b4d77f6eca..aa7b5ce6f623 100644
--- a/drivers/net/ethernet/amd/pds_core/main.c
+++ b/drivers/net/ethernet/amd/pds_core/main.c
@@ -266,6 +266,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] 22+ messages in thread
* Re: [PATCH net-next v2 2/6] pds_core: add support for identity version 2
2026-05-16 2:42 ` [PATCH net-next v2 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
@ 2026-05-20 23:44 ` Jakub Kicinski
0 siblings, 0 replies; 22+ messages in thread
From: Jakub Kicinski @ 2026-05-20 23:44 UTC (permalink / raw)
To: nikhil.rao
Cc: Jakub Kicinski, brett.creeley, andrew+netdev, davem, edumazet,
pabeni, netdev, linux-kernel, eric.joyner, Vamsi.Atluri
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
pds_core: add support for identity version 2
This patch adds a new capabilities field to struct pds_core_dev_identity
and bumps the identify command version to PDS_CORE_IDENTITY_VERSION_2,
falling back to version 1 when version 2 negotiation fails.
> diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
> index 5b86d6cd0ac3..c9abea9b2eb1 100644
> --- a/drivers/net/ethernet/amd/pds_core/dev.c
> +++ b/drivers/net/ethernet/amd/pds_core/dev.c
[ ... ]
> @@ -327,6 +334,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
> + */
[Low]
The macro names referenced in this comment are PDS_CORE_IDENTIFY_VERSION_1
and PDS_CORE_IDENTIFY_VERSION_2, but the macros defined by this patch in
include/linux/pds/pds_core_if.h are spelled PDS_CORE_IDENTITY_VERSION_1 and
PDS_CORE_IDENTITY_VERSION_2 (IDENTITY, not IDENTIFY).
Should the comment use the actual macro names so that grep finds them?
> + err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_2);
> + if (err)
> + err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_1);
> +
> + return err;
> +}
> +
--
pw-bot: cr
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH net-next v2 3/6] pds_core: add PLDM firmware update support via devlink flash
2026-05-16 2:42 ` [PATCH net-next v2 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
@ 2026-05-20 23:44 ` Jakub Kicinski
0 siblings, 0 replies; 22+ messages in thread
From: Jakub Kicinski @ 2026-05-20 23:44 UTC (permalink / raw)
To: nikhil.rao
Cc: Jakub Kicinski, brett.creeley, andrew+netdev, davem, edumazet,
pabeni, netdev, linux-kernel, eric.joyner, Vamsi.Atluri
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
pds_core: add PLDM firmware update support via devlink flash
This adds PLDM firmware update support to pds_core via the upstream
pldmfw API, allowing flashing of entire packages or individual
components through devlink dev flash.
> diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c
> index fa626719e68d..f091a753bce9 100644
> --- a/drivers/net/ethernet/amd/pds_core/fw.c
> +++ b/drivers/net/ethernet/amd/pds_core/fw.c
[ ... ]
> +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);
> + 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);
> + err = -ENOMEM;
> + 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;
> + }
[Medium]
The comment states version 0 is not supported because of alignment
issues, but the code sets err = 0 and falls through with
pdsc->fw_components.num_components left at zero.
The caller pdsc_pldm_firmware_update() treats err == 0 as success and
calls pldmfw_flash_image() with no cached components, which leads to
a few user-visible side effects:
- When a user passes a specific component, pdsc_get_slot_id() iterates
an empty fw_components array and returns PDS_CORE_FW_SLOT_MAX, which
bubbles up as -ENOENT from pdsc_send_component_table() rather than a
"firmware too old" indication.
- For a full-image flash, pdsc_set_component_name() finds no matches,
so devlink_flash_update_status_notify() ends up reporting empty
component strings throughout the flash.
- pdsc_pldm_firmware_update() resets num_components = 0 at the end of
every flash, so the dev_warn and the same downstream paths recur on
every subsequent attempt.
Should this path return an error such as -EOPNOTSUPP so the documented
intent matches what callers see?
[ ... ]
> +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(struct pldmfw *context,
> + struct pldmfw_component *component)
> +{
[ ... ]
> + 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);
[High]
On the -EAGAIN or -ETIMEDOUT exit from pdsc_devcmd_send_component(),
can the device still be DMA-reading from this buffer when the unmap
and kfree happen here?
The polling loop in pdsc_devcmd_send_component() runs up to 300 seconds
and the only exits when the device is still busy are -EAGAIN (the
status query explicitly says START has not completed) or -ETIMEDOUT.
No abort or cancel opcode is issued, and the wire format documented
for struct pds_core_send_component_cmd hands data_pa to the device on
the START call.
Looking at __pdsc_devcmd_locked() in pds_core/dev.c:
if ((err == -ENXIO || err == -ETIMEDOUT) && pdsc->wq)
queue_work(pdsc->wq, &pdsc->health_work);
-EAGAIN does not even trigger health_work, so the device is not
treated as hung in that case.
Once control returns to pdsc_flash_component():
err = pdsc_devcmd_send_component(...);
dma_unmap_single(dev, dma_addr, copy_sz, DMA_TO_DEVICE);
kfree(component_data);
If the firmware finishes the flash a moment later and reads from
data_pa, on IOMMU systems the unmap can race the DMA and produce
IOMMU faults; on non-IOMMU systems the device can DMA-read from
freed slab memory belonging to a later allocation.
Should this path leak the buffer, reset the device, or send an abort
opcode to the firmware before unmapping and freeing on the busy
timeout exits?
[ ... ]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH net-next v2 4/6] pds_core: add PLDM component info display
2026-05-16 2:42 ` [PATCH net-next v2 4/6] pds_core: add PLDM component info display Nikhil P. Rao
@ 2026-05-20 23:44 ` Jakub Kicinski
2026-05-20 23:47 ` Jakub Kicinski
1 sibling, 0 replies; 22+ messages in thread
From: Jakub Kicinski @ 2026-05-20 23:44 UTC (permalink / raw)
To: nikhil.rao
Cc: Jakub Kicinski, brett.creeley, andrew+netdev, davem, edumazet,
pabeni, netdev, linux-kernel, eric.joyner, Vamsi.Atluri
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
pds_core: add PLDM component info display
This patch extends pdsc_dl_info_get() to report individual PLDM firmware
components (name, version, fixed/running/stored status) via devlink info
when the device identity is version 2 or newer, falling back to the
existing fw_list path otherwise.
> diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c
> index 7f44e1a8d4fd..95c3d2531ef1 100644
> --- a/drivers/net/ethernet/amd/pds_core/devlink.c
> +++ b/drivers/net/ethernet/amd/pds_core/devlink.c
> @@ -93,14 +93,78 @@ int pdsc_dl_flash_update(struct devlink *dl,
> 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;
> + char buf[32];
> +
> + ver_type = DEVLINK_INFO_VERSION_TYPE_NONE;
> + snprintf(buf, sizeof(buf), "fw.%s", info->name);
> + 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)
> + return devlink_info_version_fixed_put(req, buf, ver);
[Medium]
Are the FIXED, RUNNING and STARTUP flags meant to be mutually exclusive?
Looking at enum pds_core_component_info_flags, RUNNING, STARTUP, FIXED
and UPDATE_BY_NAME are independent bits. Returning immediately on
F_FIXED short-circuits the subsequent F_RUNNING and F_STARTUP checks,
so a fixed-but-running component (the natural case for goldfw / ROM
firmware that is currently executing) only surfaces as fixed.
The example in the commit message shows fw.goldfw under "running:":
versions:
running:
fw.goldfw 1.2.3
fw.mainfwa 1.2.4
fw.mainfwb 1.2.3
If goldfw has F_FIXED|F_RUNNING set, would the early return here cause
the running entry for fw.goldfw to be dropped, contradicting the
example output?
> +
> + 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;
> + }
> +
> + if (flags & PDS_CORE_FW_COMPONENT_INFO_F_STARTUP)
> + return devlink_info_version_stored_put_ext(req, buf,
> + ver, ver_type);
> +
> + return 0;
> +}
> +
> +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;
> + int err;
> + int i;
> +
> + if (!pdsc->fw_components.num_components) {
> + err = pdsc_get_component_info(pdsc);
> + if (err) {
> + dev_err(pdsc->dev, "Failed to get component_info %pe\n",
> + ERR_PTR(err));
> + return err;
> + }
> + }
[Medium, Medium]
Two questions about the cache-population logic here.
First, pdsc_get_component_info() handles the ver==0 firmware-incompat
case by emitting a dev_warn and returning 0 without populating
pdsc->fw_components.num_components:
drivers/net/ethernet/amd/pds_core/fw.c:pdsc_get_component_info() {
...
if (comp.get_component_info.ver == 0) {
dev_warn(pdsc->dev,
"Incompatible get_component_info version %u reported by firmware\n",
comp.get_component_info.ver);
err = 0;
goto out;
}
...
}
The cache-miss guard above checks num_components, which stays zero on
that firmware path. Will every devlink dev info invocation then re-run
the full PDS_CORE_CMD_GET_COMPONENT_INFO devcmd (a 4 KB kzalloc, a
dma_map_single/dma_unmap_single, a synchronous devcmd under
devcmd_lock with timeout devcmd_timeout * 2) and emit a fresh dev_warn
each time?
Second, is there a path that invalidates the cache after a successful
flash? pdsc_dl_flash_update() -> pdsc_firmware_update() ->
pdsc_pldm_firmware_update() / pdsc_legacy_firmware_update() does not
appear to clear pdsc->fw_components.num_components, and
pdsc_get_component_info() populates the info[] array in place. After a
flash, will subsequent devlink dev info calls keep showing the
pre-flash version strings until the driver is reloaded?
> +
> + list_info = &pdsc->fw_components;
> + num_components = min_t(u8, list_info->num_components,
> + le16_to_cpu(pdsc->dev_ident.max_fw_slots));
[Medium]
Is the type chosen for min_t() here correct?
max_fw_slots is __le16 in struct pds_core_dev_identity, so the wire
protocol allows values up to 65535, and min_t(u8, ...) casts both
operands to u8 before comparing.
For example, with max_fw_slots = 256, le16_to_cpu() yields 256, the
u8 cast turns it into 0, and the loop reports zero components even
when num_components is non-zero. With max_fw_slots = 257 only one
component would be reported, and so on.
Should this be min_t(u32, ...) (or similar wider type), or even just
list_info->num_components, given that pdsc_get_component_info()
already bounds num_components by PDS_CORE_FW_COMPONENT_LIST_LEN, the
actual size of list_info->info[]? Capping by max_fw_slots also seems
to silently drop valid entries when firmware reports more components
than slots.
> + for (i = 0; i < num_components; i++) {
> + err = pdsc_dl_report_component(req, &list_info->info[i]);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
[ ... ]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH net-next v2 5/6] pds_core: add host backed memory support for firmware
2026-05-16 2:42 ` [PATCH net-next v2 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
@ 2026-05-20 23:44 ` Jakub Kicinski
0 siblings, 0 replies; 22+ messages in thread
From: Jakub Kicinski @ 2026-05-20 23:44 UTC (permalink / raw)
To: nikhil.rao
Cc: Jakub Kicinski, brett.creeley, andrew+netdev, davem, edumazet,
pabeni, netdev, linux-kernel, eric.joyner, Vamsi.Atluri
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
pds_core: add host backed memory support for firmware
This patch adds support for the firmware to use host-backed DMA memory
on AMD/Pensando cards with minimal on-card memory. A new set of adminq
opcodes (MEM_GET_COUNT, MEM_QUERY, MEM_ADD, MEM_DEL) coordinate the
allocation, mapping, and teardown of these regions.
> diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
> index 705cab7b0727..215242430b10 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);
[High]
Can pdsc_host_mem_free() unmap and free pages while firmware DMA is
still in flight?
pdsc_teardown() runs pdsc_devcmd_reset() earlier, but that command can
itself time out when the firmware is hung, and even on success the PCI
device remains bus-master enabled (pci_disable_device() runs much later
in pdsc_remove(), and pdsc_dev_uninit() only frees IRQ vectors).
Unlike the adminq/notifyq descriptor rings, host-mem buffers are used
asynchronously by firmware for control-plane data, so the exposure
window is much wider. If firmware did not actually halt DMA, can it
continue writing into pages that have already been returned to the page
allocator?
Would it be safer to issue MEM_DEL and verify firmware quiescence (for
example via pci_disable_device() or pcie_flr()) before unmapping and
freeing the pages?
>
> 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;
> }
[High]
Does this introduce a self-deadlock on pdsc->wq during firmware
recovery?
pdsc_start() is invoked on the recovery path from
pdsc_health_thread() -> pdsc_fw_up() -> pdsc_setup() -> pdsc_start().
pdsc_health_thread runs on pdsc->wq, which is created via
create_singlethread_workqueue() (max_active=1).
pdsc_host_mem_add() then posts adminq commands via pdsc_adminq_post(),
which calls wait_for_completion_timeout(). The completion is signaled
only by pdsc_work_thread()/pdsc_process_adminq(), which is queued by
pdsc_adminq_isr() onto the same pdsc->wq:
queue_work(pdsc->wq, &qcq->work);
Because the wq's only worker is busy running pdsc_health_thread, the
qcq->work item cannot be dispatched, the completion never fires, and
every adminq command (MEM_GET_COUNT, MEM_QUERY, MEM_ADD) times out
after devcmd_timeout.
On top of that, pdsc_adminq_post() does:
if (err == -ENXIO || err == -ETIMEDOUT)
queue_work(pdsc->wq, &pdsc->health_work);
which re-queues the currently running work on the same wq.
[Medium]
Could ignoring the result of pdsc_host_mem_add() be a problem?
> @@ -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;
> }
pdsc_host_mem_add() returns void and pdsc_start() always returns 0.
Inside pdsc_host_mem_add() the kzalloc_objs() failure path and the
inner add-loop simply return/break:
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;
}
The commit message says the firmware needs this memory for control-plane
components, but the driver provides no mechanism to refuse bring-up or
tell firmware "I gave you fewer regions than you requested". Should the
final count be communicated, or should bring-up fail?
Also, count from MEM_GET_COUNT is an le16 (up to 65535), and only
per-region size is sanity-checked. Should there be an aggregate cap as
well?
> @@ -658,3 +660,168 @@ void pdsc_health_thread(struct work_struct *work)
[ ... ]
> +static int pdsc_host_mem_add_one(struct pdsc *pdsc, int index)
> +{
[ ... ]
> + 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.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 %pad\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;
> + }
[Medium]
Can MEM_DEL be sent twice for the same tag here?
num_host_mem_reqs++ runs after dma_map_page() succeeds but before
MEM_ADD is posted. If MEM_ADD then fails, the err_del label calls
pdsc_host_mem_del_one(pdsc, hm->tag, PDS_RC_ENOMEM) immediately, but
the entry is still counted in num_host_mem_reqs.
Later, pdsc_host_mem_del() iterates by num_host_mem_reqs and calls
pdsc_host_mem_del_one() again with the same tag:
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);
The dma_mapping_error() path correctly avoids this by jumping to
err_del before incrementing num_host_mem_reqs, which makes the two
failure paths inconsistent.
[Low]
Is num_host_mem_reqs intended to track "pages to dma_unmap+free" or
"tags to send MEM_DEL for"?
The comment says:
/* Track this allocation so pdsc_host_mem_free() can clean it up */
pdsc->num_host_mem_reqs++;
but the same counter also drives pdsc_host_mem_del()'s MEM_DEL loop.
Those have different lifetimes: a successfully mapped page must always
be freed, but MEM_DEL should only be sent for entries whose MEM_ADD was
acknowledged. Correctness here depends on the exact placement of the
increment between dma_map_page() success and MEM_ADD posting. Would
per-entry booleans (mapped, added) make the lifecycle clearer and less
fragile to future edits?
> +
> +void pdsc_host_mem_add(struct pdsc *pdsc)
> +{
[ ... ]
> + 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);
[Low]
Does baking PDSC_HOST_MEM_MAX_CONTIG into the firmware ABI cause
problems across kernel builds?
PDSC_HOST_MEM_MAX_CONTIG is defined as:
#define PDSC_HOST_MEM_MAX_CONTIG ((PAGE_SIZE) << (MAX_PAGE_ORDER))
MAX_PAGE_ORDER varies by architecture and CONFIG_ARCH_FORCE_MAX_ORDER,
so the same firmware will see different "maximum contiguous size"
values depending on which kernel build is loaded. The driver then tries
to satisfy each request with bare:
hm->pg = alloc_pages(GFP_KERNEL | __GFP_ZERO, hm->order);
which is unreliable for high-order allocations on a fragmented running
system. pdsc_host_mem_add() runs both at probe and on firmware
recovery, where fragmentation is likely.
Also, PDSC_HOST_MEM_MAX_CONTIG is unsigned long while
mem_get_count.max_contig is __le32. On configurations where
(PAGE_SIZE << MAX_PAGE_ORDER) >= 4 GiB, cpu_to_le32() silently
truncates without any BUILD_BUG_ON guarding it. Would advertising a
fixed conservative size, with a compile-time size check, be more
robust?
> diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c
> index 22db78343eb0..58b4d77f6eca 100644
> --- a/drivers/net/ethernet/amd/pds_core/main.c
> +++ b/drivers/net/ethernet/amd/pds_core/main.c
> @@ -434,7 +436,8 @@ static void pdsc_remove(struct pci_dev *pdev)
> pdsc_sriov_configure(pdev, 0);
> pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev);
>
> - timer_shutdown_sync(&pdsc->wdtimer);
> + pdsc_stop_health_thread(pdsc);
> + pdsc_host_mem_del(pdsc);
> if (pdsc->wq)
> destroy_workqueue(pdsc->wq);
[Medium]
Can health_work be re-queued after it has been cancelled here?
The new ordering is:
pdsc_stop_health_thread(pdsc); /* cancels health_work */
pdsc_host_mem_del(pdsc); /* posts MEM_DEL adminq cmds */
if (pdsc->wq)
destroy_workqueue(pdsc->wq);
pdsc_adminq_post() does:
if (err == -ENXIO || err == -ETIMEDOUT)
queue_work(pdsc->wq, &pdsc->health_work);
If pdsc_host_mem_del()'s MEM_DEL adminq posts time out (firmware
partially hung), health_work is re-queued and then drained by
destroy_workqueue(). At that point PDSC_S_STOPPING_DRIVER is not yet
set (it is set later, inside config_lock, after destroy_workqueue
returns), so pdsc_health_thread() runs the full health check, sees FW
dead, and dispatches pdsc_fw_down() -> pdsc_stop() +
pdsc_teardown(RECOVERY).
Then the main thread proceeds to call pdsc_stop() +
pdsc_teardown(REMOVING) again. Some inner cleanups are idempotent, but
pci_free_irq_vectors() in pdsc_dev_uninit() is not gated by an
idempotency guard.
Would setting PDSC_S_STOPPING_DRIVER before pdsc_host_mem_del(), or
calling cancel_work_sync() once more after pdsc_host_mem_del(), close
this window?
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH net-next v2 6/6] pds_core: add debugfs support for host backed memory
2026-05-16 2:42 ` [PATCH net-next v2 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
@ 2026-05-20 23:44 ` Jakub Kicinski
0 siblings, 0 replies; 22+ messages in thread
From: Jakub Kicinski @ 2026-05-20 23:44 UTC (permalink / raw)
To: nikhil.rao
Cc: Jakub Kicinski, brett.creeley, andrew+netdev, davem, edumazet,
pabeni, netdev, linux-kernel, eric.joyner, Vamsi.Atluri
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
pds_core: add debugfs support for host backed memory
This adds a debugfs file (host_mem) under the pds_core device directory
that displays the tag, size, order, and physical address of each host
memory request tracked in pdsc->host_mem_reqs.
> diff --git a/drivers/net/ethernet/amd/pds_core/debugfs.c b/drivers/net/ethernet/amd/pds_core/debugfs.c
> index 04c5e3abd8d7..058071f6f17e 100644
> --- a/drivers/net/ethernet/amd/pds_core/debugfs.c
> +++ b/drivers/net/ethernet/amd/pds_core/debugfs.c
> @@ -173,3 +173,53 @@ 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;
> + }
[High]
Can host_mem_show() race with pdsc_host_mem_free() and end up
dereferencing freed memory?
The reader takes no lock, but the writers in pdsc_host_mem_add() and
pdsc_host_mem_free() are serialized by pdsc->config_lock and run from
pdsc_fw_down()/pdsc_fw_up() during PCI reset_prepare/reset_done, AER
recovery, and the watchdog-triggered pdsc_health_thread workqueue.
pdsc_teardown() ultimately calls pdsc_host_mem_free(), which kfree()s
host_mem_reqs and sets the pointer to NULL.
The initial check is also TOCTOU:
if (!pdsc->host_mem_reqs || pdsc->num_host_mem_reqs == 0) {
After this passes, host_mem_reqs can be freed and nulled before the
loop body dereferences &pdsc->host_mem_reqs[i].
Could this be addressed by holding pdsc->config_lock around the
iteration, by removing the host_mem dentry from pdsc_fw_down() /
pdsc_teardown() before pdsc_host_mem_free(), or by switching the
array to RCU?
> +
> + 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);
[Low, Low]
hm->tag is u16 and hm->order is u8, but they are printed with the
signed %-6d specifier while hm->size (u32) uses %-12u. Should the
unsigned types use %u here as well?
Also, the canonical format specifier for dma_addr_t is %pad. Could
hm->pa be printed with &hm->pa and %pad rather than cast to unsigned
long long with 0x%llx?
> + }
> +
> + return 0;
> +}
> +DEFINE_SHOW_ATTRIBUTE(host_mem);
> +
> +void pdsc_debugfs_add_host_mem(struct pdsc *pdsc)
> +{
> + struct dentry *dentry;
> +
> + if (!(pdsc->dev_ident.capabilities &
> + cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM)))
> + return;
> +
> + /* Check if file already exists (e.g., during reset recovery) */
> + dentry = debugfs_lookup("host_mem", pdsc->dentry);
> + if (!IS_ERR_OR_NULL(dentry)) {
> + dput(dentry);
> + return;
> + }
> +
> + debugfs_create_file("host_mem", 0400, pdsc->dentry,
> + pdsc, &host_mem_fops);
> +}
[Low]
Is the comment "Check if file already exists (e.g., during reset
recovery)" accurate here?
pdsc_debugfs_add_host_mem() is only called from pdsc_init_pf(), which
is itself only called from pdsc_probe(). The reset and recovery paths
go pdsc_fw_up() -> pdsc_setup() -> pdsc_start() -> pdsc_host_mem_add()
and never re-invoke pdsc_debugfs_add_host_mem().
By contrast, pdsc_debugfs_add_ident() is genuinely called during
recovery via pdsc_dev_init() from pdsc_setup(), so the same idiom
there is meaningful.
Should the dead defensive branch be dropped, or was the intent to
also wire pdsc_debugfs_add_host_mem() into the recovery path so the
file reflects the post-recovery host_mem_reqs array?
> diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c
> index 58b4d77f6eca..aa7b5ce6f623 100644
> --- a/drivers/net/ethernet/amd/pds_core/main.c
> +++ b/drivers/net/ethernet/amd/pds_core/main.c
> @@ -266,6 +266,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;
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH net-next v2 4/6] pds_core: add PLDM component info display
2026-05-16 2:42 ` [PATCH net-next v2 4/6] pds_core: add PLDM component info display Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
@ 2026-05-20 23:47 ` Jakub Kicinski
1 sibling, 0 replies; 22+ messages in thread
From: Jakub Kicinski @ 2026-05-20 23:47 UTC (permalink / raw)
To: Nikhil P. Rao
Cc: Brett Creeley, Andrew Lunn, David S. Miller, Eric Dumazet,
Paolo Abeni, netdev, linux-kernel, Eric Joyner
On Sat, 16 May 2026 02:42:38 +0000 Nikhil P. Rao wrote:
> + ver_type = DEVLINK_INFO_VERSION_TYPE_NONE;
> + snprintf(buf, sizeof(buf), "fw.%s", info->name);
No passthru of names from FW, please, and the FW versions should
be documented in driver doc file under Documentation/.
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-05-20 23:47 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-29 8:28 [PATCH net-next 0/6] pds_core: Add PLDM firmware update and host backed memory support Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
2026-05-01 1:05 ` Jakub Kicinski
2026-05-01 20:03 ` Rao, Nikhil
2026-04-29 8:28 ` [PATCH net-next 4/6] pds_core: add PLDM component info display Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
2026-04-29 8:28 ` [PATCH net-next 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 0/6] PLDM Firmware Update Support for pds_core Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 1/6] pds_core: add support for quiet devcmd failures Nikhil P. Rao
2026-05-16 2:42 ` [PATCH net-next v2 2/6] pds_core: add support for identity version 2 Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
2026-05-16 2:42 ` [PATCH net-next v2 3/6] pds_core: add PLDM firmware update support via devlink flash Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
2026-05-16 2:42 ` [PATCH net-next v2 4/6] pds_core: add PLDM component info display Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
2026-05-20 23:47 ` Jakub Kicinski
2026-05-16 2:42 ` [PATCH net-next v2 5/6] pds_core: add host backed memory support for firmware Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
2026-05-16 2:42 ` [PATCH net-next v2 6/6] pds_core: add debugfs support for host backed memory Nikhil P. Rao
2026-05-20 23:44 ` Jakub Kicinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox