Netdev List
 help / color / mirror / Atom feed
* [PATCH 4/6] pds_core: add PLDM component info display
From: Nikhil P. Rao @ 2026-07-08 21:22 UTC (permalink / raw)
  To: netdev
  Cc: kuba, brett.creeley, eric.joyner, andrew+netdev, davem, edumazet,
	pabeni, jacob.e.keller
In-Reply-To: <20260708212222.296202-1-nikhil.rao@amd.com>

From: Brett Creeley <brett.creeley@amd.com>

Add detailed component information display via devlink info. This
allows users to see individual firmware components and their versions.
Components are reported as fixed, running, or stored based on their
firmware-provided flags.

Example output:
  $ devlink dev info pci/0000:00:05.0
  versions:
    fixed:
      asic.id 0x0
      asic.rev 0x0
    running:
      fw.bootloader 1.2.3
      fw.uboot 1.60.0-73
      fw 1.60.0-73
      fw.cpld 3.18
    stored:
      fw.bootloader 1.2.3
      fw.uboot 1.60.0-73
      fw.uboot.gold 1.50.0-22
      fw.gold 1.50.0-22
      fw 1.60.0-73
      fw.cpld 3.18

Signed-off-by: Brett Creeley <brett.creeley@amd.com>
---
 drivers/net/ethernet/amd/pds_core/core.c    |   2 +
 drivers/net/ethernet/amd/pds_core/core.h    |   1 +
 drivers/net/ethernet/amd/pds_core/devlink.c | 145 +++++++++++++++++++-
 drivers/net/ethernet/amd/pds_core/fw.c      |  12 +-
 4 files changed, 153 insertions(+), 7 deletions(-)

diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
index 6c62ff7a32f0..a7a0bcf98ed3 100644
--- a/drivers/net/ethernet/amd/pds_core/core.c
+++ b/drivers/net/ethernet/amd/pds_core/core.c
@@ -589,6 +589,8 @@ void pdsc_fw_up(struct pdsc *pdsc)
 		return;
 	}
 
+	pdsc_fw_components_invalidate(pdsc);
+
 	err = pdsc_setup(pdsc, PDSC_SETUP_RECOVERY);
 	if (err)
 		goto err_out;
diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h
index c686f0bbbaeb..73356c74bb9f 100644
--- a/drivers/net/ethernet/amd/pds_core/core.h
+++ b/drivers/net/ethernet/amd/pds_core/core.h
@@ -340,6 +340,7 @@ int pdsc_firmware_update(struct pdsc *pdsc,
 			 struct netlink_ext_ack *extack);
 int pdsc_get_component_info(struct pdsc *pdsc);
 const char *pdsc_fw_type_to_name(u8 type);
+void pdsc_fw_components_invalidate(struct pdsc *pdsc);
 
 void pdsc_fw_down(struct pdsc *pdsc);
 void pdsc_fw_up(struct pdsc *pdsc);
diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c
index 3b763ee1715e..63fe45e91f71 100644
--- a/drivers/net/ethernet/amd/pds_core/devlink.c
+++ b/drivers/net/ethernet/amd/pds_core/devlink.c
@@ -93,14 +93,120 @@ 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;
+	const char *name;
+	char buf[32];
+
+	/* Main firmware is reported as generic "fw" */
+	if (info->component_type == PDS_CORE_FW_TYPE_MAIN) {
+		if (info->slot_id == PDS_CORE_FW_SLOT_GOLD)
+			snprintf(buf, sizeof(buf), "fw.gold");
+		else
+			snprintf(buf, sizeof(buf), "fw");
+	} else {
+		name = pdsc_fw_type_to_name(info->component_type);
+		if (!name)
+			return 0;
+
+		if (info->slot_id == PDS_CORE_FW_SLOT_GOLD)
+			snprintf(buf, sizeof(buf), "fw.%s.gold", name);
+		else
+			snprintf(buf, sizeof(buf), "fw.%s", name);
+	}
+
+	ver_type = DEVLINK_INFO_VERSION_TYPE_NONE;
+	if (flags & PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME)
+		ver_type = DEVLINK_INFO_VERSION_TYPE_COMPONENT;
+
+	if (flags & PDS_CORE_FW_COMPONENT_INFO_F_FIXED) {
+		int err;
+
+		err = devlink_info_version_fixed_put(req, buf, ver);
+		if (err)
+			return err;
+	}
+
+	if (flags & PDS_CORE_FW_COMPONENT_INFO_F_RUNNING) {
+		int err;
+
+		err = devlink_info_version_running_put_ext(req, buf,
+							   ver, ver_type);
+		if (err)
+			return err;
+	}
+
+	if (flags & PDS_CORE_FW_COMPONENT_INFO_F_STARTUP) {
+		int err;
+
+		err = devlink_info_version_stored_put_ext(req, buf,
+							  ver, ver_type);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int pdsc_dl_report_fw_ver(struct devlink_info_req *req, char *fw_ver)
+{
+	return devlink_info_version_running_put(req,
+						DEVLINK_INFO_VERSION_GENERIC_FW,
+						fw_ver);
+}
+
+static int pdsc_dl_component_info_get(struct devlink *dl,
+				      struct devlink_info_req *req,
+				      struct netlink_ext_ack *extack)
+{
+	struct pdsc *pdsc = devlink_priv(dl);
+	u8 num_components;
+	int err;
+	int i;
+
+	/* Pairs with WRITE_ONCE in pdsc_fw_components_invalidate().
+	 * Use READ_ONCE to get a consistent snapshot of num_components.
+	 * pdsc_fw_components_invalidate() can zero it concurrently during
+	 * firmware recovery; using the local copy avoids iterating zero
+	 * times when we already decided the cache was valid.
+	 */
+	num_components = READ_ONCE(pdsc->fw_components.num_components);
+	if (!num_components) {
+		err = pdsc_get_component_info(pdsc);
+		if (err)
+			return pdsc_dl_report_fw_ver(req,
+						    pdsc->dev_info.fw_version);
+		num_components = READ_ONCE(pdsc->fw_components.num_components);
+		if (!num_components)
+			return pdsc_dl_report_fw_ver(req,
+						    pdsc->dev_info.fw_version);
+	}
+
+	num_components = min_t(u16, num_components,
+			       le16_to_cpu(pdsc->dev_ident.max_fw_slots));
+	for (i = 0; i < num_components; i++) {
+		err = pdsc_dl_report_component(req,
+					       &pdsc->fw_components.info[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
 static char *fw_slotnames[] = {
 	"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,
@@ -134,12 +240,41 @@ int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,
 			return err;
 	}
 
-	err = devlink_info_version_running_put(req,
-					       DEVLINK_INFO_VERSION_GENERIC_FW,
-					       pdsc->dev_info.fw_version);
+	return 0;
+}
+
+static int pdsc_dl_info_get_v1(struct devlink *dl,
+			       struct devlink_info_req *req,
+			       struct netlink_ext_ack *extack)
+{
+	struct pdsc *pdsc = devlink_priv(dl);
+	int err;
+
+	err = pdsc_dl_fw_list_info_get(dl, req, extack);
 	if (err)
 		return err;
 
+	/* Version 1: report fw from dev_info (running only) */
+	return pdsc_dl_report_fw_ver(req, pdsc->dev_info.fw_version);
+}
+
+int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,
+		     struct netlink_ext_ack *extack)
+{
+	struct pdsc *pdsc = devlink_priv(dl);
+	char buf[32];
+	int err;
+
+	if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2) {
+		err = pdsc_dl_component_info_get(dl, req, extack);
+		if (err)
+			return err;
+	} else {
+		err = pdsc_dl_info_get_v1(dl, req, extack);
+		if (err)
+			return err;
+	}
+
 	snprintf(buf, sizeof(buf), "0x%x", pdsc->dev_info.asic_type);
 	err = devlink_info_version_fixed_put(req,
 					     DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c
index dc793005ec70..ae39f684c3b8 100644
--- a/drivers/net/ethernet/amd/pds_core/fw.c
+++ b/drivers/net/ethernet/amd/pds_core/fw.c
@@ -42,6 +42,12 @@ const char *pdsc_fw_type_to_name(u8 type)
 	return NULL;
 }
 
+void pdsc_fw_components_invalidate(struct pdsc *pdsc)
+{
+	/* Pairs with READ_ONCE in pdsc_dl_component_info_get() */
+	WRITE_ONCE(pdsc->fw_components.num_components, 0);
+}
+
 static u8 pdsc_name_to_fw_type(const char *name)
 {
 	size_t prefix_len;
@@ -765,7 +771,9 @@ static int pdsc_flash_component(struct pldmfw *context,
 	if (component_type) {
 		const char *type_name = pdsc_fw_type_to_name(component_type);
 
-		if (type_name) {
+		if (component_type == PDS_CORE_FW_TYPE_MAIN) {
+			component_name = "fw";
+		} else if (type_name) {
 			snprintf(component_name_buf, sizeof(component_name_buf),
 				 "%s%s", PDSC_FW_COMPONENT_PREFIX, type_name);
 			component_name = component_name_buf;
@@ -966,7 +974,7 @@ int pdsc_firmware_update(struct pdsc *pdsc,
 		err = pdsc_legacy_firmware_update(pdsc, params, extack);
 
 	/* Invalidate cached component info so next info_get refreshes */
-	pdsc->fw_components.num_components = 0;
+	pdsc_fw_components_invalidate(pdsc);
 
 	return err;
 }
-- 
2.43.0


^ permalink raw reply related

* [PATCH 5/6] pds_core: add host backed memory support for firmware
From: Nikhil P. Rao @ 2026-07-08 21:22 UTC (permalink / raw)
  To: netdev
  Cc: kuba, brett.creeley, eric.joyner, andrew+netdev, davem, edumazet,
	pabeni, jacob.e.keller, Nikhil P. Rao, Vamsi Atluri
In-Reply-To: <20260708212222.296202-1-nikhil.rao@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.

Host memory allocation is best-effort: if some allocations fail, the
driver continues with whatever succeeded. Firmware gracefully degrades
when less memory is available than requested.

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 | 160 +++++++++++++++++++++++
 drivers/net/ethernet/amd/pds_core/core.h |  22 ++++
 drivers/net/ethernet/amd/pds_core/main.c |   4 +-
 include/linux/pds/pds_core_if.h          |  64 +++++++++
 4 files changed, 249 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
index a7a0bcf98ed3..356a69c895b6 100644
--- a/drivers/net/ethernet/amd/pds_core/core.c
+++ b/drivers/net/ethernet/amd/pds_core/core.c
@@ -500,6 +500,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);
@@ -509,6 +510,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;
 }
@@ -673,3 +675,161 @@ void pdsc_health_thread(struct work_struct *work)
 out_unlock:
 	mutex_unlock(&pdsc->config_lock);
 }
+
+static void pdsc_host_mem_del_one(struct pdsc *pdsc, u16 tag, u8 reason)
+{
+	union pds_core_dev_comp comp = {};
+	union pds_core_dev_cmd cmd = {
+		.host_mem.opcode = PDS_CORE_CMD_HOST_MEM,
+		.host_mem.oper = PDS_CORE_HOST_MEM_DEL,
+		.host_mem.tag = cpu_to_le16(tag),
+		.host_mem.reason = reason,
+	};
+
+	dev_dbg(pdsc->dev, "Sending devcmd for mem del tag %d\n", tag);
+	pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+}
+
+static int pdsc_host_mem_add_one(struct pdsc *pdsc, int index)
+{
+	struct pdsc_host_mem *hm = &pdsc->host_mem_reqs[index];
+	union pds_core_dev_comp comp = {};
+	union pds_core_dev_cmd cmd = {};
+	int err;
+
+	cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;
+	cmd.host_mem.oper = PDS_CORE_HOST_MEM_QUERY;
+	cmd.host_mem.index = cpu_to_le16(index);
+	dev_dbg(pdsc->dev, "Sending devcmd for mem query index %d\n", index);
+	err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+	if (err || comp.status != PDS_RC_SUCCESS) {
+		dev_err(pdsc->dev, "mem query failed err %d status %d\n",
+			err, comp.status);
+		return err ? err : -EIO;
+	}
+	hm->size = le32_to_cpu(comp.host_mem.size);
+	hm->tag = le16_to_cpu(comp.host_mem.tag);
+	dev_dbg(pdsc->dev, "mem query returned size %d tag %d\n",
+		hm->size, hm->tag);
+
+	if (!hm->size || hm->size > PDSC_HOST_MEM_MAX_CONTIG) {
+		dev_err(pdsc->dev, "invalid size %d for tag %d\n",
+			hm->size, hm->tag);
+		err = -EINVAL;
+		goto err_del;
+	}
+
+	hm->order = get_order(hm->size);
+	hm->pg = alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN, hm->order);
+	if (!hm->pg) {
+		dev_warn(pdsc->dev, "alloc order %d failed for tag %d\n",
+			 hm->order, hm->tag);
+		err = -ENOMEM;
+		goto err_del;
+	}
+
+	hm->pa = dma_map_page(pdsc->dev, hm->pg, 0, hm->size,
+			      DMA_BIDIRECTIONAL);
+	if (dma_mapping_error(pdsc->dev, hm->pa)) {
+		dev_err(pdsc->dev, "dma map failed for tag %d size %d\n",
+			hm->tag, hm->size);
+		__free_pages(hm->pg, hm->order);
+		hm->pg = NULL;
+		err = -EIO;
+		goto err_del;
+	}
+
+	/* Track this allocation so pdsc_host_mem_free() can clean it up */
+	pdsc->num_host_mem_reqs++;
+
+	memset(&cmd, 0, sizeof(cmd));
+	memset(&comp, 0, sizeof(comp));
+	cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;
+	cmd.host_mem.oper = PDS_CORE_HOST_MEM_ADD;
+	cmd.host_mem.tag = cpu_to_le16(hm->tag);
+	cmd.host_mem.size = cpu_to_le32(hm->size);
+	cmd.host_mem.buf_pa = cpu_to_le64(hm->pa);
+
+	dev_dbg(pdsc->dev, "Sending devcmd for mem add tag %d size %d pa %pad\n",
+		hm->tag, hm->size, &hm->pa);
+	err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+	if (err || comp.status != PDS_RC_SUCCESS) {
+		dev_err(pdsc->dev, "mem add failed err %d status %d for tag %d\n",
+			err, comp.status, hm->tag);
+		err = err ? err : -EIO;
+		goto err_del;
+	}
+	dev_dbg(pdsc->dev, "mem add completed for tag %d\n", hm->tag);
+
+	return 0;
+
+err_del:
+	/* After MEM_QUERY succeeds, firmware expects MEM_ADD or MEM_DEL */
+	pdsc_host_mem_del_one(pdsc, hm->tag, PDS_RC_ENOMEM);
+	return err;
+}
+
+void pdsc_host_mem_add(struct pdsc *pdsc)
+{
+	union pds_core_dev_comp comp = {};
+	union pds_core_dev_cmd cmd = {};
+	u16 count;
+	int err;
+	int i;
+
+	if (!(pdsc->dev_ident.capabilities &
+	     cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM)))
+		return;
+
+	cmd.host_mem.opcode = PDS_CORE_CMD_HOST_MEM;
+	cmd.host_mem.oper = PDS_CORE_HOST_MEM_GET_COUNT;
+	cmd.host_mem.index = cpu_to_le16(PDSC_HOST_MEM_MAX_COUNT);
+	cmd.host_mem.max_contig = cpu_to_le32(PDSC_HOST_MEM_MAX_CONTIG);
+	dev_dbg(pdsc->dev, "Sending devcmd for mem get count max_contig %u\n",
+		PDSC_HOST_MEM_MAX_CONTIG);
+	err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+	if (err || comp.status != PDS_RC_SUCCESS) {
+		dev_err(pdsc->dev, "mem get count failed err %d status %d\n",
+			err, comp.status);
+		return;
+	}
+
+	count = min(le16_to_cpu(comp.host_mem.count),
+		    PDSC_HOST_MEM_MAX_COUNT);
+	dev_dbg(pdsc->dev, "mem get count returned count %d\n", count);
+	if (count == 0)
+		return;
+
+	pdsc->host_mem_reqs = kzalloc_objs(*pdsc->host_mem_reqs, count,
+					   GFP_KERNEL);
+	if (!pdsc->host_mem_reqs) {
+		dev_err(pdsc->dev, "failed to alloc host_mem_reqs array\n");
+		return;
+	}
+
+	for (i = 0; i < count; i++) {
+		err = pdsc_host_mem_add_one(pdsc, i);
+		if (err)
+			break;
+	}
+}
+
+void pdsc_host_mem_free(struct pdsc *pdsc)
+{
+	int i;
+
+	if (!pdsc->host_mem_reqs)
+		return;
+
+	for (i = 0; i < pdsc->num_host_mem_reqs; i++) {
+		dma_unmap_page(pdsc->dev, pdsc->host_mem_reqs[i].pa,
+			       pdsc->host_mem_reqs[i].size,
+			       DMA_BIDIRECTIONAL);
+		__free_pages(pdsc->host_mem_reqs[i].pg,
+			     pdsc->host_mem_reqs[i].order);
+	}
+
+	kfree(pdsc->host_mem_reqs);
+	pdsc->host_mem_reqs = NULL;
+	pdsc->num_host_mem_reqs = 0;
+}
diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h
index 73356c74bb9f..085f2e988aa0 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,12 @@
 #define PDSC_SETUP_RECOVERY	false
 #define PDSC_SETUP_INIT		true
 
+/* Use fixed 4MB instead of PAGE_SIZE << MAX_PAGE_ORDER to avoid
+ * cpu_to_le32() truncation on large-page configs
+ */
+#define PDSC_HOST_MEM_MAX_CONTIG (4 * 1024 * 1024)
+#define PDSC_HOST_MEM_MAX_COUNT  256
+
 struct pdsc_deferred_dma {
 	struct list_head list;
 	dma_addr_t dma_addr;
@@ -149,6 +156,14 @@ struct pdsc_viftype {
 	struct pds_auxiliary_dev *padev;
 };
 
+struct pdsc_host_mem {
+	u32 size;
+	u16 tag;
+	u8 order;
+	struct page *pg;
+	dma_addr_t pa;
+};
+
 /* No state flags set means we are in a steady running state */
 enum pdsc_state_flags {
 	PDSC_S_FW_DEAD,		    /* stopped, wait on startup or recovery */
@@ -210,6 +225,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;
 };
 
@@ -287,6 +305,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);
@@ -346,6 +365,9 @@ void pdsc_fw_down(struct pdsc *pdsc);
 void pdsc_fw_up(struct pdsc *pdsc);
 void pdsc_pci_reset_thread(struct work_struct *work);
 
+void pdsc_host_mem_add(struct pdsc *pdsc);
+void pdsc_host_mem_free(struct pdsc *pdsc);
+
 void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry,
 			   dma_addr_t dma_addr, void *va, size_t size,
 			   enum dma_data_direction dir);
diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c
index 17b64177871a..bb619f360f3c 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);
@@ -437,7 +439,7 @@ 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);
 		if (pdsc->wq)
 			destroy_workqueue(pdsc->wq);
 
diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h
index 5a1fafaccf20..901e1e628f89 100644
--- a/include/linux/pds/pds_core_if.h
+++ b/include/linux/pds/pds_core_if.h
@@ -46,6 +46,7 @@ enum pds_core_cmd_opcode {
 	PDS_CORE_CMD_SEND_COMPONENT	= 9,
 	PDS_CORE_CMD_FINALIZE_UPDATE	= 10,
 	PDS_CORE_CMD_MATCH_RECORD_DESC	= 11,
+	PDS_CORE_CMD_HOST_MEM		= 12,
 
 	/* SR/IOV commands */
 	PDS_CORE_CMD_VF_GETATTR		= 60,
@@ -110,9 +111,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
@@ -837,6 +840,65 @@ struct pds_core_match_record_desc_comp {
 	u8 rsvd;
 };
 
+/**
+ * enum pds_core_host_mem_oper - HOST_MEM sub-operations
+ * @PDS_CORE_HOST_MEM_GET_COUNT: Query number of memory requests
+ * @PDS_CORE_HOST_MEM_QUERY:     Query details of a memory request
+ * @PDS_CORE_HOST_MEM_ADD:       Provide allocated memory to firmware
+ * @PDS_CORE_HOST_MEM_DEL:       Notify firmware of memory deallocation
+ */
+enum pds_core_host_mem_oper {
+	PDS_CORE_HOST_MEM_GET_COUNT	= 0,
+	PDS_CORE_HOST_MEM_QUERY		= 1,
+	PDS_CORE_HOST_MEM_ADD		= 2,
+	PDS_CORE_HOST_MEM_DEL		= 3,
+};
+
+/**
+ * struct pds_core_host_mem_cmd - HOST_MEM command
+ * @opcode:     Opcode PDS_CORE_CMD_HOST_MEM
+ * @oper:       Operation (enum pds_core_host_mem_oper)
+ * @index:      Memory request index (GET_COUNT: max_count, QUERY: index)
+ * @tag:        Tag for this memory request (ADD/DEL)
+ * @reason:     Reason for deletion (DEL only)
+ * @rsvd:       Reserved
+ * @max_contig: Maximum contiguous memory size (GET_COUNT only)
+ * @size:       Size of memory in bytes (ADD only)
+ * @buf_pa:     DMA address of memory (ADD only)
+ *
+ * Unified command for all host memory operations. Fields are reused
+ * across operations to minimize opcode space usage.
+ */
+struct pds_core_host_mem_cmd {
+	u8     opcode;
+	u8     oper;
+	__le16 index;
+	__le16 tag;
+	u8     reason;
+	u8     rsvd;
+	__le32 max_contig;
+	__le32 size;
+	__le64 buf_pa;
+};
+
+/**
+ * struct pds_core_host_mem_comp - HOST_MEM completion
+ * @status:       Status of the command (enum pds_core_status_code)
+ * @oper:         Operation that was performed
+ * @count:        Number of memory requests (GET_COUNT)
+ * @size:         Size of memory request in bytes (QUERY)
+ * @tag:          Tag for this memory request (QUERY/DEL)
+ * @rsvd:         Reserved
+ */
+struct pds_core_host_mem_comp {
+	u8     status;
+	u8     oper;
+	__le16 count;
+	__le32 size;
+	__le16 tag;
+	u8     rsvd[6];
+};
+
 /*
  * union pds_core_dev_cmd - Overlay of core device command structures
  */
@@ -860,6 +922,7 @@ union pds_core_dev_cmd {
 	struct pds_core_send_component_cmd     send_component;
 	struct pds_core_finalize_update_cmd    finalize_update;
 	struct pds_core_match_record_desc_cmd  match_record_desc;
+	struct pds_core_host_mem_cmd           host_mem;
 };
 
 /*
@@ -885,6 +948,7 @@ union pds_core_dev_comp {
 	struct pds_core_send_component_comp     send_component;
 	struct pds_core_finalize_update_comp    finalize_update;
 	struct pds_core_match_record_desc_comp  match_record_desc;
+	struct pds_core_host_mem_comp           host_mem;
 };
 
 /**
-- 
2.43.0


^ permalink raw reply related

* [PATCH 3/6] pds_core: add PLDM firmware update support via devlink flash
From: Nikhil P. Rao @ 2026-07-08 21:22 UTC (permalink / raw)
  To: netdev
  Cc: kuba, brett.creeley, eric.joyner, andrew+netdev, davem, edumazet,
	pabeni, jacob.e.keller, Nikhil P . Rao
In-Reply-To: <20260708212222.296202-1-nikhil.rao@amd.com>

From: Brett Creeley <brett.creeley@amd.com>

Implement PLDM FW Update in the pds_core driver using the upstream
pldmfw API. This allows updating an entire PLDM FW package at once
or updating specific firmware components by name.

Flash the entire image:
  devlink dev flash pci/0000:b5:00.0 file firmware.pldmfw

Flash a specific component from the PLDM FW package:
  devlink dev flash pci/0000:b5:00.0 \
    file firmware.pldmfw component fw.cpld

Per-component update uses driver-defined component names (fw, fw.cpld,
etc.). Not all components support per-component update - devlink will
reject the request if the specified component cannot be updated.

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>
---
 .../device_drivers/ethernet/amd/pds_core.rst  |  89 ++
 drivers/net/ethernet/amd/Kconfig              |   1 +
 drivers/net/ethernet/amd/pds_core/core.c      |   9 +
 drivers/net/ethernet/amd/pds_core/core.h      |  30 +-
 drivers/net/ethernet/amd/pds_core/dev.c       |  82 ++
 drivers/net/ethernet/amd/pds_core/devlink.c   |   2 +-
 drivers/net/ethernet/amd/pds_core/fw.c        | 781 +++++++++++++++++-
 drivers/net/ethernet/amd/pds_core/main.c      |   5 +
 include/linux/pds/pds_core_if.h               | 401 +++++++++
 9 files changed, 1395 insertions(+), 5 deletions(-)

diff --git a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst
index 9e8a16c44102..49487a35c163 100644
--- a/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst
+++ b/Documentation/networking/device_drivers/ethernet/amd/pds_core.rst
@@ -102,6 +102,95 @@ currently in use, and that bank will used for the next boot::
   # devlink dev flash pci/0000:b5:00.0 \
             file pensando/dsc_fw_1.63.0-22.tar
 
+Firmware Management (PLDM)
+==========================
+
+Firmware that supports PLDM can be updated using the devlink flash command
+with a PLDM firmware package. The entire package can be updated at once::
+
+  # devlink dev flash pci/0000:b5:00.0 file firmware.pldmfw
+
+Individual components can also be updated by specifying the component name::
+
+  # devlink dev flash pci/0000:b5:00.0 \
+            file firmware.pldmfw component fw.cpld
+
+Per-component update uses driver-defined component names (fw, fw.cpld,
+etc.). Not all components support per-component update -
+devlink will reject the request if the specified component cannot
+be updated.
+
+Gold (recovery) components can be updated by specifying the base component
+name (e.g., ``fw`` for ``fw.gold``) with a goldfw package file when the
+device supports per-component update. The ``.gold`` suffix in devlink info
+output indicates the gold slot version, not a flash target.
+
+Info versions (PLDM)
+====================
+
+Firmware that supports PLDM reports component versions using driver-defined
+names. The driver reports the following component versions:
+
+.. list-table:: devlink info versions for PLDM-capable firmware
+   :widths: 5 5 90
+
+   * - Name
+     - Type
+     - Description
+   * - ``fw``
+     - running, stored
+     - Main firmware
+   * - ``fw.gold``
+     - stored
+     - Gold (recovery) firmware
+   * - ``fw.bootloader``
+     - running, stored
+     - Boot loader
+   * - ``fw.cpld``
+     - running, stored
+     - CPLD
+   * - ``fw.secure``
+     - running, stored
+     - Secure boot firmware
+   * - ``fw.fpga``
+     - running, stored
+     - FPGA configuration
+   * - ``fw.suc``
+     - running, stored
+     - System Unit Controller firmware
+   * - ``fw.suc.bootloader``
+     - running, stored
+     - System Unit Controller bootloader
+   * - ``fw.uboot``
+     - running, stored
+     - U-Boot bootloader
+   * - ``asic.id``
+     - fixed
+     - The ASIC type for this device
+   * - ``asic.rev``
+     - fixed
+     - The revision of the ASIC for this device
+
+Example output::
+
+  $ devlink dev info pci/0000:00:05.0
+  pci/0000:00:05.0:
+    driver pds_core
+    serial_number FLM18420073
+    versions:
+        fixed:
+          asic.id 0x0
+          asic.rev 0x0
+        running:
+          fw.bootloader 1.2.3
+          fw 1.3.0
+          fw.cpld 3.18
+        stored:
+          fw.bootloader 1.2.3
+          fw.gold 1.2.0
+          fw 1.3.0
+          fw.cpld 3.18
+
 Health Reporters
 ================
 
diff --git a/drivers/net/ethernet/amd/Kconfig b/drivers/net/ethernet/amd/Kconfig
index e35991141a1a..743e3d4b6b94 100644
--- a/drivers/net/ethernet/amd/Kconfig
+++ b/drivers/net/ethernet/amd/Kconfig
@@ -171,6 +171,7 @@ config PDS_CORE
 	depends on 64BIT && PCI
 	select AUXILIARY_BUS
 	select NET_DEVLINK
+	select PLDMFW
 	help
 	  This enables the support for the AMD/Pensando Core device family of
 	  adapters.  More specific information on this driver can be
diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
index 38a2446571af..6c62ff7a32f0 100644
--- a/drivers/net/ethernet/amd/pds_core/core.c
+++ b/drivers/net/ethernet/amd/pds_core/core.c
@@ -483,6 +483,15 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing)
 		cancel_work_sync(&pdsc->adminqcq.work);
 
 	pci_clear_master(pdsc->pdev);
+	if (!pdsc->pdev->is_virtfn) {
+		u16 val;
+
+		/* Flush any in-flight DMA before freeing buffers.
+		 * A config read completion cannot return until all prior
+		 * device-initiated memory writes have completed.
+		 */
+		pci_read_config_word(pdsc->pdev, PCI_VENDOR_ID, &val);
+	}
 
 	pdsc_core_uninit(pdsc);
 
diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h
index b7fe9ad73349..c686f0bbbaeb 100644
--- a/drivers/net/ethernet/amd/pds_core/core.h
+++ b/drivers/net/ethernet/amd/pds_core/core.h
@@ -23,6 +23,14 @@
 #define PDSC_SETUP_RECOVERY	false
 #define PDSC_SETUP_INIT		true
 
+struct pdsc_deferred_dma {
+	struct list_head list;
+	dma_addr_t dma_addr;
+	void *va;
+	size_t size;
+	enum dma_data_direction dir;
+};
+
 struct pdsc_dev_bar {
 	void __iomem *vaddr;
 	phys_addr_t bus_addr;
@@ -185,6 +193,8 @@ struct pdsc {
 	struct mutex devcmd_lock;	/* lock for dev_cmd operations */
 	struct mutex config_lock;	/* lock for configuration operations */
 	spinlock_t adminq_lock;		/* lock for adminq operations */
+	struct list_head deferred_dma_list;
+	spinlock_t deferred_dma_lock;	/* lock for deferred DMA list */
 	refcount_t adminq_refcnt;
 	struct pds_core_dev_info_regs __iomem *info_regs;
 	struct pds_core_dev_cmd_regs __iomem *cmd_regs;
@@ -199,6 +209,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 +293,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,11 +335,19 @@ void pdsc_process_adminq(struct pdsc_qcq *qcq);
 void pdsc_work_thread(struct work_struct *work);
 irqreturn_t pdsc_adminq_isr(int irq, void *data);
 
-int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
+int pdsc_firmware_update(struct pdsc *pdsc,
+			 struct devlink_flash_update_params *params,
 			 struct netlink_ext_ack *extack);
+int pdsc_get_component_info(struct pdsc *pdsc);
+const char *pdsc_fw_type_to_name(u8 type);
 
 void pdsc_fw_down(struct pdsc *pdsc);
 void pdsc_fw_up(struct pdsc *pdsc);
 void pdsc_pci_reset_thread(struct work_struct *work);
 
+void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry,
+			   dma_addr_t dma_addr, void *va, size_t size,
+			   enum dma_data_direction dir);
+void pdsc_deferred_dma_free(struct pdsc *pdsc);
+
 #endif /* _PDSC_H_ */
diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
index 84ea502ecb12..b149d29bd256 100644
--- a/drivers/net/ethernet/amd/pds_core/dev.c
+++ b/drivers/net/ethernet/amd/pds_core/dev.c
@@ -206,15 +206,56 @@ static int __pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
 	else
 		memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp));
 
+	if (err != -ETIMEDOUT && err != -EAGAIN)
+		pdsc_deferred_dma_free(pdsc);
+
 	return err;
 }
 
+void pdsc_deferred_dma_add(struct pdsc *pdsc, struct pdsc_deferred_dma *entry,
+			   dma_addr_t dma_addr, void *va, size_t size,
+			   enum dma_data_direction dir)
+{
+	entry->dma_addr = dma_addr;
+	entry->va = va;
+	entry->size = size;
+	entry->dir = dir;
+
+	spin_lock(&pdsc->deferred_dma_lock);
+	list_add_tail(&entry->list, &pdsc->deferred_dma_list);
+	spin_unlock(&pdsc->deferred_dma_lock);
+}
+
+void pdsc_deferred_dma_free(struct pdsc *pdsc)
+{
+	struct pdsc_deferred_dma *entry, *tmp;
+	LIST_HEAD(local_list);
+
+	spin_lock(&pdsc->deferred_dma_lock);
+	list_splice_init(&pdsc->deferred_dma_list, &local_list);
+	spin_unlock(&pdsc->deferred_dma_lock);
+
+	list_for_each_entry_safe(entry, tmp, &local_list, list) {
+		dma_unmap_single(pdsc->dev, entry->dma_addr,
+				 entry->size, entry->dir);
+		kfree(entry->va);
+		list_del(&entry->list);
+		kfree(entry);
+	}
+}
+
 int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
 		       union pds_core_dev_comp *comp, int max_seconds)
 {
 	return __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, true);
 }
 
+int pdsc_devcmd_locked_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+			     union pds_core_dev_comp *comp, int max_seconds)
+{
+	return __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, false);
+}
+
 int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
 		union pds_core_dev_comp *comp, int max_seconds)
 {
@@ -227,6 +268,47 @@ int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
 	return err;
 }
 
+static int __pdsc_devcmd_with_data(struct pdsc *pdsc,
+				   union pds_core_dev_cmd *cmd,
+				   const void *data, size_t data_len,
+				   union pds_core_dev_comp *comp,
+				   int max_seconds, bool do_msg)
+{
+	int err;
+
+	mutex_lock(&pdsc->devcmd_lock);
+	if (!pdsc->cmd_regs) {
+		err = -ENXIO;
+		goto unlock;
+	}
+	if (data_len > sizeof(pdsc->cmd_regs->data)) {
+		err = -ENOSPC;
+		goto unlock;
+	}
+	memcpy_toio(&pdsc->cmd_regs->data, data, data_len);
+	err = __pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds, do_msg);
+unlock:
+	mutex_unlock(&pdsc->devcmd_lock);
+
+	return err;
+}
+
+int pdsc_devcmd_with_data(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+			  const void *data, size_t data_len,
+			  union pds_core_dev_comp *comp, int max_seconds)
+{
+	return __pdsc_devcmd_with_data(pdsc, cmd, data, data_len,
+				       comp, max_seconds, true);
+}
+
+int pdsc_devcmd_with_data_nomsg(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+				const void *data, size_t data_len,
+				union pds_core_dev_comp *comp, int max_seconds)
+{
+	return __pdsc_devcmd_with_data(pdsc, cmd, data, data_len,
+				       comp, max_seconds, false);
+}
+
 int pdsc_devcmd_init(struct pdsc *pdsc)
 {
 	union pds_core_dev_comp comp = {};
diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c
index 2ea97e1c5939..3b763ee1715e 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..dc793005ec70 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,52 @@
 /* Number of periodic log updates during fw file download */
 #define PDSC_FW_INTERVAL_FRACTION	32
 
+#define PDSC_FW_COMPONENT_PREFIX		"fw."
+#define PDSC_FW_COMPONENT_FULL_NAME_BUFLEN \
+	(sizeof(PDSC_FW_COMPONENT_PREFIX) + PDS_CORE_FW_COMPONENT_NAME_BUFLEN)
+
+/* Driver-defined component type to name mapping.
+ * PDS_CORE_FW_TYPE_MAIN is NULL - handled specially as "fw" without prefix.
+ */
+static const char * const pdsc_fw_type_names[] = {
+	[PDS_CORE_FW_TYPE_MAIN]      = NULL,
+	[PDS_CORE_FW_TYPE_BOOT]      = "bootloader",
+	[PDS_CORE_FW_TYPE_CPLD]      = "cpld",
+	[PDS_CORE_FW_TYPE_SECURE]    = "secure",
+	[PDS_CORE_FW_TYPE_FPGA]      = "fpga",
+	[PDS_CORE_FW_TYPE_SUC_MAIN]  = "suc",
+	[PDS_CORE_FW_TYPE_SUC_BOOT]  = "suc.bootloader",
+	[PDS_CORE_FW_TYPE_UBOOT]     = "uboot",
+};
+
+const char *pdsc_fw_type_to_name(u8 type)
+{
+	if (type < ARRAY_SIZE(pdsc_fw_type_names) && pdsc_fw_type_names[type])
+		return pdsc_fw_type_names[type];
+	return NULL;
+}
+
+static u8 pdsc_name_to_fw_type(const char *name)
+{
+	size_t prefix_len;
+	int i;
+
+	/* "fw" without suffix maps to main firmware */
+	if (!strcmp(name, "fw"))
+		return PDS_CORE_FW_TYPE_MAIN;
+
+	prefix_len = str_has_prefix(name, PDSC_FW_COMPONENT_PREFIX);
+	if (prefix_len)
+		name += prefix_len;
+
+	for (i = 1; i < ARRAY_SIZE(pdsc_fw_type_names); i++) {
+		if (pdsc_fw_type_names[i] &&
+		    !strcmp(name, pdsc_fw_type_names[i]))
+			return i;
+	}
+	return 0;
+}
+
 static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr,
 					  u32 offset, u32 length)
 {
@@ -23,7 +72,7 @@ static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr,
 		.fw_download.addr = cpu_to_le64(addr),
 		.fw_download.length = cpu_to_le32(length),
 	};
-	union pds_core_dev_comp comp;
+	union pds_core_dev_comp comp = {};
 
 	return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
 }
@@ -95,9 +144,12 @@ static int pdsc_fw_status_long_wait(struct pdsc *pdsc,
 	return err;
 }
 
-int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
-			 struct netlink_ext_ack *extack)
+static int
+pdsc_legacy_firmware_update(struct pdsc *pdsc,
+			    struct devlink_flash_update_params *params,
+			    struct netlink_ext_ack *extack)
 {
+	const struct firmware *fw = params->fw;
 	u32 buf_sz, copy_sz, offset;
 	struct devlink *dl;
 	int next_interval;
@@ -105,6 +157,12 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
 	int err = 0;
 	int fw_slot;
 
+	if (params->component) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "Component update not supported by this device");
+		return -EOPNOTSUPP;
+	}
+
 	dev_info(pdsc->dev, "Installing firmware\n");
 
 	if (!pdsc->cmd_regs)
@@ -195,3 +253,720 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
 						   NULL, 0, 0);
 	return err;
 }
+
+struct pdsc_component_priv {
+	u16 component_id;
+	bool skip;
+	struct list_head list_entry;
+};
+
+struct pds_core_fwu_priv {
+	struct pldmfw context;
+	struct devlink_flash_update_params *params;
+	struct netlink_ext_ack *extack;
+	struct pdsc *pdsc;
+	struct list_head components;
+};
+
+static void pdsc_free_fwu_priv(struct pds_core_fwu_priv *priv)
+{
+	struct pdsc_component_priv *component_priv, *tmp;
+
+	list_for_each_entry_safe(component_priv, tmp, &priv->components,
+				 list_entry) {
+		list_del(&component_priv->list_entry);
+		kfree(component_priv);
+	}
+}
+
+static int pdsc_devcmd_match_record_desc(struct pdsc *pdsc, u16 desc_type,
+					 u16 desc_size, const u8 *desc_data,
+					 u8 *match)
+{
+	union pds_core_dev_cmd cmd = {
+		.match_record_desc.opcode = PDS_CORE_CMD_MATCH_RECORD_DESC,
+		.match_record_desc.ver = 1,
+		.match_record_desc.type = cpu_to_le16(desc_type),
+		.match_record_desc.size = cpu_to_le16(desc_size),
+	};
+	union pds_core_dev_comp comp = {};
+	int err;
+
+	err = pdsc_devcmd_with_data(pdsc, &cmd, desc_data, desc_size,
+				    &comp, pdsc->devcmd_timeout);
+	*match = comp.match_record_desc.match;
+
+	return err;
+}
+
+static bool pdsc_match_record_descs(struct pldmfw *context,
+				    struct pldmfw_record *record)
+{
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	struct pdsc *pdsc = priv->pdsc;
+	struct pldmfw_desc_tlv *desc;
+
+	if (!pldmfw_op_pci_match_record(context, record))
+		return false;
+
+	list_for_each_entry(desc, &record->descs, entry) {
+		u8 match;
+		int err;
+
+		switch (desc->type) {
+		/* skip types checked in pldmfw_op_pci_match_record */
+		case PLDM_DESC_ID_PCI_VENDOR_ID:
+		case PLDM_DESC_ID_PCI_DEVICE_ID:
+		case PLDM_DESC_ID_PCI_SUBVENDOR_ID:
+		case PLDM_DESC_ID_PCI_SUBDEV_ID:
+			continue;
+		}
+
+		if (!desc->size)
+			return false;
+
+		err = pdsc_devcmd_match_record_desc(pdsc, desc->type,
+						    desc->size, desc->data,
+						    &match);
+		if (err) {
+			dev_err(pdsc->dev,
+				"match_record_desc failed type: 0x%04x size: %u, err %d\n",
+				desc->type, desc->size, err);
+			return false;
+		}
+		/* all record descriptors must match */
+		if (!match)
+			return false;
+	}
+
+	return true;
+}
+
+static int pdsc_devcmd_send_package_data(struct pdsc *pdsc, u64 addr,
+					 u16 length, u16 offset, u16 total_len)
+{
+	union pds_core_dev_cmd cmd = {
+		.send_pkg_data.opcode = PDS_CORE_CMD_SEND_PKG_DATA,
+		.send_pkg_data.ver = 1,
+		.send_pkg_data.data_pa = cpu_to_le64(addr),
+		.send_pkg_data.data_len = cpu_to_le16(length),
+		.send_pkg_data.offset = cpu_to_le16(offset),
+		.send_pkg_data.total_len = cpu_to_le16(total_len),
+	};
+	union pds_core_dev_comp comp = {};
+
+	return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+}
+
+static int pdsc_send_package_data(struct pldmfw *context, const u8 *data,
+				  u16 length)
+{
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	struct pdsc_deferred_dma *deferred;
+	struct device *dev = context->dev;
+	struct pdsc *pdsc = priv->pdsc;
+	dma_addr_t dma_addr;
+	u8 *package_data;
+	u32 offset;
+	int err;
+
+	if (!length)
+		return 0;
+
+	deferred = kmalloc_obj(*deferred, GFP_KERNEL);
+	if (!deferred)
+		return -ENOMEM;
+
+	package_data = kmemdup(data, length, GFP_KERNEL);
+	if (!package_data) {
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	dma_addr = dma_map_single(dev, package_data, length, DMA_TO_DEVICE);
+	if (dma_mapping_error(dev, dma_addr)) {
+		dev_err(dev, "Failed to dma_map package_data length 0x%x\n",
+			length);
+		kfree(package_data);
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	for (offset = 0; offset < length; offset += PDS_PAGE_SIZE) {
+		u32 copy_sz;
+
+		copy_sz = min_t(unsigned int, PDS_PAGE_SIZE, length - offset);
+		err = pdsc_devcmd_send_package_data(pdsc, dma_addr + offset,
+						    copy_sz, offset, length);
+		if (err) {
+			NL_SET_ERR_MSG_MOD(priv->extack,
+					   "Failed to send package data");
+			break;
+		}
+	}
+
+	if (err == -ETIMEDOUT || err == -EAGAIN) {
+		pdsc_deferred_dma_add(pdsc, deferred, dma_addr,
+				      package_data, length, DMA_TO_DEVICE);
+		return err;
+	}
+
+	kfree(deferred);
+	dma_unmap_single(dev, dma_addr, length, DMA_TO_DEVICE);
+	kfree(package_data);
+	return err;
+}
+
+static bool pdsc_component_type_exists(struct pdsc *pdsc, u8 type)
+{
+	int i;
+
+	for (i = 0; i < pdsc->fw_components.num_components; i++) {
+		if (pdsc->fw_components.info[i].component_type == type)
+			return true;
+	}
+	return false;
+}
+
+static u8 pdsc_get_component_type_by_id(struct pdsc *pdsc, u16 component_id)
+{
+	int i;
+
+	for (i = 0; i < pdsc->fw_components.num_components; i++) {
+		struct pds_core_fw_component_info *info =
+			&pdsc->fw_components.info[i];
+
+		if (info->identifier == component_id)
+			return info->component_type;
+	}
+	return 0;
+}
+
+static bool pdsc_component_id_matches_type(struct pdsc *pdsc,
+					   u8 component_id, u8 type)
+{
+	int i;
+
+	for (i = 0; i < pdsc->fw_components.num_components; i++) {
+		struct pds_core_fw_component_info *info =
+			&pdsc->fw_components.info[i];
+
+		if (info->identifier == component_id &&
+		    info->component_type == type)
+			return true;
+	}
+	return false;
+}
+
+static bool pdsc_skip_component(struct pds_core_fwu_priv *priv,
+				u16 component_id)
+{
+	struct pdsc_component_priv *component_priv;
+
+	list_for_each_entry(component_priv, &priv->components, list_entry) {
+		if (component_priv->component_id == component_id)
+			return component_priv->skip;
+	}
+
+	return false;
+}
+
+static int pdsc_send_component_table(struct pldmfw *context,
+				     struct pldmfw_component *component,
+				     u8 transfer_flag)
+{
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	struct pds_core_component_tbl *component_tbl;
+	struct pdsc_component_priv *component_priv;
+	struct device *dev = context->dev;
+	union pds_core_dev_comp comp = {};
+	union pds_core_dev_cmd cmd = {};
+	struct pdsc *pdsc = priv->pdsc;
+	bool skip_component = false;
+	u8 requested_type = 0;
+	u16 buf_sz, tbl_sz;
+	int err = 0;
+
+	dev_dbg(dev,
+		"component name %s classification %u id %u activation_method %u ver_len %d ver_str %.*s index %u size %u transfer_flag 0x%02x\n",
+		priv->params->component, component->classification,
+		component->identifier, component->activation_method,
+		component->version_len, component->version_len,
+		component->version_string, component->index,
+		component->component_size, transfer_flag);
+
+	component_priv = kzalloc_obj(*component_priv, GFP_KERNEL);
+	if (!component_priv)
+		return -ENOMEM;
+
+	if (priv->params->component) {
+		requested_type = pdsc_name_to_fw_type(priv->params->component);
+		if (component->identifier > U8_MAX ||
+		    !pdsc_component_id_matches_type(pdsc,
+						    component->identifier,
+						    requested_type)) {
+			skip_component = true;
+			goto add_component_priv;
+		}
+	}
+
+	buf_sz = sizeof(pdsc->cmd_regs->data);
+	tbl_sz = struct_size(component_tbl, version_str,
+			     component->version_len);
+	if (tbl_sz > buf_sz) {
+		dev_err(dev, "component_tbl size %d too big, max size: %d\n",
+			tbl_sz, buf_sz);
+		err = -ENOSPC;
+		goto free_component_priv;
+	}
+	component_tbl = kzalloc(tbl_sz, GFP_KERNEL);
+	if (!component_tbl) {
+		err = -ENOMEM;
+		goto free_component_priv;
+	}
+
+	component_tbl->comparison_stamp =
+		cpu_to_le32(component->comparison_stamp);
+	component_tbl->classification = cpu_to_le16(component->classification);
+	component_tbl->identifier = cpu_to_le16(component->identifier);
+	component_tbl->transfer_flag = transfer_flag;
+	component_tbl->version_str_type = component->version_type;
+	component_tbl->version_str_len = component->version_len;
+	memcpy(component_tbl->version_str, component->version_string,
+	       component->version_len);
+
+	cmd.send_component_tbl.opcode = PDS_CORE_CMD_SEND_COMPONENT_TBL;
+	cmd.send_component_tbl.ver = 1;
+	cmd.send_component_tbl.slot_id = PDS_CORE_FW_SLOT_INVALID;
+
+	err = pdsc_devcmd_with_data(pdsc, &cmd, component_tbl, tbl_sz,
+				    &comp, pdsc->devcmd_timeout);
+	kfree(component_tbl);
+	if (err) {
+		dev_err(dev, "Failed sending component table: %pe\n",
+			ERR_PTR(err));
+		goto free_component_priv;
+	}
+
+	if (comp.send_component_tbl.response == 1 &&
+	    comp.send_component_tbl.response_code ==
+		PDS_CORE_COMPONENT_PREREQS_NOT_MET)
+		skip_component = true;
+
+add_component_priv:
+	component_priv->skip = skip_component;
+	component_priv->component_id = component->identifier;
+	list_add(&component_priv->list_entry, &priv->components);
+
+	return 0;
+
+free_component_priv:
+	kfree(component_priv);
+	return err;
+}
+
+int pdsc_get_component_info(struct pdsc *pdsc)
+{
+	union pds_core_dev_cmd cmd = {
+		.get_component_info.opcode = PDS_CORE_CMD_GET_COMPONENT_INFO,
+		.get_component_info.ver = 1,
+	};
+	struct pds_core_component_list_info *list_info;
+	struct pdsc_deferred_dma *deferred;
+	union pds_core_dev_comp comp = {};
+	dma_addr_t dma_addr;
+	u8 num_components;
+	int err, i;
+
+	deferred = kmalloc_obj(*deferred);
+	if (!deferred)
+		return -ENOMEM;
+
+	list_info = kzalloc(PDS_PAGE_SIZE, GFP_KERNEL);
+	if (!list_info) {
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	dma_addr = dma_map_single(pdsc->dev, list_info, PDS_PAGE_SIZE,
+				  DMA_FROM_DEVICE);
+	if (dma_mapping_error(pdsc->dev, dma_addr)) {
+		dev_err(pdsc->dev,
+			"Failed to dma_map component_list_info length %d\n",
+			PDS_PAGE_SIZE);
+		kfree(list_info);
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	cmd.get_component_info.data_len = cpu_to_le16(PDS_PAGE_SIZE);
+	cmd.get_component_info.data_pa = cpu_to_le64(dma_addr);
+
+	err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout * 2);
+	if (err == -ETIMEDOUT || err == -EAGAIN) {
+		pdsc_deferred_dma_add(pdsc, deferred, dma_addr, list_info,
+				      PDS_PAGE_SIZE, DMA_FROM_DEVICE);
+		return err;
+	}
+
+	kfree(deferred);
+	dma_unmap_single(pdsc->dev, dma_addr, PDS_PAGE_SIZE, DMA_FROM_DEVICE);
+	if (err)
+		goto out;
+
+	if (comp.get_component_info.ver == 0) {
+		/* Don't support backward compatibility as version 0 has
+		 * alignment issues, so give a hint to users to update
+		 * their firmware
+		 */
+		dev_warn_once(pdsc->dev,
+			      "Incompatible get_component_info version %u reported by firmware\n",
+			      comp.get_component_info.ver);
+		err = 0;
+		goto out;
+	}
+
+	num_components = list_info->num_components;
+	if (num_components > PDS_CORE_FW_COMPONENT_LIST_LEN) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	pdsc->fw_components.num_components = num_components;
+	for (i = 0; i < num_components; i++) {
+		struct pds_core_fw_component_info *info =
+			&pdsc->fw_components.info[i];
+
+		memcpy(info, &list_info->info[i], sizeof(*info));
+		info->version[PDS_CORE_FW_COMPONENT_VER_BUFLEN - 1] = 0;
+		info->name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN - 1] = 0;
+	}
+
+out:
+	kfree(list_info);
+	return err;
+}
+
+static int pdsc_devcmd_send_component(struct pdsc *pdsc,
+				      struct pds_core_flash_component *info,
+				      u16 info_sz, dma_addr_t addr, u32 length,
+				      u32 offset, u16 slot_id,
+				      union pds_core_dev_comp *comp)
+{
+	union pds_core_dev_cmd cmd = {
+		.send_component.opcode = PDS_CORE_CMD_SEND_COMPONENT,
+		.send_component.ver = 1,
+		.send_component.operation = PDS_CORE_SEND_COMPONENT_START,
+		.send_component.data_pa = cpu_to_le64(addr),
+		.send_component.data_len = cpu_to_le32(length),
+		.send_component.offset = cpu_to_le32(offset),
+		.send_component.slot_id = slot_id,
+	};
+	unsigned long timeout = 300 * HZ;
+	unsigned long start_time;
+	unsigned long end_time;
+	int err;
+
+	start_time = jiffies;
+	end_time = start_time + timeout;
+	do {
+		/* prevent noisy/benign devcmd failures */
+		err = pdsc_devcmd_with_data_nomsg(pdsc, &cmd, info, info_sz,
+						  comp, 60);
+		if (err != -EAGAIN)
+			break;
+
+		/* if required, subsequent commands check status of
+		 * PDS_CORE_CMD_SEND_COMPONENT command, which returns
+		 * EAGAIN while the command is still running,
+		 * else we get the final command status.
+		 */
+		cmd.send_component.operation = PDS_CORE_SEND_COMPONENT_STATUS;
+		msleep(20);
+	} while (time_before(jiffies, end_time));
+
+	if (err == -EAGAIN || err == -ETIMEDOUT)
+		dev_err(pdsc->dev, "PDS_CORE_CMD_SEND_COMPONENT timed out\n");
+
+	return err;
+}
+
+static int pdsc_flash_component_chunk(struct pdsc *pdsc, struct device *dev,
+				      struct pds_core_flash_component *info,
+				      u16 info_sz, const u8 *data, u16 copy_sz,
+				      u32 offset, u8 slot_id,
+				      union pds_core_dev_comp *comp)
+{
+	struct pdsc_deferred_dma *deferred;
+	dma_addr_t dma_addr;
+	u8 *component_data;
+	int err;
+
+	deferred = kmalloc_obj(*deferred, GFP_KERNEL);
+	if (!deferred)
+		return -ENOMEM;
+
+	component_data = kmemdup(data, copy_sz, GFP_KERNEL);
+	if (!component_data) {
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	dma_addr = dma_map_single(dev, component_data, copy_sz, DMA_TO_DEVICE);
+	if (dma_mapping_error(dev, dma_addr)) {
+		dev_err(dev,
+			"Failed to dma_map component_data at offset 0x%x copy_sz 0x%x\n",
+			offset, copy_sz);
+		kfree(component_data);
+		kfree(deferred);
+		return -ENOMEM;
+	}
+
+	err = pdsc_devcmd_send_component(pdsc, info, info_sz, dma_addr,
+					 copy_sz, offset, slot_id, comp);
+	if (err == -ETIMEDOUT || err == -EAGAIN) {
+		pdsc_deferred_dma_add(pdsc, deferred, dma_addr,
+				      component_data, copy_sz, DMA_TO_DEVICE);
+		return err;
+	}
+
+	kfree(deferred);
+	dma_unmap_single(dev, dma_addr, copy_sz, DMA_TO_DEVICE);
+	kfree(component_data);
+
+	return err;
+}
+
+static int pdsc_flash_component(struct pldmfw *context,
+				struct pldmfw_component *component)
+{
+	char component_name_buf[PDSC_FW_COMPONENT_FULL_NAME_BUFLEN];
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	struct pds_core_flash_component *component_info;
+	const char *component_name = NULL;
+	struct device *dev = context->dev;
+	struct pdsc *pdsc = priv->pdsc;
+	u16 buf_sz, info_sz;
+	struct devlink *dl;
+	u8 component_type;
+	u32 total_len;
+	u32 offset;
+	int err;
+
+	if (pdsc_skip_component(priv, component->identifier))
+		return 0;
+
+	component_type = pdsc_get_component_type_by_id(pdsc,
+						       component->identifier);
+	if (component_type) {
+		const char *type_name = pdsc_fw_type_to_name(component_type);
+
+		if (type_name) {
+			snprintf(component_name_buf, sizeof(component_name_buf),
+				 "%s%s", PDSC_FW_COMPONENT_PREFIX, type_name);
+			component_name = component_name_buf;
+		}
+	}
+
+	total_len = component->component_size;
+	dev_dbg(dev,
+		"component name %s class %u id %u act_meth %u ver_str %.*s index %u size %u\n",
+		component_name ?: "(unknown)", component->classification,
+		component->identifier, component->activation_method,
+		component->version_len, component->version_string,
+		component->index, component->component_size);
+
+	buf_sz = sizeof(pdsc->cmd_regs->data);
+	info_sz = struct_size(component_info, version_str,
+			      component->version_len);
+	if (info_sz > buf_sz) {
+		dev_err(dev, "component_info size %d too big, max size: %d\n",
+			info_sz, buf_sz);
+		return -ENOSPC;
+	}
+	component_info = vzalloc(info_sz);
+	if (!component_info)
+		return -ENOMEM;
+
+	component_info->comparison_stamp =
+		cpu_to_le32(component->comparison_stamp);
+	component_info->image_size = cpu_to_le32(total_len);
+	component_info->classification = cpu_to_le16(component->classification);
+	component_info->identifier = cpu_to_le16(component->identifier);
+	component_info->options = cpu_to_le16(component->options);
+	component_info->version_str_type = component->version_type;
+	component_info->version_str_len = component->version_len;
+	memcpy(component_info->version_str, component->version_string,
+	       component->version_len);
+
+	dl = priv_to_devlink(pdsc);
+
+	offset = 0;
+	while (offset < total_len) {
+		union pds_core_dev_comp comp = {};
+		u16 copy_sz;
+
+		copy_sz = min_t(unsigned int, PDS_PAGE_SIZE,
+				total_len - offset);
+
+		err = pdsc_flash_component_chunk(pdsc, dev, component_info,
+						 info_sz,
+						 component->component_data +
+						 offset, copy_sz, offset,
+						 PDS_CORE_FW_SLOT_INVALID,
+						 &comp);
+		if (err &&
+		    comp.send_component.compat_response &&
+		    (comp.send_component.compat_response_code ==
+		     PDS_CORE_COMPONENT_STAMP_IDENTICAL ||
+		     comp.send_component.compat_response_code ==
+		     PDS_CORE_COMPONENT_STAMP_LOWER)) {
+			err = 0;
+			devlink_flash_update_status_notify(dl, "Skipped",
+							   component_name,
+							   0, 0);
+			goto skip_component;
+		}
+
+		if (err) {
+			NL_SET_ERR_MSG_MOD(priv->extack,
+					   "Failed to flash component");
+			goto err_out;
+		}
+
+		offset += copy_sz;
+		devlink_flash_update_status_notify(dl,
+						   "Erasing/Flashing",
+						   component_name, offset,
+						   total_len);
+	}
+
+	vfree(component_info);
+	return 0;
+
+err_out:
+	devlink_flash_update_status_notify(dl,
+					   "Erasing/Flashing Component Failed",
+					   component_name, 0, 0);
+skip_component:
+	vfree(component_info);
+	return err;
+}
+
+static int pdsc_devcmd_finalize_update(struct pdsc *pdsc)
+{
+	union pds_core_dev_cmd cmd = {
+		.finalize_update.opcode = PDS_CORE_CMD_FINALIZE_UPDATE,
+		.finalize_update.ver = 1,
+	};
+	union pds_core_dev_comp comp = {};
+
+	return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
+}
+
+static int pdsc_finalize_update(struct pldmfw *context)
+{
+	struct pds_core_fwu_priv *priv =
+		container_of(context, struct pds_core_fwu_priv, context);
+	const char *component_name = priv->params->component;
+	unsigned long start_time, end_time;
+	struct device *dev = context->dev;
+	struct pdsc *pdsc = priv->pdsc;
+	struct devlink *dl;
+	int err;
+
+	dl = priv_to_devlink(pdsc);
+
+	start_time = jiffies;
+	end_time = start_time + (PDSC_FW_INSTALL_TIMEOUT * HZ);
+	do {
+		err = pdsc_devcmd_finalize_update(pdsc);
+		if (err != -EAGAIN)
+			break;
+
+		dev_dbg(dev, "retrying finalize_update: %pe\n", ERR_PTR(err));
+		msleep(20);
+	} while (time_before(jiffies, end_time) && err == -EAGAIN);
+
+	if (err) {
+		devlink_flash_update_status_notify(dl, "Finalize Update Failed",
+						   component_name, 0, 0);
+		NL_SET_ERR_MSG_MOD(priv->extack, "Finalize update failed");
+		return err;
+	}
+
+	devlink_flash_update_status_notify(dl, "Finalized Update",
+					   component_name, 0, 0);
+	return 0;
+}
+
+static const struct pldmfw_ops pdsc_pldmfw_ops = {
+	.match_record = pdsc_match_record_descs,
+	.send_package_data = pdsc_send_package_data,
+	.send_component_table = pdsc_send_component_table,
+	.flash_component = pdsc_flash_component,
+	.finalize_update = pdsc_finalize_update
+};
+
+static int pdsc_pldm_firmware_update(struct pdsc *pdsc,
+				     struct devlink_flash_update_params *params,
+				     struct netlink_ext_ack *extack,
+				     const struct firmware *fw)
+{
+	struct pds_core_fwu_priv priv = {};
+	int err;
+
+	if (!pdsc->fw_components.num_components) {
+		err = pdsc_get_component_info(pdsc);
+		if (err) {
+			NL_SET_ERR_MSG_MOD(extack,
+					   "Failed to get component info");
+			return err;
+		}
+	}
+
+	if (params->component) {
+		u8 type = pdsc_name_to_fw_type(params->component);
+
+		if (!type || !pdsc_component_type_exists(pdsc, type)) {
+			NL_SET_ERR_MSG_MOD(extack, "Unknown component name");
+			return -ENOENT;
+		}
+	}
+
+	INIT_LIST_HEAD(&priv.components);
+	priv.context.ops = &pdsc_pldmfw_ops;
+	priv.context.dev = pdsc->dev;
+	priv.params = params;
+	priv.extack = extack;
+	priv.pdsc = pdsc;
+
+	err = pldmfw_flash_image(&priv.context, fw);
+	pdsc_free_fwu_priv(&priv);
+
+	return err;
+}
+
+int pdsc_firmware_update(struct pdsc *pdsc,
+			 struct devlink_flash_update_params *params,
+			 struct netlink_ext_ack *extack)
+{
+	int err;
+
+	if (pdsc->dev_ident.version >= PDS_CORE_IDENTITY_VERSION_2 &&
+	    pdsc->dev_ident.capabilities &
+		cpu_to_le64(PDS_CORE_DEV_CAP_PLDM_FW_UPDATE))
+		err = pdsc_pldm_firmware_update(pdsc, params, extack,
+						params->fw);
+	else
+		err = pdsc_legacy_firmware_update(pdsc, params, extack);
+
+	/* Invalidate cached component info so next info_get refreshes */
+	pdsc->fw_components.num_components = 0;
+
+	return err;
+}
diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c
index 22db78343eb0..17b64177871a 100644
--- a/drivers/net/ethernet/amd/pds_core/main.c
+++ b/drivers/net/ethernet/amd/pds_core/main.c
@@ -246,6 +246,8 @@ static int pdsc_init_pf(struct pdsc *pdsc)
 	mutex_init(&pdsc->devcmd_lock);
 	mutex_init(&pdsc->config_lock);
 	spin_lock_init(&pdsc->adminq_lock);
+	INIT_LIST_HEAD(&pdsc->deferred_dma_list);
+	spin_lock_init(&pdsc->deferred_dma_lock);
 
 	mutex_lock(&pdsc->config_lock);
 	set_bit(PDSC_S_FW_DEAD, &pdsc->state);
@@ -311,6 +313,7 @@ static int pdsc_init_pf(struct pdsc *pdsc)
 		destroy_workqueue(pdsc->wq);
 	mutex_destroy(&pdsc->config_lock);
 	mutex_destroy(&pdsc->devcmd_lock);
+	pdsc_deferred_dma_free(pdsc);
 	pci_free_irq_vectors(pdsc->pdev);
 	pdsc_unmap_bars(pdsc);
 err_out_release_regions:
@@ -452,6 +455,7 @@ static void pdsc_remove(struct pci_dev *pdev)
 	}
 
 	pci_disable_device(pdev);
+	pdsc_deferred_dma_free(pdsc);
 
 	ida_free(&pdsc_ida, pdsc->uid);
 	pdsc_debugfs_del_dev(pdsc);
@@ -499,6 +503,7 @@ static void pdsc_reset_prepare(struct pci_dev *pdev)
 	pci_release_regions(pdev);
 	if (pci_is_enabled(pdev))
 		pci_disable_device(pdev);
+	pdsc_deferred_dma_free(pdsc);
 }
 
 static void pdsc_reset_done(struct pci_dev *pdev)
diff --git a/include/linux/pds/pds_core_if.h b/include/linux/pds/pds_core_if.h
index 619186f26b5b..5a1fafaccf20 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,9 @@ struct pds_core_drv_identity {
  *		      value in usecs to device units using:
  *		      device units = usecs * mult / div
  * @vif_types:        How many of each VIF device type is supported
+ * @max_fw_slots:     Number of firmware components reported by device
+ *		      only supported on version >= PDS_CORE_IDENTITY_VERSION_2
+ * @rsvd2:	      Word boundary padding
  * @capabilities:     Device capabilities
  *		      only supported on version >= PDS_CORE_IDENTITY_VERSION_2
  */
@@ -133,6 +151,8 @@ struct pds_core_dev_identity {
 	__le32 intr_coal_mult;
 	__le32 intr_coal_div;
 	__le16 vif_types[PDS_DEV_TYPE_MAX];
+	__le16 max_fw_slots;
+	u8     rsvd2[6];
 	__le64 capabilities;
 };
 
@@ -279,11 +299,20 @@ enum pds_core_fw_control_oper {
 	PDS_CORE_FW_GET_LIST               = 7,
 };
 
+/**
+ * enum pds_core_fw_slot - Firmware slot identifiers
+ * @PDS_CORE_FW_SLOT_INVALID: Let firmware select slot based on package metadata
+ * @PDS_CORE_FW_SLOT_A:       Primary firmware slot A
+ * @PDS_CORE_FW_SLOT_B:       Primary firmware slot B
+ * @PDS_CORE_FW_SLOT_GOLD:    Gold/recovery firmware slot
+ * @PDS_CORE_FW_SLOT_MAX:     Sentinel value indicating no slot resolved
+ */
 enum pds_core_fw_slot {
 	PDS_CORE_FW_SLOT_INVALID    = 0,
 	PDS_CORE_FW_SLOT_A	    = 1,
 	PDS_CORE_FW_SLOT_B          = 2,
 	PDS_CORE_FW_SLOT_GOLD       = 3,
+	PDS_CORE_FW_SLOT_MAX        = 0xff,
 };
 
 /**
@@ -450,6 +479,364 @@ struct pds_core_vf_ctrl_comp {
 	u8	status;
 };
 
+/**
+ * struct pds_core_send_pkg_data_cmd - Send package data command
+ * @opcode: Opcode PDS_CORE_CMD_SEND_PKG_DATA
+ * @ver: Driver's max support version of this command
+ * @total_len: Total length of the package data
+ * @offset: Offset in the package data, non-zero if multiple commands are
+ *	    needed for sending the package data
+ * @data_len: Length of data stored at data_pa
+ * @data_pa: Data physical address for DMA to device
+ *
+ * The package data may be too large to store in a single buffer, so multiple
+ * PDS_CORE_CMD_SEND_PKG_DATA devcmds may be needed.
+ */
+struct pds_core_send_pkg_data_cmd {
+	u8 opcode;
+	u8 ver;
+	__le16 total_len;
+	__le16 offset;
+	__le16 data_len;
+	__le64 data_pa;
+};
+
+/**
+ * struct pds_core_send_pkg_data_comp - Send package data completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @ver: Device's max supported version of this command
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_send_pkg_data_comp {
+	u8 status;
+	u8 ver;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_component_tbl - Component table details
+ * @comparison_stamp: Comparison stamp used for component version checks
+ * @classification: Vendor specific classification info
+ * @identifier: Component's ID
+ * @transfer_flag: Part of the component table this request represents
+ * @version_str_type: The types of strings used
+ * @version_str_len: Length of @version_str
+ * @version_str: Component version information
+ */
+struct pds_core_component_tbl {
+	__le32 comparison_stamp;
+	__le16 classification;
+	__le16 identifier;
+	u8     transfer_flag;
+	u8     version_str_type;
+	u8     version_str_len;
+	u8     version_str[];
+};
+
+/**
+ * struct pds_core_send_component_tbl_cmd - Send component table command
+ * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT_TBL
+ * @ver: Driver's max support version of this command
+ * @slot_id: enum pds_core_fw_slot
+ * @rsvd: Word boundary padding
+ *
+ * Expects to find component table info (struct pds_core_component_tbl)
+ * in cmd_regs->data.  Driver should keep the devcmd interface locked
+ * while preparing the component table info.
+ */
+struct pds_core_send_component_tbl_cmd {
+	u8 opcode;
+	u8 ver;
+	u8 slot_id;
+	u8 rsvd;
+};
+
+enum pds_core_component_resp_code {
+	PDS_CORE_COMPONENT_VALID = 0x0,
+	PDS_CORE_COMPONENT_STAMP_IDENTICAL = 0x1,
+	PDS_CORE_COMPONENT_STAMP_LOWER = 0x2,
+	PDS_CORE_COMPONENT_STAMP_OR_VERSION_INVALID = 0x3,
+	PDS_CORE_COMPONENT_CONFLICT = 0x4,
+	PDS_CORE_COMPONENT_PREREQS_NOT_MET = 0x5,
+	PDS_CORE_COMPONENT_NOT_SUPPORTED = 0x6,
+	PDS_CORE_COMPONENT_FW_TYPE_INVALID = 0xd0,
+};
+
+/**
+ * struct pds_core_send_component_tbl_comp - Send component table completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @ver: Device's max supported version of this command
+ * @completion_code: Component completion code
+ * @response: Component response
+ * @response_code: Component response code
+ * @slot_id: Actual slot_id of the component (enum pds_core_fw_slot)
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_send_component_tbl_comp {
+	u8 status;
+	u8 ver;
+	u8 completion_code;
+	u8 response;
+	u8 response_code;
+	u8 slot_id;
+	u8 rsvd[2];
+};
+
+/**
+ * enum pds_core_send_component_op - PDS_CORE_CMD_SEND_COMPONENT operation
+ * @PDS_CORE_SEND_COMPONENT_START: Initial operation to start transfer
+ * @PDS_CORE_SEND_COMPONENT_STATUS: Subsequent calls to check on status
+ */
+enum pds_core_send_component_op {
+	PDS_CORE_SEND_COMPONENT_START = 0,
+	PDS_CORE_SEND_COMPONENT_STATUS = 1,
+};
+
+#define PDS_CORE_FW_COMPONENT_ID_INVALID 0xFFFF
+/**
+ * struct pds_core_flash_component - Component details
+ * @comparison_stamp: Comparison stamp used for component version checks
+ * @image_size: Component image size
+ * @classification: Vendor specific classification info
+ * @identifier: Component's ID
+ * @options: Component options
+ * @rsvd: Word boundary padding
+ * @version_str_type: The types of strings used
+ * @version_str_len: Length of @version_str
+ * @version_str: Component version information
+ */
+struct pds_core_flash_component {
+	__le32 comparison_stamp;
+	__le32 image_size;
+	__le16 classification;
+	__le16 identifier;
+	__le16 options;
+	u8 rsvd[3];
+	u8 version_str_type;
+	u8 version_str_len;
+	u8 version_str[];
+};
+
+/**
+ * struct pds_core_send_component_cmd - Send component command
+ * @opcode: Opcode PDS_CORE_CMD_SEND_COMPONENT
+ * @ver: Driver's max supported version of this command
+ * @slot_id: enum pds_core_fw_slot
+ * @operation: enum pds_core_send_component_op
+ * @offset: Offset into the component, non-zero if multiple commands
+ *	    are needed for a single component
+ * @data_len: Length of this part of the component stored at @data_pa
+ * @rsvd: Word boundary padding
+ * @data_pa: DMA address of the component
+ *
+ * A component may be too large to store in a single buffer, so multiple
+ * PDS_CORE_CMD_SEND_COMPONENT devcmds may be needed.
+ *
+ * Expects to find flash component info (struct pds_core_flash_component)
+ * in cmd_regs->data. Driver should keep the devcmd interface locked
+ * while preparing and sending the flash component info.
+ */
+struct pds_core_send_component_cmd {
+	u8 opcode;
+	u8 ver;
+	u8 slot_id;
+	u8 operation;
+	__le32 offset;
+	__le32 data_len;
+	u8 rsvd[4];
+	__le64 data_pa;
+};
+
+/**
+ * struct pds_core_send_component_comp - Send component completion
+ * @status: Status of the command (enum pds_core_status_code)
+ * @ver: Device's max supported version of this command
+ * @completion_code: Completion code
+ * @compat_response: Compatibility response (0 = Component can be updated)
+ * @compat_response_code: Compatibility response code
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_send_component_comp {
+	u8 status;
+	u8 ver;
+	u8 completion_code;
+	u8 compat_response;
+	u8 compat_response_code;
+	u8 rsvd[3];
+};
+
+/**
+ * enum pds_core_fw_component_type - Firmware component type
+ * @PDS_CORE_FW_TYPE_UNKNOWN: Unknown component type
+ * @PDS_CORE_FW_TYPE_MAIN: Main firmware
+ * @PDS_CORE_FW_TYPE_BOOT: Boot loader
+ * @PDS_CORE_FW_TYPE_CPLD: CPLD firmware
+ * @PDS_CORE_FW_TYPE_SECURE: Secure firmware
+ * @PDS_CORE_FW_TYPE_FPGA: FPGA configuration
+ * @PDS_CORE_FW_TYPE_SUC_MAIN: System Unit Controller firmware
+ * @PDS_CORE_FW_TYPE_SUC_BOOT: System Unit Controller bootloader
+ * @PDS_CORE_FW_TYPE_UBOOT: U-Boot bootloader
+ *
+ * Gold/recovery variants are identified by slot_id == PDS_CORE_FW_SLOT_GOLD
+ * and reported with a ".gold" suffix (e.g., fw.gold).
+ */
+enum pds_core_fw_component_type {
+	PDS_CORE_FW_TYPE_UNKNOWN   = 0,
+	PDS_CORE_FW_TYPE_MAIN      = 1,
+	PDS_CORE_FW_TYPE_BOOT      = 2,
+	PDS_CORE_FW_TYPE_CPLD      = 3,
+	PDS_CORE_FW_TYPE_SECURE    = 4,
+	PDS_CORE_FW_TYPE_FPGA      = 5,
+	PDS_CORE_FW_TYPE_SUC_MAIN  = 6,
+	PDS_CORE_FW_TYPE_SUC_BOOT  = 7,
+	PDS_CORE_FW_TYPE_UBOOT     = 8,
+};
+
+/**
+ * enum pds_core_component_info_flags - Component info flags
+ * @PDS_CORE_FW_COMPONENT_INFO_F_RUNNING: Component is currently running
+ * @PDS_CORE_FW_COMPONENT_INFO_F_STARTUP: Component version on next FW boot
+ * @PDS_CORE_FW_COMPONENT_INFO_F_FIXED: Component is fixed and cannot be updated
+ * @PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME: Component can be updated
+ *	by name
+ */
+enum pds_core_component_info_flags {
+	PDS_CORE_FW_COMPONENT_INFO_F_RUNNING = BIT(0),
+	PDS_CORE_FW_COMPONENT_INFO_F_STARTUP = BIT(1),
+	PDS_CORE_FW_COMPONENT_INFO_F_FIXED = BIT(2),
+	PDS_CORE_FW_COMPONENT_INFO_F_UPDATE_BY_NAME = BIT(3),
+};
+
+/**
+ * struct pds_core_fw_component_info - GET_COMPONENT_INFO entry
+ * @name: Component's name
+ * @component_type: enum pds_core_fw_component_type
+ * @rsvd: Word boundary padding
+ * @flags: enum pds_core_component_info_flags
+ * @identifier: Component's identifier
+ * @slot_id: Component's slot identifier
+ * @version: Component's version
+ */
+struct pds_core_fw_component_info {
+#define PDS_CORE_FW_COMPONENT_NAME_BUFLEN 24
+	char name[PDS_CORE_FW_COMPONENT_NAME_BUFLEN];
+	u8 component_type;
+	u8 rsvd[3];
+	__le16 flags;
+	u8 identifier;
+	u8 slot_id;
+#define PDS_CORE_FW_COMPONENT_VER_BUFLEN 32
+	char version[PDS_CORE_FW_COMPONENT_VER_BUFLEN];
+};
+
+#define PDS_CORE_FW_COMPONENT_LIST_LEN	((PDS_PAGE_SIZE - 8) / \
+		sizeof(struct pds_core_fw_component_info))
+
+/**
+ * struct pds_core_component_list_info - GET_COMPONENT_INFO completion data
+ * @num_components: Number of valid components
+ * @rsvd: Word boundary padding
+ * @info: List of valid components
+ */
+struct pds_core_component_list_info {
+	u8 num_components;
+	u8 rsvd[7];
+	struct pds_core_fw_component_info info[PDS_CORE_FW_COMPONENT_LIST_LEN];
+};
+
+/**
+ * struct pds_core_get_component_info_cmd - GET_COMPONENT_INFO command
+ * @opcode: PDS_CORE_CMD_GET_COMPONENT_INFO
+ * @ver: Driver's max supported version of this command
+ * @data_len: Length of data at data_pa
+ * @rsvd: Word boundary padding
+ * @data_pa: DMA address of data
+ *
+ * FW populates struct pds_core_component_list_info pointed to by @data_pa
+ */
+struct pds_core_get_component_info_cmd {
+	u8 opcode;
+	u8 ver;
+	__le16 data_len;
+	u8 rsvd[4];
+	__le64 data_pa;
+};
+
+/**
+ * struct pds_core_get_component_info_comp - GET_COMPONENT_INFO completion
+ * @status: enum pds_core_status_code
+ * @ver: Device's max supported version of this command
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_get_component_info_comp {
+	u8 status;
+	u8 ver;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_finalize_update_cmd - FINALIZE_UPDATE command
+ * @opcode: PDS_CORE_CMD_FINALIZE_UPDATE
+ * @ver: Driver's max support version of this command
+ * @rsvd: Word boundary padding
+ *
+ * Driver sends at the end of updating all components to finalize the update
+ */
+struct pds_core_finalize_update_cmd {
+	u8 opcode;
+	u8 ver;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_finalize_update_comp - FINALIZE_UPDATE completion
+ * @status: enum pds_core_status_code
+ * @ver: Device's max supported version of this command
+ * @rsvd: Word boundary padding
+ */
+struct pds_core_finalize_update_comp {
+	u8 status;
+	u8 ver;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_match_record_desc_cmd - MATCH_RECORD_DESC command
+ * @opcode: PDS_CORE_CMD_MATCH_RECORD_DESC
+ * @ver: Driver's max supported version of this command
+ * @type: PLDM Descriptor Identifier Type
+ * @size: Length of the Descriptor Identifier Value
+ * @rsvd: Word boundary padding
+ *
+ * Expects to find the Descriptor Identifier Data in cmd_regs->data. Driver
+ * should keep the devcmd interface locked while preparing and sending this
+ * command.
+ */
+struct pds_core_match_record_desc_cmd {
+	u8 opcode;
+	u8 ver;
+	__le16 type;
+	__le16 size;
+	u8 rsvd[2];
+};
+
+/**
+ * struct pds_core_match_record_desc_comp - MATCH_RECORD_DESC completion
+ * @status: enum pds_core_status_code
+ * @ver: Device's max supported version of this command
+ * @match: Whether or not the Record Descriptor matches the device
+ * @rsvd: Word boundary padding
+ *
+ * When status is PDS_RC_SUCCESS, then @match is valid, otherwise it's
+ * undefined.
+ */
+struct pds_core_match_record_desc_comp {
+	u8 status;
+	u8 ver;
+	u8 match;
+	u8 rsvd;
+};
+
 /*
  * union pds_core_dev_cmd - Overlay of core device command structures
  */
@@ -466,6 +853,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 +878,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

* [PATCH 6/6] pds_core: add debugfs support for host backed memory
From: Nikhil P. Rao @ 2026-07-08 21:22 UTC (permalink / raw)
  To: netdev
  Cc: kuba, brett.creeley, eric.joyner, andrew+netdev, davem, edumazet,
	pabeni, jacob.e.keller, Nikhil P. Rao, Vamsi Atluri
In-Reply-To: <20260708212222.296202-1-nikhil.rao@amd.com>

Add debugfs entry to dump host backed memory allocations for debug
purposes.

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    |  2 +
 drivers/net/ethernet/amd/pds_core/core.h    |  1 +
 drivers/net/ethernet/amd/pds_core/debugfs.c | 45 +++++++++++++++++++++
 3 files changed, 48 insertions(+)

diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
index 356a69c895b6..d4857bf4860d 100644
--- a/drivers/net/ethernet/amd/pds_core/core.c
+++ b/drivers/net/ethernet/amd/pds_core/core.c
@@ -500,6 +500,7 @@ void pdsc_teardown(struct pdsc *pdsc, bool removing)
 		pdsc->viftype_status = NULL;
 	}
 
+	pdsc_debugfs_del_host_mem(pdsc);
 	pdsc_host_mem_free(pdsc);
 	pdsc_dev_uninit(pdsc);
 
@@ -511,6 +512,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);
+	pdsc_debugfs_add_host_mem(pdsc);
 
 	return 0;
 }
diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h
index 085f2e988aa0..791756a50870 100644
--- a/drivers/net/ethernet/amd/pds_core/core.h
+++ b/drivers/net/ethernet/amd/pds_core/core.h
@@ -306,6 +306,7 @@ void pdsc_debugfs_add_irqs(struct pdsc *pdsc);
 void pdsc_debugfs_add_qcq(struct pdsc *pdsc, struct pdsc_qcq *qcq);
 void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq);
 void pdsc_debugfs_add_host_mem(struct pdsc *pdsc);
+void pdsc_debugfs_del_host_mem(struct pdsc *pdsc);
 
 int pdsc_err_to_errno(enum pds_core_status_code code);
 bool pdsc_is_fw_running(struct pdsc *pdsc);
diff --git a/drivers/net/ethernet/amd/pds_core/debugfs.c b/drivers/net/ethernet/amd/pds_core/debugfs.c
index 810a0cd9bcac..ef0a1b7d159b 100644
--- a/drivers/net/ethernet/amd/pds_core/debugfs.c
+++ b/drivers/net/ethernet/amd/pds_core/debugfs.c
@@ -178,3 +178,48 @@ void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq)
 	debugfs_remove_recursive(qcq->dentry);
 	qcq->dentry = NULL;
 }
+
+static int host_mem_show(struct seq_file *seq, void *v)
+{
+	struct pdsc *pdsc = seq->private;
+	struct pdsc_host_mem *hm;
+	int i;
+
+	if (!pdsc->host_mem_reqs || pdsc->num_host_mem_reqs == 0) {
+		seq_puts(seq, "No host memory allocated\n");
+		return 0;
+	}
+
+	seq_printf(seq, "Host memory requests: %u\n\n",
+		   pdsc->num_host_mem_reqs);
+	seq_puts(seq, "Tag    Size         Order  PA\n");
+	seq_puts(seq, "---    ----         -----  --\n");
+
+	for (i = 0; i < pdsc->num_host_mem_reqs; i++) {
+		hm = &pdsc->host_mem_reqs[i];
+
+		if (!hm->pg)
+			continue;
+
+		seq_printf(seq, "%-6u %-12u %-6u %pad\n",
+			   hm->tag, hm->size, hm->order, &hm->pa);
+	}
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(host_mem);
+
+void pdsc_debugfs_add_host_mem(struct pdsc *pdsc)
+{
+	if (!(pdsc->dev_ident.capabilities &
+	     cpu_to_le64(PDS_CORE_DEV_CAP_HOST_MEM)))
+		return;
+
+	debugfs_create_file("host_mem", 0400, pdsc->dentry,
+			    pdsc, &host_mem_fops);
+}
+
+void pdsc_debugfs_del_host_mem(struct pdsc *pdsc)
+{
+	debugfs_lookup_and_remove("host_mem", pdsc->dentry);
+}
-- 
2.43.0


^ permalink raw reply related

* [PATCH 1/6] pds_core: add support for quiet devcmd failures
From: Nikhil P. Rao @ 2026-07-08 21:22 UTC (permalink / raw)
  To: netdev
  Cc: kuba, brett.creeley, eric.joyner, andrew+netdev, davem, edumazet,
	pabeni, jacob.e.keller
In-Reply-To: <20260708212222.296202-1-nikhil.rao@amd.com>

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 bded6b33289c..dd9989cfe6b3 100644
--- a/drivers/net/ethernet/amd/pds_core/dev.c
+++ b/drivers/net/ethernet/amd/pds_core/dev.c
@@ -126,7 +126,8 @@ static const char *pdsc_devcmd_str(int opcode)
 	}
 }
 
-static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)
+static int __pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds,
+			      const bool do_msg)
 {
 	struct device *dev = pdsc->dev;
 	unsigned long start_time;
@@ -179,7 +180,7 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)
 
 	status = pdsc_devcmd_status(pdsc);
 	err = pdsc_err_to_errno(status);
-	if (err && err != -EAGAIN)
+	if (do_msg && err && err != -EAGAIN)
 		dev_err(dev, "DEVCMD %d %s failed, status=%d err %d %pe\n",
 			opcode, pdsc_devcmd_str(opcode), status, err,
 			ERR_PTR(err));
@@ -187,8 +188,9 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)
 	return err;
 }
 
-int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
-		       union pds_core_dev_comp *comp, int max_seconds)
+static int __pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
+				union pds_core_dev_comp *comp, int max_seconds,
+				const bool do_msg)
 {
 	int err;
 
@@ -197,7 +199,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);
@@ -207,6 +209,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

* [PATCH 2/6] pds_core: add support for identity version 2
From: Nikhil P. Rao @ 2026-07-08 21:22 UTC (permalink / raw)
  To: netdev
  Cc: kuba, brett.creeley, eric.joyner, andrew+netdev, davem, edumazet,
	pabeni, jacob.e.keller
In-Reply-To: <20260708212222.296202-1-nikhil.rao@amd.com>

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 | 40 ++++++++++++++++++++-----
 include/linux/pds/pds_core_if.h         |  4 +++
 2 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
index dd9989cfe6b3..84ea502ecb12 100644
--- a/drivers/net/ethernet/amd/pds_core/dev.c
+++ b/drivers/net/ethernet/amd/pds_core/dev.c
@@ -250,15 +250,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)
@@ -281,8 +283,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;
@@ -305,17 +308,24 @@ static int pdsc_identify(struct pdsc *pdsc)
 	sz = min_t(size_t, sizeof(drv), sizeof(pdsc->cmd_regs->data));
 	memcpy_toio(&pdsc->cmd_regs->data, &drv, sz);
 
-	err = pdsc_devcmd_identify_locked(pdsc);
+	err = pdsc_devcmd_identify_locked(pdsc, drv_ident_ver, do_msg);
 	if (!err) {
 		sz = min_t(size_t, sizeof(pdsc->dev_ident),
 			   sizeof(pdsc->cmd_regs->data));
 		memcpy_fromio(&pdsc->dev_ident, &pdsc->cmd_regs->data, sz);
+
+		/* V1 firmware doesn't set capabilities, so the field may
+		 * contain garbage from the outgoing driver identity.
+		 */
+		if (pdsc->dev_ident.version < PDS_CORE_IDENTITY_VERSION_2)
+			pdsc->dev_ident.capabilities = 0;
 	}
 	mutex_unlock(&pdsc->devcmd_lock);
 
 	if (err) {
-		dev_err(pdsc->dev, "Cannot identify device: %pe\n",
-			ERR_PTR(err));
+		if (do_msg)
+			dev_err(pdsc->dev, "Cannot identify device: %pe\n",
+				ERR_PTR(err));
 		return err;
 	}
 
@@ -334,6 +344,22 @@ static int pdsc_identify(struct pdsc *pdsc)
 	return 0;
 }
 
+static int pdsc_identify(struct pdsc *pdsc)
+{
+	int err;
+
+	/* Older firmware rejects anything but PDS_CORE_IDENTITY_VERSION_1
+	 * with PDS_RC_EVERSION (-EINVAL), so retry with V1 on version
+	 * rejection. Don't retry on other errors like -ENXIO/-ETIMEDOUT
+	 * which indicate firmware is not running or hung.
+	 */
+	err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_2);
+	if (err == -EINVAL)
+		err = pdsc_identify_ver(pdsc, PDS_CORE_IDENTITY_VERSION_1);
+
+	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

* [PATCH net-next v7 0/6] pds_core: Add PLDM firmware update and host backed memory support
From: Nikhil P. Rao @ 2026-07-08 21:22 UTC (permalink / raw)
  To: netdev
  Cc: kuba, brett.creeley, eric.joyner, andrew+netdev, davem, edumazet,
	pabeni, jacob.e.keller, Nikhil P. Rao

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 v6:
- Patch 3 (PLDM firmware update):
  - Add config space read after pci_clear_master() in teardown to ensure
    in-flight DMA has completed before freeing deferred buffers
  - Use list_splice_init() to detach deferred DMA list under lock, then
    iterate and free without holding the lock
  - Add NL_SET_ERR_MSG_MOD calls to PLDM error paths for better error
    reporting via netlink extack
  - Use PDSC_FW_COMPONENT_FULL_NAME_BUFLEN macro instead of ad-hoc buffer
    size in pdsc_flash_component()
  - Simplify finalize_update loop condition to `if (err != -EAGAIN)`
  - Reject devlink flash with component parameter when firmware doesn't
    support PLDM (returns -EOPNOTSUPP). This depends on a fix submitted
    via net: https://lore.kernel.org/netdev/20260708163649.128620-1-nikhil.rao@amd.com
  - Update documentation clarifying fw.gold is not a flash target
- Patch 4 (component info):
  - Use READ_ONCE/WRITE_ONCE pairing for num_components to get a consistent
    snapshot; avoids iterating zero times with no fallback when recovery
    path invalidates the cache concurrently

Changes since v5:
- Patch 3: Changed "fw.suc.mainfw" to "fw.suc" for System Unit Controller
  firmware to be consistent with the "fw.mainfw" to "fw" change in v4
- Fixed bugs identified by sashiko:
  Patch 3 (PLDM firmware update):
  - Remove stray kdoc fragment in pds_core_send_component_op enum

  Patch 4 (component info):
  - Fix fallback to dev_info.fw_version when pdsc_get_component_info()
    succeeds but returns zero components

  Patch 5 (host backed memory):
  - Expand PDSC_HOST_MEM_MAX_CONTIG comment to clarify why PAGE_SIZE <<
    MAX_PAGE_ORDER is not used

Changes since v4 (sashiko review, Simon Horman):
- Invalidate cached component info in recovery path to ensure stale
  versions are not reported after firmware changes
- Fix v1 error handling: propagate errors from pdsc_dl_fw_list_info_get()
  instead of masking them
- Fix v2 error handling: fall back to dev_info.fw_version only when
  pdsc_get_component_info() fails; propagate devlink errors so partial
  replies are discarded
- Clean up max_fw_slots comment to clarify it contains component count

Changes since v3:
- Changed "fw.mainfw" to just "fw" for main firmware (Jakub Kicinski).
  Gold slot main firmware is reported as "fw.gold".
- Removed redundant memset before alloc_pages (Paolo Abeni)
- Changed dev_err to dev_warn for alloc_pages failure (Paolo Abeni)
- Only report dev_info.fw_version for identity version 1 (version 2+
  reports firmware via PLDM component info)
- Fixed checkpatch alignment issue by extracting pdsc_dl_info_get_v1()
  helper function

Changes since v2:
- Use driver-defined component names instead of passing through firmware
  names (Jakub Kicinski). Added component_type enum that firmware populates,
  driver maps to stable names like fw, fw.gold, fw.bootloader.
  Added documentation of firmware version names to pds_core.rst.
- Fixed bugs identified by sashiko:
  Patch 2 (identity version 2):
  - Fix comment using wrong macro names (IDENTIFY vs IDENTITY)

  Patch 3 (PLDM firmware update):
  - DMA-after-free on EAGAIN/ETIMEDOUT: when a command times out or
    returns busy, firmware may still be accessing the DMA buffer; defer
    freeing until a subsequent command succeeds
  - Use dev_warn_once for incompatible firmware version (ver==0)
  - Clear component cache after flash to show updated versions

  Patch 4 (component info):
  - Fix min_t(u8) truncation of max_fw_slots (u16) to min_t(u16)
  - Fix F_FIXED early return skipping F_RUNNING flag check
  - Don't fail devlink info if component query fails; use dev_warn_once
    and continue to report generic fields (fw, asic.id, serial_number)

  Patch 5 (host backed memory):
  - Switch from adminq to devcmd; fixes both workqueue self-deadlock
    during recovery (adminq completion runs on same wq as health_thread)
    and health_work re-queued after cancel (adminq timeout re-queues work)
  - Remove MEM_DEL from teardown path; fixes both MEM_DEL sent twice
    for same tag and num_host_mem_reqs ambiguous semantics (now only
    tracks pages to free). pci_clear_master guarantees DMA quiescence.
  - Fix PDSC_HOST_MEM_MAX_CONTIG to 4MB constant (was arch-dependent)
  - Not fixed: pdsc_host_mem_add() failure ignored; partial host memory
    is acceptable and firmware handles fewer regions than requested

  Patch 6 (debugfs):
  - Move pdsc_debugfs_del_host_mem() before pdsc_host_mem_free() to
    fix use-after-free race with debugfs readers
  - Use %u for unsigned types and %pad for dma_addr_t
  - Remove "file exists" check (now dead code since teardown removes file)

Note: The following fix was submitted separately via net:
- DMA in flight during teardown (call pci_clear_master before freeing
  host memory):
  https://lore.kernel.org/all/20260604213637.3844317-1-nikhil.rao@amd.com/

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/

Link to v6: https://lore.kernel.org/netdev/20260623-upstream_v6-v6-0-3bb64e0dc4f3@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

Nikhil P. Rao (2):
  pds_core: add host backed memory support for firmware
  pds_core: add debugfs support for host backed memory

 .../device_drivers/ethernet/amd/pds_core.rst  |  89 ++
 drivers/net/ethernet/amd/Kconfig              |   1 +
 drivers/net/ethernet/amd/pds_core/core.c      | 173 ++++
 drivers/net/ethernet/amd/pds_core/core.h      |  54 +-
 drivers/net/ethernet/amd/pds_core/debugfs.c   |  45 +
 drivers/net/ethernet/amd/pds_core/dev.c       | 140 +++-
 drivers/net/ethernet/amd/pds_core/devlink.c   | 147 +++-
 drivers/net/ethernet/amd/pds_core/fw.c        | 789 +++++++++++++++++-
 drivers/net/ethernet/amd/pds_core/main.c      |   9 +-
 include/linux/pds/pds_core_if.h               | 469 +++++++++++
 10 files changed, 1893 insertions(+), 23 deletions(-)

--
2.43.0


^ permalink raw reply

* Re: [PATCH net] sctp: validate stream count in sctp_process_strreset_inreq()
From: Xin Long @ 2026-07-08 21:20 UTC (permalink / raw)
  To: Cen Zhang (Microsoft)
  Cc: marcelo.leitner, davem, edumazet, kuba, pabeni, horms, linux-sctp,
	netdev, linux-kernel, AutonomousCodeSecurity, tgopinath, kys
In-Reply-To: <20260707203215.2752-1-blbllhy@gmail.com>

On Tue, Jul 7, 2026 at 4:32 PM Cen Zhang (Microsoft) <blbllhy@gmail.com> wrote:
>
> When processing a RESET_IN_REQUEST from a peer,
> sctp_process_strreset_inreq() derives the stream count from the
> parameter length but does not check whether the resulting
> RESET_OUT_REQUEST response would exceed SCTP_MAX_CHUNK_LEN.
>
> The OUT request header (sctp_strreset_outreq, 16 bytes) is 8 bytes larger
> than the IN request header (sctp_strreset_inreq, 8 bytes). Generally, the
> IP payload is bounded to 65535 bytes, so the stream list cannot be
> large enough to trigger the overflow. However, on interfaces with MTU >
> 65535 (e.g., loopback with IPv6 jumbograms), a stream list that fits
> within the incoming IN parameter can cause a __u16 overflow in
> sctp_make_strreset_req() when computing the OUT response size, leading to
> an undersized skb allocation, raising a kernel BUG:
>
>   net/core/skbuff.c:207        skb_panic
>   net/core/skbuff.c:2625       skb_put
>   net/sctp/sm_make_chunk.c:1535 sctp_addto_chunk
>   net/sctp/sm_make_chunk.c:3695 sctp_make_strreset_req
>   net/sctp/stream.c:655        sctp_process_strreset_inreq
>
> The local setsockopt path (sctp_send_reset_streams) already performs length
> validation, but the network packet path does not. Fix by adding similar
> length check before calling sctp_make_strreset_req().
>
> Fixes: 7f9d68ac944e ("sctp: implement sender-side procedures for SSN Reset
> Request Parameter")
> Reported-by: AutonomousCodeSecurity@microsoft.com
> Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com>
> ---
>  net/sctp/stream.c | 4 ++++
>  1 file changed, 4 insertions(+)
>
> diff --git a/net/sctp/stream.c b/net/sctp/stream.c
> index 5c2fdedea..ea3805712 100644
> --- a/net/sctp/stream.c
> +++ b/net/sctp/stream.c
> @@ -639,6 +639,10 @@ struct sctp_chunk *sctp_process_strreset_inreq(
>
>         nums = (ntohs(param.p->length) - sizeof(*inreq)) / sizeof(__u16);
>         str_p = inreq->list_of_streams;
> +       if (nums * sizeof(__u16) + sizeof(struct sctp_strreset_outreq)
> +                       > SCTP_MAX_CHUNK_LEN - sizeof(struct sctp_reconf_chunk)) {
> +               goto out;
> +       }
>         for (i = 0; i < nums; i++) {
>                 if (ntohs(str_p[i]) >= stream->outcnt) {
>                         result = SCTP_STRRESET_ERR_WRONG_SSN;
> --
> 2.53.0
>
I think we should also prevent sending such an 'inreq', since it will
always be rejected by the peer. We can add improve the check in
'sctp_send_reset_streams()' like:

diff --git a/net/sctp/stream.c b/net/sctp/stream.c
index ea3805712b76..51a14d1f2391 100644
--- a/net/sctp/stream.c
+++ b/net/sctp/stream.c
@@ -308,7 +308,8 @@ int sctp_send_reset_streams(struct sctp_association *asoc,
                                        goto out;

                        param_len += str_nums * sizeof(__u16) +
-                                    sizeof(struct sctp_strreset_inreq);
+                                    (out ? sizeof(struct sctp_strreset_inreq)
+                                         : sizeof(struct
sctp_strreset_outreq));
                }

Nits: Please keep the '>' on the same line as the left-hand operand and
indent the continuation line using the usual kernel style. Also No braces
are needed for a single statement.

if (nums * sizeof(__u16) + sizeof(struct sctp_strreset_outreq) >
    SCTP_MAX_CHUNK_LEN - sizeof(struct sctp_reconf_chunk))
        goto out;

Thanks.

^ permalink raw reply related

* [PATCH net-next v3] net: mana: Add handler for sriov configure
From: Haiyang Zhang @ 2026-07-08 20:59 UTC (permalink / raw)
  To: linux-hyperv, netdev, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
	Dexuan Cui, Long Li, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Erni Sri Satya Vennela,
	Dipayaan Roy, Aditya Garg, Shradha Gupta, linux-kernel
  Cc: paulros

From: Haiyang Zhang <haiyangz@microsoft.com>

Add callback function for the pci_driver / sriov_configure.

It asks the NIC to provide certain number of VFs, or disable
VFs if the request is zero.

Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com>
---
v3:
  Updated sriov disabling paths suggested by Paolo Abeni

v2:
  No longer change VF autoprobe as discussed with Leon Romanovsky and Bjorn Helgaas.

---
 .../net/ethernet/microsoft/mana/gdma_main.c   | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c
index aef3b77229c1..80a9118a90bc 100644
--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
+++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
@@ -2456,6 +2456,8 @@ static void mana_gd_remove(struct pci_dev *pdev)
 {
 	struct gdma_context *gc = pci_get_drvdata(pdev);
 
+	pci_disable_sriov(pdev);
+
 	mana_rdma_remove(&gc->mana_ib);
 	mana_remove(&gc->mana, false);
 
@@ -2517,6 +2519,8 @@ static void mana_gd_shutdown(struct pci_dev *pdev)
 
 	dev_info(&pdev->dev, "Shutdown was called\n");
 
+	pci_disable_sriov(pdev);
+
 	mana_rdma_remove(&gc->mana_ib);
 	mana_remove(&gc->mana, true);
 
@@ -2525,6 +2529,27 @@ static void mana_gd_shutdown(struct pci_dev *pdev)
 	pci_disable_device(pdev);
 }
 
+static int mana_sriov_configure(struct pci_dev *pdev, int numvfs)
+{
+	int err = 0;
+
+	dev_info(&pdev->dev, "Requested num VFs: %d\n", numvfs);
+
+	if (numvfs > 0) {
+		err = pci_enable_sriov(pdev, numvfs);
+	} else {
+		if (pci_vfs_assigned(pdev)) {
+			dev_warn(&pdev->dev,
+				 "Cannot disable SR-IOV while VFs are assigned\n");
+			return -EPERM;
+		}
+
+		pci_disable_sriov(pdev);
+	}
+
+	return err ? err : numvfs;
+}
+
 static const struct pci_device_id mana_id_table[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_MICROSOFT, MANA_PF_DEVICE_ID) },
 	{ PCI_DEVICE(PCI_VENDOR_ID_MICROSOFT, MANA_PF2_DEVICE_ID) },
@@ -2540,6 +2565,7 @@ static struct pci_driver mana_driver = {
 	.suspend	= mana_gd_suspend,
 	.resume		= mana_gd_resume,
 	.shutdown	= mana_gd_shutdown,
+	.sriov_configure = mana_sriov_configure,
 };
 
 static int __init mana_driver_init(void)
-- 
2.34.1


^ permalink raw reply related

* [PATCH nf] netfilter: flowtable: tear down HW offloaded flows on FIB route changes
From: Ahmed Zaki @ 2026-07-08 20:54 UTC (permalink / raw)
  To: netfilter-devel; +Cc: pablo, fw, kuba, edumazet, davem, pabeni, horms, netdev

Hardware-offloaded flows bypass the CPU and, unlike the software
datapath, dst_check() does not invalidate them when a route changes.
For ephemeral flows, this is usually not a problem as the flow expire on
its own and the driver clears the entry in the HW. However, for persistent
flows forwarded through the device, the HW is never informed that the
route has expired.

For tables marked with NF_FLOWTABLE_HW_OFFLOAD, listen to the per-net FIB
notifier chain and tear down the affected flows so they are re-evaluated by
the SW forwarding path.

A lockless list is used to reduce the work items overhead in case of a
route change storm allowing many FIB events to be processed by one work
item.

Fixes: c29f74e0df7a ("netfilter: nf_flow_table: hardware offload support")
Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Ahmed Zaki <anzaki@gmail.com>
---
 include/net/netfilter/nf_flow_table.h |   3 +
 net/netfilter/nf_flow_table_core.c    | 202 ++++++++++++++++++++++++++
 2 files changed, 205 insertions(+)

diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
index 7b23b245a5a8..7c36255f5a4f 100644
--- a/include/net/netfilter/nf_flow_table.h
+++ b/include/net/netfilter/nf_flow_table.h
@@ -84,6 +84,9 @@ struct nf_flowtable {
 	struct flow_block		flow_block;
 	struct rw_semaphore		flow_block_lock; /* Guards flow_block */
 	possible_net_t			net;
+	struct notifier_block		fib_nb;
+	struct work_struct		fib_work;
+	struct llist_head		fib_events;
 };
 
 static inline bool nf_flowtable_hw_offload(struct nf_flowtable *flowtable)
diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
index 99c5b9d671a0..2c912056921a 100644
--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -5,8 +5,12 @@
 #include <linux/netfilter.h>
 #include <linux/rhashtable.h>
 #include <linux/netdevice.h>
+#include <linux/llist.h>
 #include <net/ip.h>
 #include <net/ip6_route.h>
+#include <net/fib_notifier.h>
+#include <net/ip_fib.h>
+#include <net/ip6_fib.h>
 #include <net/netfilter/nf_tables.h>
 #include <net/netfilter/nf_flow_table.h>
 #include <net/netfilter/nf_conntrack.h>
@@ -695,11 +699,179 @@ void nf_flow_dnat_port(const struct flow_offload *flow, struct sk_buff *skb,
 }
 EXPORT_SYMBOL_GPL(nf_flow_dnat_port);
 
+struct nf_flow_fib_match {
+	struct llist_node	*events;
+};
+
+struct nf_flow_fib_event {
+	struct llist_node	node;
+	u8			family;
+	u8			prefix_len;
+	union {
+		__be32		ip4;
+		struct in6_addr	ip6;
+	} addr;
+};
+
+static bool nf_flow_fib_tuple_match(const struct flow_offload_tuple *tuple,
+				    const struct nf_flow_fib_event *ev)
+{
+	if (tuple->l3proto != ev->family)
+		return false;
+
+	switch (ev->family) {
+	case NFPROTO_IPV4: {
+		__be32 mask = ev->prefix_len ?
+			htonl(~0u << (32 - ev->prefix_len)) : 0;
+		return (tuple->dst_v4.s_addr & mask) == (ev->addr.ip4 & mask);
+	}
+#if IS_ENABLED(CONFIG_IPV6)
+	case NFPROTO_IPV6:
+		return ipv6_prefix_equal(&tuple->dst_v6, &ev->addr.ip6,
+					 ev->prefix_len);
+#endif
+	default:
+		return false;
+	}
+}
+
+static bool nf_flow_fib_flow_match(const struct flow_offload *flow,
+				   const struct nf_flow_fib_match *m)
+{
+	const struct flow_offload_tuple *orig, *reply;
+	const struct nf_flow_fib_event *ev;
+	struct llist_node *node;
+
+	orig  = &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple;
+	reply = &flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple;
+
+	for (node = m->events; node; node = node->next) {
+		ev = llist_entry(node, struct nf_flow_fib_event, node);
+		if (nf_flow_fib_tuple_match(orig, ev) ||
+		    nf_flow_fib_tuple_match(reply, ev))
+			return true;
+	}
+
+	return false;
+}
+
+static void nf_flow_offload_fib_cb(struct nf_flowtable *flow_table,
+				   struct flow_offload *flow, void *data)
+{
+	const struct nf_flow_fib_match *m = data;
+
+	if (test_bit(NF_FLOW_TEARDOWN, &flow->flags))
+		return;
+
+	if (nf_flow_fib_flow_match(flow, m))
+		flow_offload_teardown(flow);
+}
+
+static void nf_flow_table_fib_work(struct work_struct *work)
+{
+	struct nf_flowtable *flow_table =
+		container_of(work, struct nf_flowtable, fib_work);
+	struct nf_flow_fib_event *ev, *next;
+	struct nf_flow_fib_match m = {};
+	struct llist_node *events;
+
+	events = llist_del_all(&flow_table->fib_events);
+	if (!events)
+		return;
+
+	m.events = events;
+	nf_flow_table_iterate(flow_table, nf_flow_offload_fib_cb, &m);
+
+	llist_for_each_entry_safe(ev, next, events, node)
+		kfree(ev);
+}
+
+static bool nf_flowtable_fib_family_match(const struct nf_flowtable *flowtable,
+					  u8 event_family)
+{
+	switch (flowtable->type->family) {
+	case NFPROTO_IPV4:
+		return event_family == NFPROTO_IPV4;
+	case NFPROTO_IPV6:
+		return event_family == NFPROTO_IPV6;
+	case NFPROTO_INET:
+		return event_family == NFPROTO_IPV4 ||
+		       event_family == NFPROTO_IPV6;
+	default:
+		return false;
+	}
+}
+
+/* Called with rcu_read_lock() */
+static int nf_flow_table_fib_event(struct notifier_block *nb,
+				   unsigned long event, void *ptr)
+{
+	struct nf_flowtable *flow_table =
+		container_of(nb, struct nf_flowtable, fib_nb);
+	struct fib_notifier_info *info = ptr;
+	struct nf_flow_fib_event *ev;
+
+	switch (event) {
+	case FIB_EVENT_ENTRY_REPLACE:
+	case FIB_EVENT_ENTRY_APPEND:
+	case FIB_EVENT_ENTRY_DEL:
+		break;
+	default:
+		return NOTIFY_DONE;
+	}
+
+	/* Skip events for an address family this table cannot hold. */
+	if (!nf_flowtable_fib_family_match(flow_table, info->family))
+		return NOTIFY_DONE;
+
+	ev = kzalloc(sizeof(*ev), GFP_ATOMIC);
+	if (!ev)
+		return NOTIFY_DONE;
+
+	switch (info->family) {
+	case NFPROTO_IPV4:
+		struct fib_entry_notifier_info *fen;
+
+		fen = container_of(info, struct fib_entry_notifier_info, info);
+		ev->family     = NFPROTO_IPV4;
+		ev->addr.ip4   = htonl(fen->dst);
+		ev->prefix_len = fen->dst_len;
+		break;
+
+#if IS_ENABLED(CONFIG_IPV6)
+	case NFPROTO_IPV6:
+		struct fib6_entry_notifier_info *fen6;
+
+		fen6 = container_of(info, struct fib6_entry_notifier_info, info);
+		if (!fen6->rt)
+			goto err;
+
+		ev->family     = NFPROTO_IPV6;
+		ev->addr.ip6   = fen6->rt->fib6_dst.addr;
+		ev->prefix_len = fen6->rt->fib6_dst.plen;
+		break;
+#endif
+	default:
+		goto err;
+	}
+
+	llist_add(&ev->node, &flow_table->fib_events);
+	queue_work(system_power_efficient_wq, &flow_table->fib_work);
+	return NOTIFY_DONE;
+
+err:
+	kfree(ev);
+	return NOTIFY_DONE;
+}
+
 int nf_flow_table_init(struct nf_flowtable *flowtable)
 {
+	struct net *net = read_pnet(&flowtable->net);
 	int err;
 
 	INIT_DELAYED_WORK(&flowtable->gc_work, nf_flow_offload_work_gc);
+	INIT_WORK(&flowtable->fib_work, nf_flow_table_fib_work);
+	init_llist_head(&flowtable->fib_events);
 	flow_block_init(&flowtable->flow_block);
 	init_rwsem(&flowtable->flow_block_lock);
 
@@ -711,11 +883,24 @@ int nf_flow_table_init(struct nf_flowtable *flowtable)
 	queue_delayed_work(system_power_efficient_wq,
 			   &flowtable->gc_work, HZ);
 
+	if (nf_flowtable_hw_offload(flowtable)) {
+		flowtable->fib_nb.notifier_call = nf_flow_table_fib_event;
+		err = register_fib_notifier(net, &flowtable->fib_nb,
+					    NULL, NULL);
+		if (err < 0)
+			goto err_fib;
+	}
+
 	mutex_lock(&flowtable_lock);
 	list_add(&flowtable->list, &flowtables);
 	mutex_unlock(&flowtable_lock);
 
 	return 0;
+
+err_fib:
+	cancel_delayed_work_sync(&flowtable->gc_work);
+	rhashtable_destroy(&flowtable->rhashtable);
+	return err;
 }
 EXPORT_SYMBOL_GPL(nf_flow_table_init);
 
@@ -754,8 +939,25 @@ void nf_flow_table_cleanup(struct net_device *dev)
 }
 EXPORT_SYMBOL_GPL(nf_flow_table_cleanup);
 
+static void nf_flow_table_fib_drain(struct nf_flowtable *flow_table)
+{
+	struct nf_flow_fib_event *ev, *next;
+	struct llist_node *events;
+
+	events = llist_del_all(&flow_table->fib_events);
+	llist_for_each_entry_safe(ev, next, events, node)
+		kfree(ev);
+}
+
 void nf_flow_table_free(struct nf_flowtable *flow_table)
 {
+	if (nf_flowtable_hw_offload(flow_table)) {
+		unregister_fib_notifier(read_pnet(&flow_table->net),
+					&flow_table->fib_nb);
+		cancel_work_sync(&flow_table->fib_work);
+		nf_flow_table_fib_drain(flow_table);
+	}
+
 	mutex_lock(&flowtable_lock);
 	list_del(&flow_table->list);
 	mutex_unlock(&flowtable_lock);
-- 
2.43.0


^ permalink raw reply related

* RE: [EXTERNAL] Re: [PATCH net-next v2] net: mana: Add handler for sriov configure
From: Haiyang Zhang @ 2026-07-08 20:52 UTC (permalink / raw)
  To: Paolo Abeni, haiyangz@linux.microsoft.com
  Cc: linux-hyperv@vger.kernel.org, netdev@vger.kernel.org,
	KY Srinivasan, wei.liu@kernel.org, Dexuan Cui, Long Li,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, horms@kernel.org, ernis@linux.microsoft.com,
	dipayanroy@linux.microsoft.com, gargaditya@linux.microsoft.com,
	shradhagupta@linux.microsoft.com, linux-kernel@vger.kernel.org,
	Paul Rosswurm
In-Reply-To: <20260708080854.64655-1-pabeni@redhat.com>



> -----Original Message-----
> From: Paolo Abeni <pabeni@redhat.com>
> Sent: Wednesday, July 8, 2026 4:09 AM
> To: haiyangz@linux.microsoft.com
> Cc: linux-hyperv@vger.kernel.org; netdev@vger.kernel.org; KY Srinivasan
> <kys@microsoft.com>; Haiyang Zhang <haiyangz@microsoft.com>;
> wei.liu@kernel.org; Dexuan Cui <DECUI@microsoft.com>; Long Li
> <longli@microsoft.com>; andrew+netdev@lunn.ch; davem@davemloft.net;
> edumazet@google.com; kuba@kernel.org; pabeni@redhat.com; horms@kernel.org;
> ernis@linux.microsoft.com; dipayanroy@linux.microsoft.com;
> gargaditya@linux.microsoft.com; shradhagupta@linux.microsoft.com; linux-
> kernel@vger.kernel.org; Paul Rosswurm <paulros@microsoft.com>
> Subject: [EXTERNAL] Re: [PATCH net-next v2] net: mana: Add handler for
> sriov configure
> 
> From: AI Reviewer <ai@example.com>
> 
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at:
> https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fsashiko.
> dev%2F&data=05%7C02%7Chaiyangz%40microsoft.com%7C27add93a1c4b4c0c7b4808ded
> cc833d4%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C639190949593984694%7C
> Unknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJX
> aW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=qLm%2BrL1nEF6%
> 2FlF2%2Fmed2mnc%2BVR4Vp9P6G6i5Im1PquU%3D&reserved=0
> ---
> net: mana: Add handler for sriov configure
> 
> Add callback function for the pci_driver / sriov_configure.
> 
> It asks the NIC to provide certain number of VFs, or disable
> VFs if the request is zero.
> 
> > diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c
> b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> > index e8b7ffb47eb98..f7a79874fd0eb 100644
> > --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
> > +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> > @@ -2496,6 +2496,20 @@ static void mana_gd_shutdown(struct pci_dev
> *pdev)
> >  	pci_disable_device(pdev);
> >  }
> 
> [Severity: High]
> With the addition of SR-IOV support in this patch, do mana_gd_shutdown()
> and
> mana_gd_remove() need to call pci_disable_sriov()?
> 
> If a user enables VFs via sysfs and then unbinds the driver or shuts down
> the
> system, could this omission introduce a regression where orphaned, dead VF
> PCI
> devices are left in the kernel?
> 
> Accessing these orphaned VFs might trigger use-after-free crashes or
> hardware
> faults, as the underlying PF hardware and driver context have been
> destroyed.
Will fix.

> 
> > +static int mana_sriov_configure(struct pci_dev *pdev, int numvfs)
> > +{
> > +	int err = 0;
> > +
> > +	dev_info(&pdev->dev, "Requested num VFs: %d\n", numvfs);
> > +
> > +	if (numvfs > 0)
> > +		err = pci_enable_sriov(pdev, numvfs);
> > +	else
> > +		pci_disable_sriov(pdev);
> 
> [Severity: High]
> Should this path check if the VFs are currently assigned to a guest VM
> before
> disabling SR-IOV?
> 
> If a privileged user writes 0 to sriov_numvfs while VFs are actively
> assigned
> to a VM (e.g., via vfio-pci), could forcefully removing the PCI devices
> cause
> a regression resulting in host IOMMU faults and guest kernel panics?
> 
> Would it be better to check pci_vfs_assigned(pdev) here, or use the
> pci_sriov_configure_simple() helper to safely enforce this?

Will update.
I will send out an updated patch soon.

Thanks,
- Haiyang

^ permalink raw reply

* Re: [PATCH 0/3] SM8450 IPA support
From: Esteban Urrutia @ 2026-07-08 20:45 UTC (permalink / raw)
  To: Alex Elder, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alex Elder
  Cc: linux-arm-msm, devicetree, linux-kernel, netdev
In-Reply-To: <8309e9c7-a110-478b-8cd4-c002070d2b4c@riscstar.com>

On 7/8/26 3:49 PM, Alex Elder wrote:
> This was interesting to see.  It's something I should probably
> document better.  Most everything maps to the downstream code,
> but it's not always completely obvious how, because the upstream
> driver has evolved substantially.

On a personal note, I'm surprised to see someone not from Qualcomm
maintaining this driver. It must be hard, so kudos.

> This means that the SRAM size (ipa_mem_data->smem_size) should
> possibly be defined in devicetree (as the IMEM address and size
> now are).
> 
> The SMEM region is used for "IPA filter tables", and access to
> it is shared between the AP and the modem.  Unlike the other
> (host) memory regions, the size used is *not* included in the
> ipa_init_modem_driver_req message that communicates from the
> AP to the modem where the regions are, and their sizes.
> 
> So it's possible that the size used must actually match what
> is expected by both the AP and modem.  If that is the case,
> using the smaller size might have problems on whichever
> platform (SM8450?) expects the larger one.
> 
> So I'm not sure whether using the smaller size for both
> platforms is OK; someone from Qualcomm might be able to
> answer that question.

I actually went ahead and reviewed downstream device trees I found on
GitHub (1) which contain both SM8450 and SM8475 device trees looking for
the qcom,ipa-q6-smem-size property, which would correspond to the SRAM
size, and to my surprise, this was set to 0x9000 for both SoCs.
Most likely the commit I got the SRAM information from (2) never made it
to production devices.

With this clarified, I think it should be okay to keep things defined as
they currently are.

> I'll try to explain those things separately.
Regarding this, I have a question: where would this be published?

Thanks for taking the time to properly review my changes.
I'll address the review when I can.

(1) https://github.com/sm8450-mainline/fdt
(2) https://github.com/LineageOS/android_kernel_qcom_sm8450-devicetrees/commit/477aab9e7479ff553c7a162ae74029170a2e8291.patch

Regards,
Esteban


^ permalink raw reply

* Re: [PATCH net-next v4 1/3] net: devmem: allow rx-buf-size > PAGE_SIZE per dmabuf binding
From: Bobby Eshleman @ 2026-07-08 20:35 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: Mina Almasry, Donald Hunter, Jakub Kicinski, David S. Miller,
	Eric Dumazet, Simon Horman, Andrew Lunn, Gerd Hoffmann,
	Vivek Kasireddy, Sumit Semwal, Christian König, Shuah Khan,
	netdev, linux-kernel, dri-devel, linux-media, linaro-mm-sig,
	linux-kselftest, sdf, razor, daniel, matttbe, skhawaja, dw,
	Joe Damato, Bobby Eshleman
In-Reply-To: <15e72c82-ca99-481b-bd53-744fabd503b0@redhat.com>

On Wed, Jul 08, 2026 at 12:50:07PM +0200, Paolo Abeni wrote:
> On 7/7/26 10:36 PM, Mina Almasry wrote:
> > On Wed, Jul 1, 2026 at 12:22 PM Bobby Eshleman <bobbyeshleman@gmail.com> wrote:
> >>
> >> From: Bobby Eshleman <bobbyeshleman@meta.com>
> >>
> >> Every devmem dmabuf binding today hands the page_pool PAGE_SIZE niovs.
> >> This caps a single RX descriptor at PAGE_SIZE, burning CPU on buffer
> >> churn for large flows.
> >>
> >> Add a bind-time netlink attribute, NETDEV_A_DMABUF_RX_BUF_SIZE, that
> >> lets userspace request a larger niov size. The value must be a power of
> >> two >= PAGE_SIZE.
> >>
> >> Measurements
> >> ------------
> 
> Checkpatch complains about this separator usage:
> 
> ERROR: Invalid commit separator - some tools may have problems applying this
> #15:
> ------------
> 
> Please replace or remove it in the next revision
> 
> >> @@ -90,16 +90,17 @@ net_devmem_alloc_dmabuf(struct net_devmem_dmabuf_binding *binding)
> >>         struct dmabuf_genpool_chunk_owner *owner;
> >>         unsigned long dma_addr;
> >>         struct net_iov *niov;
> >> -       ssize_t offset;
> >> -       ssize_t index;
> >> +       size_t offset;
> >> +       size_t index;
> >>
> > 
> > nit: I would keep this signed. Some of the most frustrating issues I
> > ran into is some of the underflowing and then passing a > check or
> > something. Although if the LLM is not complaining about this
> > particular case, there is probably no issue with it. I also notice a
> > lot of existing code that deals with indexes and offsets goes for
> > signed.
> 
> At very least the above change should go in a separate patch, as is
> quite unrelated from the rest.
> 
> /P
> 

Sounds good, I'll drop the type change.

Thanks,
Bobby

^ permalink raw reply

* [PATCH bpf-next v4 6/6] selftests: drv-net: add XDP RX checksum metadata tests
From: Vladimir Vdovin @ 2026-07-08 20:34 UTC (permalink / raw)
  To: Lorenzo Bianconi, Donald Hunter, Jakub Kicinski, David S . Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Alexei Starovoitov,
	Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
	Stanislav Fomichev, Andrew Lunn, Tony Nguyen, Przemek Kitszel,
	Alexander Lobakin, Andrii Nakryiko, Martin KaFai Lau,
	Eduard Zingerman, Song Liu, Yonghong Song, KP Singh, Hao Luo,
	Jiri Olsa, Shuah Khan, Maciej Fijalkowski
  Cc: Jakub Sitnicki, Aleksandr Loktionov, netdev, bpf, intel-wired-lan,
	linux-kselftest, Vladimir Vdovin
In-Reply-To: <20260708203410.45121-1-deliran@verdict.gg>

Extend the xdp_metadata.py driver test with coverage for
bpf_xdp_metadata_rx_checksum().

Add an xdp_rx_csum program to xdp_metadata.bpf.o that reads the RX
checksum verdict and stores the ip_summed bitmask, the hw checksum
value and the checksum level into a map.  The L4 port/protocol filter
is the same as in the existing xdp_rss_hash program, so move it into a
common helper.

The new cases only run on devices whose driver implements the
xmo_rx_checksum callback, detected through the "checksum" bit of the
xdp-rx-metadata-features netlink attribute; on other devices they
report SKIP:

 - xdp_rx_csum_valid (tcp/udp variants): traffic with a correct
   checksum sent from the remote endpoint must be reported with a
   usable verdict, i.e. CHECKSUM_UNNECESSARY and/or CHECKSUM_COMPLETE.
   CHECKSUM_NONE is a legitimate verdict for a device that does not
   verify the packets (e.g. veth reports it for locally generated
   CHECKSUM_PARTIAL traffic), so it results in SKIP rather than in a
   failure;

 - xdp_rx_csum_invalid: UDP packets with a corrupted L4 checksum
   (sent with the net/lib csum tool) must not be reported as
   CHECKSUM_UNNECESSARY.

Signed-off-by: Vladimir Vdovin <deliran@verdict.gg>
---
 .../selftests/drivers/net/hw/xdp_metadata.py  | 110 +++++++++++++++++
 .../selftests/net/lib/xdp_metadata.bpf.c      | 112 ++++++++++++++++--
 2 files changed, 209 insertions(+), 13 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/hw/xdp_metadata.py b/tools/testing/selftests/drivers/net/hw/xdp_metadata.py
index 33a1985356d9..1a623771477b 100644
--- a/tools/testing/selftests/drivers/net/hw/xdp_metadata.py
+++ b/tools/testing/selftests/drivers/net/hw/xdp_metadata.py
@@ -8,6 +8,8 @@ These tests load device-bound XDP programs from xdp_metadata.bpf.o
 that call metadata kfuncs, send traffic, and verify the extracted
 metadata via BPF maps.
 """
+import time
+
 from lib.py import ksft_run, ksft_eq, ksft_exit, ksft_ge, ksft_ne, ksft_pr
 from lib.py import KsftNamedVariant, ksft_variants
 from lib.py import CmdExitFailure, KsftSkipEx, NetDrvEpEnv
@@ -81,8 +83,22 @@ _RSS_KEY_TYPE = 1
 _RSS_KEY_PKT_CNT = 2
 _RSS_KEY_ERR_CNT = 3
 
+_CSUM_KEY_IP_SUMMED = 0
+_CSUM_KEY_CKSUM = 1
+_CSUM_KEY_LEVEL = 2
+_CSUM_KEY_PKT_CNT = 3
+_CSUM_KEY_ERR_CNT = 4
+
 XDP_RSS_L4 = 0x8  # BIT(3) from enum xdp_rss_hash_type
 
+# Mirror of enum xdp_checksum from include/net/xdp.h
+XDP_CHECKSUM_NONE = 0x1
+XDP_CHECKSUM_UNNECESSARY = 0x2
+XDP_CHECKSUM_COMPLETE = 0x4
+
+# Fixed destination port of the net/lib csum tool
+_CSUM_TOOL_PORT = 34000
+
 
 @ksft_variants([
     KsftNamedVariant("tcp", "tcp"),
@@ -130,6 +146,98 @@ def test_xdp_rss_hash(cfg, proto):
             f"RSS hash type should include L4 for {proto.upper()} traffic")
 
 
+def _require_rx_csum_meta(cfg):
+    """Skip unless the device exposes XDP RX checksum metadata."""
+    dev_info = cfg.netnl.dev_get({"ifindex": cfg.ifindex})
+    rx_meta = dev_info.get("xdp-rx-metadata-features", [])
+    if "checksum" not in rx_meta:
+        raise KsftSkipEx("device does not support XDP rx checksum metadata")
+
+
+@ksft_variants([
+    KsftNamedVariant("tcp", "tcp"),
+    KsftNamedVariant("udp", "udp"),
+])
+def test_xdp_rx_csum_valid(cfg, proto):
+    """Test RX checksum metadata for packets with a correct checksum.
+
+    Loads the xdp_rx_csum program, sends traffic with a valid L4 checksum
+    from the remote endpoint, and verifies that the checksum verdict
+    reported via bpf_xdp_metadata_rx_checksum() is usable
+    (CHECKSUM_UNNECESSARY and/or a CHECKSUM_COMPLETE value).
+
+    CHECKSUM_NONE is a valid verdict for a device that did not verify
+    the packets (e.g. veth reports it for locally generated traffic,
+    which is CHECKSUM_PARTIAL on the skb), so it results in SKIP, not
+    in a failure.
+    """
+    _require_rx_csum_meta(cfg)
+
+    prog_info = _load_xdp_metadata_prog(cfg, "xdp_rx_csum")
+
+    port = rand_port()
+    bpf_map_set("map_xdp_setup", _SETUP_KEY_PORT, port)
+
+    csum_map_id = prog_info["maps"]["map_csum"]
+
+    _send_probe(cfg, port, proto=proto)
+
+    csum = bpf_map_dump(csum_map_id)
+
+    pkt_cnt = csum.get(_CSUM_KEY_PKT_CNT, 0)
+    err_cnt = csum.get(_CSUM_KEY_ERR_CNT, 0)
+    ip_summed = csum.get(_CSUM_KEY_IP_SUMMED, 0)
+
+    ksft_ge(pkt_cnt, 1, comment="should have received at least one packet")
+    ksft_eq(err_cnt, 0, comment=f"RX checksum error count: {err_cnt}")
+
+    ksft_pr(f"  ip_summed: {ip_summed:#x} cksum: "
+            f"{csum.get(_CSUM_KEY_CKSUM, 0):#010x} "
+            f"level: {csum.get(_CSUM_KEY_LEVEL, 0)}")
+    ksft_ne(ip_summed, 0, "the program should have stored a checksum verdict")
+    if not ip_summed & (XDP_CHECKSUM_UNNECESSARY | XDP_CHECKSUM_COMPLETE):
+        raise KsftSkipEx("device did not verify the packet checksum "
+                         "(CHECKSUM_NONE)")
+
+
+def test_xdp_rx_csum_invalid(cfg):
+    """Test RX checksum metadata for packets with a corrupted checksum.
+
+    Sends UDP packets with an intentionally bad L4 checksum using the
+    net/lib csum tool and verifies the device does not claim it validated
+    them: the CHECKSUM_UNNECESSARY bit must not be set.
+    """
+    _require_rx_csum_meta(cfg)
+
+    ipver = cfg.addr_ipver
+    bin_remote = cfg.remote.deploy(cfg.net_lib_dir / "csum")
+
+    prog_info = _load_xdp_metadata_prog(cfg, "xdp_rx_csum")
+
+    bpf_map_set("map_xdp_setup", _SETUP_KEY_PORT, _CSUM_TOOL_PORT)
+
+    csum_map_id = prog_info["maps"]["map_csum"]
+
+    cmd(f"{bin_remote} -i {cfg.remote_ifname} -n 20 -{ipver} "
+        f"-S {cfg.remote_addr} -D {cfg.addr} -r 1 -T -E",
+        host=cfg.remote)
+
+    # no receiver to synchronize against; let NAPI drain the last packets
+    time.sleep(1)
+
+    csum = bpf_map_dump(csum_map_id)
+
+    pkt_cnt = csum.get(_CSUM_KEY_PKT_CNT, 0)
+    ip_summed = csum.get(_CSUM_KEY_IP_SUMMED, 0)
+
+    ksft_ge(pkt_cnt, 1, comment="should have received at least one packet")
+
+    ksft_pr(f"  ip_summed: {ip_summed:#x}")
+    ksft_eq(ip_summed & XDP_CHECKSUM_UNNECESSARY, 0,
+            "device must not report CHECKSUM_UNNECESSARY for a corrupted "
+            "checksum")
+
+
 def main():
     """Run XDP metadata kfunc tests against a real device."""
     with NetDrvEpEnv(__file__) as cfg:
@@ -137,6 +245,8 @@ def main():
         ksft_run(
             [
                 test_xdp_rss_hash,
+                test_xdp_rx_csum_valid,
+                test_xdp_rx_csum_invalid,
             ],
             args=(cfg,))
     ksft_exit()
diff --git a/tools/testing/selftests/net/lib/xdp_metadata.bpf.c b/tools/testing/selftests/net/lib/xdp_metadata.bpf.c
index f71f59215239..70decae0a663 100644
--- a/tools/testing/selftests/net/lib/xdp_metadata.bpf.c
+++ b/tools/testing/selftests/net/lib/xdp_metadata.bpf.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <stddef.h>
+#include <stdbool.h>
 #include <linux/bpf.h>
 #include <linux/in.h>
 #include <linux/if_ether.h>
@@ -40,6 +41,24 @@ struct {
 	__uint(max_entries, 4);
 } map_rss SEC(".maps");
 
+/* RX checksum results: key 0 = ip_summed bitmask, key 1 = hw cksum value,
+ * key 2 = cksum level, key 3 = packet count, key 4 = error count.
+ */
+enum {
+	CSUM_KEY_IP_SUMMED = 0,
+	CSUM_KEY_CKSUM = 1,
+	CSUM_KEY_LEVEL = 2,
+	CSUM_KEY_PKT_CNT = 3,
+	CSUM_KEY_ERR_CNT = 4,
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__type(key, __u32);
+	__type(value, __u32);
+	__uint(max_entries, 5);
+} map_csum SEC(".maps");
+
 /* Mirror of enum xdp_rss_hash_type from include/net/xdp.h.
  * Needed because the enum is not part of UAPI headers.
  */
@@ -55,8 +74,20 @@ enum xdp_rss_hash_type {
 	XDP_RSS_L4_ICMP = 1U << 8,
 };
 
+/* Mirror of enum xdp_checksum from include/net/xdp.h.
+ * Needed because the enum is not part of UAPI headers.
+ */
+enum xdp_checksum {
+	XDP_CHECKSUM_NONE = 1U << 0,
+	XDP_CHECKSUM_UNNECESSARY = 1U << 1,
+	XDP_CHECKSUM_COMPLETE = 1U << 2,
+};
+
 extern int bpf_xdp_metadata_rx_hash(const struct xdp_md *ctx, __u32 *hash,
 				    enum xdp_rss_hash_type *rss_type) __ksym;
+extern int bpf_xdp_metadata_rx_checksum(const struct xdp_md *ctx,
+					enum xdp_checksum *ip_summed,
+					__u32 *cksum, __u8 *cksum_level) __ksym;
 
 static __always_inline __u16 get_dest_port(void *l4, void *data_end,
 					   __u8 protocol)
@@ -78,41 +109,39 @@ static __always_inline __u16 get_dest_port(void *l4, void *data_end,
 	return 0;
 }
 
-SEC("xdp")
-int xdp_rss_hash(struct xdp_md *ctx)
+/* Return true when the packet matches the L4 protocol and destination
+ * port configured in map_xdp_setup (zero/unset filters match anything).
+ */
+static __always_inline bool xdp_match_setup(struct xdp_md *ctx)
 {
 	void *data_end = (void *)(long)ctx->data_end;
 	void *data = (void *)(long)ctx->data;
-	enum xdp_rss_hash_type rss_type = 0;
 	struct ethhdr *eth = data;
 	__u8 l4_proto = 0;
-	__u32 hash = 0;
-	__u32 key, val;
 	void *l4 = NULL;
-	__u32 *cnt;
-	int ret;
+	__u32 key;
 
 	if ((void *)(eth + 1) > data_end)
-		return XDP_PASS;
+		return false;
 
 	if (eth->h_proto == bpf_htons(ETH_P_IP)) {
 		struct iphdr *iph = (void *)(eth + 1);
 
 		if ((void *)(iph + 1) > data_end)
-			return XDP_PASS;
+			return false;
 		l4_proto = iph->protocol;
 		l4 = (void *)(iph + 1);
 	} else if (eth->h_proto == bpf_htons(ETH_P_IPV6)) {
 		struct ipv6hdr *ip6h = (void *)(eth + 1);
 
 		if ((void *)(ip6h + 1) > data_end)
-			return XDP_PASS;
+			return false;
 		l4_proto = ip6h->nexthdr;
 		l4 = (void *)(ip6h + 1);
 	}
 
 	if (!l4)
-		return XDP_PASS;
+		return false;
 
 	/* Filter on the configured protocol (map_xdp_setup key XDP_PROTO).
 	 * When set, only process packets matching the requested L4 protocol.
@@ -121,7 +150,7 @@ int xdp_rss_hash(struct xdp_md *ctx)
 	__s32 *proto_cfg = bpf_map_lookup_elem(&map_xdp_setup, &key);
 
 	if (proto_cfg && *proto_cfg != 0 && l4_proto != (__u8)*proto_cfg)
-		return XDP_PASS;
+		return false;
 
 	/* Filter on the configured port (map_xdp_setup key XDP_PORT).
 	 * Only applies to protocols with ports (UDP, TCP).
@@ -133,9 +162,24 @@ int xdp_rss_hash(struct xdp_md *ctx)
 		__u16 dest = get_dest_port(l4, data_end, l4_proto);
 
 		if (!dest || bpf_ntohs(dest) != (__u16)*port_cfg)
-			return XDP_PASS;
+			return false;
 	}
 
+	return true;
+}
+
+SEC("xdp")
+int xdp_rss_hash(struct xdp_md *ctx)
+{
+	enum xdp_rss_hash_type rss_type = 0;
+	__u32 hash = 0;
+	__u32 key, val;
+	__u32 *cnt;
+	int ret;
+
+	if (!xdp_match_setup(ctx))
+		return XDP_PASS;
+
 	ret = bpf_xdp_metadata_rx_hash(ctx, &hash, &rss_type);
 	if (ret < 0) {
 		key = RSS_KEY_ERR_CNT;
@@ -160,4 +204,46 @@ int xdp_rss_hash(struct xdp_md *ctx)
 	return XDP_PASS;
 }
 
+SEC("xdp")
+int xdp_rx_csum(struct xdp_md *ctx)
+{
+	enum xdp_checksum ip_summed = 0;
+	__u8 cksum_level = 0;
+	__u32 cksum = 0;
+	__u32 key, val;
+	__u32 *cnt;
+	int ret;
+
+	if (!xdp_match_setup(ctx))
+		return XDP_PASS;
+
+	ret = bpf_xdp_metadata_rx_checksum(ctx, &ip_summed, &cksum,
+					   &cksum_level);
+	if (ret < 0) {
+		key = CSUM_KEY_ERR_CNT;
+		cnt = bpf_map_lookup_elem(&map_csum, &key);
+		if (cnt)
+			__sync_fetch_and_add(cnt, 1);
+		return XDP_PASS;
+	}
+
+	key = CSUM_KEY_IP_SUMMED;
+	val = (__u32)ip_summed;
+	bpf_map_update_elem(&map_csum, &key, &val, BPF_ANY);
+
+	key = CSUM_KEY_CKSUM;
+	bpf_map_update_elem(&map_csum, &key, &cksum, BPF_ANY);
+
+	key = CSUM_KEY_LEVEL;
+	val = cksum_level;
+	bpf_map_update_elem(&map_csum, &key, &val, BPF_ANY);
+
+	key = CSUM_KEY_PKT_CNT;
+	cnt = bpf_map_lookup_elem(&map_csum, &key);
+	if (cnt)
+		__sync_fetch_and_add(cnt, 1);
+
+	return XDP_PASS;
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.47.0


^ permalink raw reply related

* [PATCH bpf-next v4 5/6] selftests/bpf: Add bpf_xdp_metadata_rx_checksum support to xdp_hw_metadat prog
From: Vladimir Vdovin @ 2026-07-08 20:34 UTC (permalink / raw)
  To: Lorenzo Bianconi, Donald Hunter, Jakub Kicinski, David S . Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Alexei Starovoitov,
	Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
	Stanislav Fomichev, Andrew Lunn, Tony Nguyen, Przemek Kitszel,
	Alexander Lobakin, Andrii Nakryiko, Martin KaFai Lau,
	Eduard Zingerman, Song Liu, Yonghong Song, KP Singh, Hao Luo,
	Jiri Olsa, Shuah Khan, Maciej Fijalkowski
  Cc: Jakub Sitnicki, Aleksandr Loktionov, netdev, bpf, intel-wired-lan,
	linux-kselftest, Vladimir Vdovin
In-Reply-To: <20260708203410.45121-1-deliran@verdict.gg>

From: Lorenzo Bianconi <lorenzo@kernel.org>

Introduce the capability to dump HW rx checksum in xdp_hw_metadata
program via bpf_xdp_metadata_rx_checksum() kfunc.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Vladimir Vdovin <deliran@verdict.gg>
---
 .../selftests/bpf/progs/xdp_hw_metadata.c     |  7 +++++
 tools/testing/selftests/bpf/xdp_hw_metadata.c | 31 +++++++++++++++++++
 tools/testing/selftests/bpf/xdp_metadata.h    | 12 ++++---
 3 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/bpf/progs/xdp_hw_metadata.c b/tools/testing/selftests/bpf/progs/xdp_hw_metadata.c
index 330ece2eabdb..5eeadb7e27cf 100644
--- a/tools/testing/selftests/bpf/progs/xdp_hw_metadata.c
+++ b/tools/testing/selftests/bpf/progs/xdp_hw_metadata.c
@@ -110,6 +110,13 @@ int rx(struct xdp_md *ctx)
 	else
 		meta->hint_valid |= XDP_META_FIELD_VLAN_TAG;
 
+	err = bpf_xdp_metadata_rx_checksum(ctx, &meta->ip_summed,
+					   &meta->cksum, &meta->cksum_level);
+	if (err)
+		meta->rx_cksum_err = err;
+	else
+		meta->hint_valid |= XDP_META_FIELD_CHECKSUM;
+
 	__sync_add_and_fetch(&pkts_redir, 1);
 	return bpf_redirect_map(&xsk, ctx->rx_queue_index, XDP_PASS);
 }
diff --git a/tools/testing/selftests/bpf/xdp_hw_metadata.c b/tools/testing/selftests/bpf/xdp_hw_metadata.c
index 6db3b5555a22..c63a70a54075 100644
--- a/tools/testing/selftests/bpf/xdp_hw_metadata.c
+++ b/tools/testing/selftests/bpf/xdp_hw_metadata.c
@@ -8,6 +8,7 @@
  * - Metadata verified:
  *   - rx_timestamp
  *   - rx_hash
+ *   - rx_checksum
  *
  * TX:
  * - UDP 9091 packets trigger TX reply
@@ -219,6 +220,30 @@ static void print_vlan_tci(__u16 tag)
 	printf("PCP=%u, DEI=%d, VID=0x%X\n", pcp, dei, vlan_id);
 }
 
+static void print_rx_cksum(__u8 ip_summed, __u32 cksum, __u8 cksum_level)
+{
+	const char *cksum_str;
+
+	switch (ip_summed) {
+	case XDP_CHECKSUM_COMPLETE | XDP_CHECKSUM_UNNECESSARY:
+		cksum_str = "CHECKSUM_COMPLETE,CHECKSUM_UNNECESSARY";
+		break;
+	case XDP_CHECKSUM_UNNECESSARY:
+		cksum_str = "CHECKSUM_UNNECESSARY";
+		break;
+	case XDP_CHECKSUM_COMPLETE:
+		cksum_str = "CHECKSUM_COMPLETE";
+		break;
+	case XDP_CHECKSUM_NONE:
+	default:
+		cksum_str = "CHECKSUM_NONE";
+		break;
+	}
+
+	printf("rx-cksum: %s, csum=0x%x, cksum_level=0x%x\n",
+	       cksum_str, cksum, cksum_level);
+}
+
 static void verify_xdp_metadata(void *data, clockid_t clock_id)
 {
 	struct xdp_meta *meta;
@@ -254,6 +279,12 @@ static void verify_xdp_metadata(void *data, clockid_t clock_id)
 		printf("No rx_vlan_tci or rx_vlan_proto, err=%d\n",
 		       meta->rx_vlan_tag_err);
 	}
+
+	if (meta->hint_valid & XDP_META_FIELD_CHECKSUM)
+		print_rx_cksum(meta->ip_summed, meta->cksum,
+			       meta->cksum_level);
+	else
+		printf("No rx_cksum, err=%d\n", meta->rx_cksum_err);
 }
 
 static void verify_skb_metadata(int fd)
diff --git a/tools/testing/selftests/bpf/xdp_metadata.h b/tools/testing/selftests/bpf/xdp_metadata.h
index bca09b94af26..f864d4a8bd8c 100644
--- a/tools/testing/selftests/bpf/xdp_metadata.h
+++ b/tools/testing/selftests/bpf/xdp_metadata.h
@@ -28,6 +28,7 @@ enum xdp_meta_field {
 	XDP_META_FIELD_TS	= BIT(0),
 	XDP_META_FIELD_RSS	= BIT(1),
 	XDP_META_FIELD_VLAN_TAG	= BIT(2),
+	XDP_META_FIELD_CHECKSUM = BIT(3),
 };
 
 #define XDP_CHECKSUM_NONE		BIT(0)
@@ -52,10 +53,13 @@ struct xdp_meta {
 		};
 		__s32 rx_vlan_tag_err;
 	};
-	struct {
-		__u32 ip_summed;
-		__u32 cksum;
-		__u8 cksum_level;
+	union {
+		struct {
+			__u32 ip_summed;
+			__u32 cksum;
+			__u8 cksum_level;
+		};
+		__s32 rx_cksum_err;
 	};
 	enum xdp_meta_field hint_valid;
 };
-- 
2.47.0


^ permalink raw reply related

* [PATCH bpf-next v4 4/6] selftests/bpf: Add selftest support for bpf_xdp_metadata_rx_checksum
From: Vladimir Vdovin @ 2026-07-08 20:34 UTC (permalink / raw)
  To: Lorenzo Bianconi, Donald Hunter, Jakub Kicinski, David S . Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Alexei Starovoitov,
	Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
	Stanislav Fomichev, Andrew Lunn, Tony Nguyen, Przemek Kitszel,
	Alexander Lobakin, Andrii Nakryiko, Martin KaFai Lau,
	Eduard Zingerman, Song Liu, Yonghong Song, KP Singh, Hao Luo,
	Jiri Olsa, Shuah Khan, Maciej Fijalkowski
  Cc: Jakub Sitnicki, Aleksandr Loktionov, netdev, bpf, intel-wired-lan,
	linux-kselftest, Vladimir Vdovin
In-Reply-To: <20260708203410.45121-1-deliran@verdict.gg>

From: Lorenzo Bianconi <lorenzo@kernel.org>

Introduce dedicated selftest for bpf_xdp_metadata_rx_checksum kfunc to
bpf selftest framework.

Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Vladimir Vdovin <deliran@verdict.gg>
---
 tools/testing/selftests/bpf/prog_tests/xdp_metadata.c | 9 +++++++++
 tools/testing/selftests/bpf/progs/xdp_metadata.c      | 2 ++
 tools/testing/selftests/bpf/xdp_metadata.h            | 9 +++++++++
 3 files changed, 20 insertions(+)

diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_metadata.c b/tools/testing/selftests/bpf/prog_tests/xdp_metadata.c
index 5c31054ad4a4..91de61c822f5 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_metadata.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_metadata.c
@@ -310,6 +310,15 @@ static int verify_xsk_metadata(struct xsk *xsk, bool sent_from_af_xdp)
 	if (!ASSERT_NEQ(meta->rx_hash, 0, "rx_hash"))
 		return -1;
 
+	if (!ASSERT_EQ(meta->ip_summed, XDP_CHECKSUM_NONE, "rx_ip_summed"))
+		return -1;
+
+	if (!ASSERT_EQ(meta->cksum, 0, "rx_cksum"))
+		return -1;
+
+	if (!ASSERT_EQ(meta->cksum_level, 0, "rx_cksum_level"))
+		return -1;
+
 	if (!sent_from_af_xdp) {
 		if (!ASSERT_NEQ(meta->rx_hash_type & XDP_RSS_TYPE_L4, 0, "rx_hash_type"))
 			return -1;
diff --git a/tools/testing/selftests/bpf/progs/xdp_metadata.c b/tools/testing/selftests/bpf/progs/xdp_metadata.c
index 09bb8a038d52..af1e19d48d67 100644
--- a/tools/testing/selftests/bpf/progs/xdp_metadata.c
+++ b/tools/testing/selftests/bpf/progs/xdp_metadata.c
@@ -98,6 +98,8 @@ int rx(struct xdp_md *ctx)
 	bpf_xdp_metadata_rx_hash(ctx, &meta->rx_hash, &meta->rx_hash_type);
 	bpf_xdp_metadata_rx_vlan_tag(ctx, &meta->rx_vlan_proto,
 				     &meta->rx_vlan_tci);
+	bpf_xdp_metadata_rx_checksum(ctx, &meta->ip_summed, &meta->cksum,
+				     &meta->cksum_level);
 
 	return bpf_redirect_map(&xsk, ctx->rx_queue_index, XDP_PASS);
 }
diff --git a/tools/testing/selftests/bpf/xdp_metadata.h b/tools/testing/selftests/bpf/xdp_metadata.h
index 87318ad1117a..bca09b94af26 100644
--- a/tools/testing/selftests/bpf/xdp_metadata.h
+++ b/tools/testing/selftests/bpf/xdp_metadata.h
@@ -30,6 +30,10 @@ enum xdp_meta_field {
 	XDP_META_FIELD_VLAN_TAG	= BIT(2),
 };
 
+#define XDP_CHECKSUM_NONE		BIT(0)
+#define XDP_CHECKSUM_UNNECESSARY	BIT(1)
+#define XDP_CHECKSUM_COMPLETE		BIT(2)
+
 struct xdp_meta {
 	union {
 		__u64 rx_timestamp;
@@ -48,5 +52,10 @@ struct xdp_meta {
 		};
 		__s32 rx_vlan_tag_err;
 	};
+	struct {
+		__u32 ip_summed;
+		__u32 cksum;
+		__u8 cksum_level;
+	};
 	enum xdp_meta_field hint_valid;
 };
-- 
2.47.0


^ permalink raw reply related

* [PATCH bpf-next v4 3/6] net: ice: Add xmo_rx_checksum callback
From: Vladimir Vdovin @ 2026-07-08 20:34 UTC (permalink / raw)
  To: Lorenzo Bianconi, Donald Hunter, Jakub Kicinski, David S . Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Alexei Starovoitov,
	Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
	Stanislav Fomichev, Andrew Lunn, Tony Nguyen, Przemek Kitszel,
	Alexander Lobakin, Andrii Nakryiko, Martin KaFai Lau,
	Eduard Zingerman, Song Liu, Yonghong Song, KP Singh, Hao Luo,
	Jiri Olsa, Shuah Khan, Maciej Fijalkowski
  Cc: Jakub Sitnicki, Aleksandr Loktionov, netdev, bpf, intel-wired-lan,
	linux-kselftest, Vladimir Vdovin
In-Reply-To: <20260708203410.45121-1-deliran@verdict.gg>

From: Lorenzo Bianconi <lorenzo@kernel.org>

Implement xmo_rx_checksum callback in ice driver to report RX checksum
result to the eBPF program bounded to the NIC.
Introduce ice_get_rx_csum utility routine in order to make the rx checksum
code reusable from ice_rx_csum()

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Vladimir Vdovin <deliran@verdict.gg>
---
 drivers/net/ethernet/intel/ice/ice_txrx_lib.c | 123 ++++++++++++------
 1 file changed, 81 insertions(+), 42 deletions(-)

diff --git a/drivers/net/ethernet/intel/ice/ice_txrx_lib.c b/drivers/net/ethernet/intel/ice/ice_txrx_lib.c
index e695a664e53d..3aa82ff03d9e 100644
--- a/drivers/net/ethernet/intel/ice/ice_txrx_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_txrx_lib.c
@@ -78,69 +78,48 @@ ice_rx_hash_to_skb(const struct ice_rx_ring *rx_ring,
 		libeth_rx_pt_set_hash(skb, hash, decoded);
 }
 
-/**
- * ice_rx_gcs - Set generic checksum in skb
- * @skb: skb currently being received and modified
- * @rx_desc: receive descriptor
- */
-static void ice_rx_gcs(struct sk_buff *skb,
-		       const union ice_32b_rx_flex_desc *rx_desc)
-{
-	const struct ice_32b_rx_flex_desc_nic *desc;
-	u16 csum;
-
-	desc = (struct ice_32b_rx_flex_desc_nic *)rx_desc;
-	skb->ip_summed = CHECKSUM_COMPLETE;
-	csum = (__force u16)desc->raw_csum;
-	skb->csum = csum_unfold((__force __sum16)swab16(csum));
-}
-
-/**
- * ice_rx_csum - Indicate in skb if checksum is good
- * @ring: the ring we care about
- * @skb: skb currently being received and modified
- * @rx_desc: the receive descriptor
- * @ptype: the packet type decoded by hardware
- *
- * skb->protocol must be set before this function is called
- */
 static void
-ice_rx_csum(struct ice_rx_ring *ring, struct sk_buff *skb,
-	    union ice_32b_rx_flex_desc *rx_desc, u16 ptype)
+ice_get_rx_csum(const union ice_32b_rx_flex_desc *rx_desc, u16 ptype,
+		struct ice_rx_ring *ring, enum xdp_checksum *ip_summed,
+		u32 *cksum, u8 *cksum_level)
 {
-	struct libeth_rx_pt decoded;
+	struct libeth_rx_pt decoded = libie_rx_pt_parse(ptype);
 	u16 rx_status0, rx_status1;
 	bool ipv4, ipv6;
 
-	/* Start with CHECKSUM_NONE and by default csum_level = 0 */
-	skb->ip_summed = CHECKSUM_NONE;
-
-	decoded = libie_rx_pt_parse(ptype);
 	if (!libeth_rx_pt_has_checksum(ring->netdev, decoded))
-		return;
+		goto checksum_none;
 
 	rx_status0 = le16_to_cpu(rx_desc->wb.status_error0);
 	rx_status1 = le16_to_cpu(rx_desc->wb.status_error1);
-
 	if ((ring->flags & ICE_RX_FLAGS_RING_GCS) &&
 	    rx_desc->wb.rxdid == ICE_RXDID_FLEX_NIC &&
 	    (decoded.inner_prot == LIBETH_RX_PT_INNER_TCP ||
 	     decoded.inner_prot == LIBETH_RX_PT_INNER_UDP ||
 	     decoded.inner_prot == LIBETH_RX_PT_INNER_ICMP)) {
-		ice_rx_gcs(skb, rx_desc);
+		const struct ice_32b_rx_flex_desc_nic *desc;
+		__wsum wcsum;
+		u16 csum;
+
+		desc = (struct ice_32b_rx_flex_desc_nic *)rx_desc;
+		*ip_summed = XDP_CHECKSUM_COMPLETE;
+		csum = (__force u16)desc->raw_csum;
+		wcsum = csum_unfold((__force __sum16)swab16(csum));
+		*cksum = (__force u32)wcsum;
+		*cksum_level = 0;
 		return;
 	}
 
 	/* check if HW has decoded the packet and checksum */
 	if (!(rx_status0 & BIT(ICE_RX_FLEX_DESC_STATUS0_L3L4P_S)))
-		return;
+		goto checksum_none;
 
 	ipv4 = libeth_rx_pt_get_ip_ver(decoded) == LIBETH_RX_PT_OUTER_IPV4;
 	ipv6 = libeth_rx_pt_get_ip_ver(decoded) == LIBETH_RX_PT_OUTER_IPV6;
 
 	if (ipv4 && (rx_status0 & (BIT(ICE_RX_FLEX_DESC_STATUS0_XSUM_EIPE_S)))) {
 		ring->vsi->back->hw_rx_eipe_error++;
-		return;
+		goto checksum_none;
 	}
 
 	if (ipv4 && (rx_status0 & (BIT(ICE_RX_FLEX_DESC_STATUS0_XSUM_IPE_S))))
@@ -164,14 +143,51 @@ ice_rx_csum(struct ice_rx_ring *ring, struct sk_buff *skb,
 	 * we need to bump the checksum level by 1 to reflect the fact that
 	 * we are indicating we validated the inner checksum.
 	 */
-	if (decoded.tunnel_type >= LIBETH_RX_PT_TUNNEL_IP_GRENAT)
-		skb->csum_level = 1;
-
-	skb->ip_summed = CHECKSUM_UNNECESSARY;
+	*cksum_level = decoded.tunnel_type >= LIBETH_RX_PT_TUNNEL_IP_GRENAT;
+	*ip_summed = XDP_CHECKSUM_UNNECESSARY;
+	*cksum = 0;
 	return;
 
 checksum_fail:
 	ring->vsi->back->hw_csum_rx_error++;
+checksum_none:
+	*ip_summed = XDP_CHECKSUM_NONE;
+	*cksum_level = 0;
+	*cksum = 0;
+}
+
+/**
+ * ice_rx_csum - Indicate in skb if checksum is good
+ * @ring: the ring we care about
+ * @skb: skb currently being received and modified
+ * @rx_desc: the receive descriptor
+ * @ptype: the packet type decoded by hardware
+ *
+ * skb->protocol must be set before this function is called
+ */
+static void
+ice_rx_csum(struct ice_rx_ring *ring, struct sk_buff *skb,
+	    union ice_32b_rx_flex_desc *rx_desc, u16 ptype)
+{
+	enum xdp_checksum ip_summed;
+	u8 cksum_level;
+	u32 cksum;
+
+	ice_get_rx_csum(rx_desc, ptype, ring, &ip_summed, &cksum,
+			&cksum_level);
+	switch (ip_summed) {
+	case XDP_CHECKSUM_UNNECESSARY:
+		skb->ip_summed = CHECKSUM_UNNECESSARY;
+		skb->csum_level = cksum_level;
+		break;
+	case XDP_CHECKSUM_COMPLETE:
+		skb->ip_summed = CHECKSUM_COMPLETE;
+		skb->csum = (__force __wsum)cksum;
+		break;
+	default:
+		skb->ip_summed = CHECKSUM_NONE;
+		break;
+	}
 }
 
 /**
@@ -566,6 +582,28 @@ static int ice_xdp_rx_hash(const struct xdp_md *ctx, u32 *hash,
 	return 0;
 }
 
+/**
+ * ice_xdp_rx_checksum - RX checksum XDP hint handler
+ * @ctx: XDP buff pointer
+ * @ip_summed: RX checksum result destination address
+ * @cksum: RX checksum value destination address
+ * @cksum_level: RX checksum level value destination address
+ */
+static int ice_xdp_rx_checksum(const struct xdp_md *ctx,
+			       enum xdp_checksum *ip_summed,
+			       u32 *cksum, u8 *cksum_level)
+{
+	const struct libeth_xdp_buff *xdp_ext = (void *)ctx;
+	const union ice_32b_rx_flex_desc *rx_desc = xdp_ext->desc;
+	struct ice_rx_ring *ring;
+
+	ring = libeth_xdp_buff_to_rq(xdp_ext, typeof(*ring), xdp_rxq);
+	ice_get_rx_csum(rx_desc, ice_get_ptype(rx_desc), ring, ip_summed,
+			cksum, cksum_level);
+
+	return 0;
+}
+
 /**
  * ice_xdp_rx_vlan_tag - VLAN tag XDP hint handler
  * @ctx: XDP buff pointer
@@ -598,4 +636,5 @@ const struct xdp_metadata_ops ice_xdp_md_ops = {
 	.xmo_rx_timestamp		= ice_xdp_rx_hw_ts,
 	.xmo_rx_hash			= ice_xdp_rx_hash,
 	.xmo_rx_vlan_tag		= ice_xdp_rx_vlan_tag,
+	.xmo_rx_checksum		= ice_xdp_rx_checksum,
 };
-- 
2.47.0


^ permalink raw reply related

* [PATCH bpf-next v4 2/6] net: veth: Add xmo_rx_checksum callback to veth driver
From: Vladimir Vdovin @ 2026-07-08 20:34 UTC (permalink / raw)
  To: Lorenzo Bianconi, Donald Hunter, Jakub Kicinski, David S . Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Alexei Starovoitov,
	Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
	Stanislav Fomichev, Andrew Lunn, Tony Nguyen, Przemek Kitszel,
	Alexander Lobakin, Andrii Nakryiko, Martin KaFai Lau,
	Eduard Zingerman, Song Liu, Yonghong Song, KP Singh, Hao Luo,
	Jiri Olsa, Shuah Khan, Maciej Fijalkowski
  Cc: Jakub Sitnicki, Aleksandr Loktionov, netdev, bpf, intel-wired-lan,
	linux-kselftest, Vladimir Vdovin
In-Reply-To: <20260708203410.45121-1-deliran@verdict.gg>

From: Lorenzo Bianconi <lorenzo@kernel.org>

Implement xmo_rx_checksum callback in veth driver to report RX checksum
result to the eBPF program bounded to the veth device.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Vladimir Vdovin <deliran@verdict.gg>
---
 drivers/net/veth.c | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/drivers/net/veth.c b/drivers/net/veth.c
index 1c5142149175..498d894d043d 100644
--- a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -1700,6 +1700,37 @@ static int veth_xdp_rx_vlan_tag(const struct xdp_md *ctx, __be16 *vlan_proto,
 	return err;
 }
 
+static int veth_xdp_rx_checksum(const struct xdp_md *ctx,
+				enum xdp_checksum *ip_summed,
+				u32 *cksum, u8 *cksum_level)
+{
+	const struct veth_xdp_buff *_ctx = (void *)ctx;
+	const struct sk_buff *skb = _ctx->skb;
+
+	if (!skb)
+		return -ENODATA;
+
+	switch (skb->ip_summed) {
+	case CHECKSUM_COMPLETE:
+		*ip_summed = XDP_CHECKSUM_COMPLETE;
+		*cksum = skb->csum;
+		*cksum_level = 0;
+		break;
+	case CHECKSUM_UNNECESSARY:
+		*ip_summed = XDP_CHECKSUM_UNNECESSARY;
+		*cksum_level = skb->csum_level;
+		*cksum = 0;
+		break;
+	default:
+		*ip_summed = XDP_CHECKSUM_NONE;
+		*cksum_level = 0;
+		*cksum = 0;
+		break;
+	}
+
+	return 0;
+}
+
 static const struct net_device_ops veth_netdev_ops = {
 	.ndo_init            = veth_dev_init,
 	.ndo_open            = veth_open,
@@ -1725,6 +1756,7 @@ static const struct xdp_metadata_ops veth_xdp_metadata_ops = {
 	.xmo_rx_timestamp		= veth_xdp_rx_timestamp,
 	.xmo_rx_hash			= veth_xdp_rx_hash,
 	.xmo_rx_vlan_tag		= veth_xdp_rx_vlan_tag,
+	.xmo_rx_checksum		= veth_xdp_rx_checksum,
 };
 
 #define VETH_FEATURES (NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HW_CSUM | \
-- 
2.47.0


^ permalink raw reply related

* [PATCH bpf-next v4 1/6] netlink: specs: Add XDP RX checksum capability to XDP metadata specs
From: Vladimir Vdovin @ 2026-07-08 20:34 UTC (permalink / raw)
  To: Lorenzo Bianconi, Donald Hunter, Jakub Kicinski, David S . Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Alexei Starovoitov,
	Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
	Stanislav Fomichev, Andrew Lunn, Tony Nguyen, Przemek Kitszel,
	Alexander Lobakin, Andrii Nakryiko, Martin KaFai Lau,
	Eduard Zingerman, Song Liu, Yonghong Song, KP Singh, Hao Luo,
	Jiri Olsa, Shuah Khan, Maciej Fijalkowski
  Cc: Jakub Sitnicki, Aleksandr Loktionov, netdev, bpf, intel-wired-lan,
	linux-kselftest, Vladimir Vdovin
In-Reply-To: <20260708203410.45121-1-deliran@verdict.gg>

From: Lorenzo Bianconi <lorenzo@kernel.org>

Introduce XDP RX checksum capability to XDP metadata specs. XDP RX
checksum will be use by devices capable of exposing receive checksum
result via bpf_xdp_metadata_rx_checksum().
Moreover, introduce xmo_rx_checksum netdev callback in order to allow
the eBPF program bound to the device to retrieve the RX checksum result
computed by the hw NIC and reported via DMA descriptors.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Vladimir Vdovin <deliran@verdict.gg>
---
 Documentation/netlink/specs/netdev.yaml |  5 ++++
 include/net/xdp.h                       | 18 ++++++++++++++
 include/uapi/linux/netdev.h             |  3 +++
 net/core/xdp.c                          | 32 +++++++++++++++++++++++++
 tools/include/uapi/linux/netdev.h       |  3 +++
 5 files changed, 61 insertions(+)

diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml
index 5f143da7458c..6d0d90d3a614 100644
--- a/Documentation/netlink/specs/netdev.yaml
+++ b/Documentation/netlink/specs/netdev.yaml
@@ -61,6 +61,11 @@ definitions:
         doc: |
           Device is capable of exposing receive packet VLAN tag via
           bpf_xdp_metadata_rx_vlan_tag().
+      -
+        name: checksum
+        doc: |
+          Device is capable of exposing receive checksum result via
+          bpf_xdp_metadata_rx_checksum().
   -
     type: flags
     name: xsk-flags
diff --git a/include/net/xdp.h b/include/net/xdp.h
index aa742f413c35..e255ff786131 100644
--- a/include/net/xdp.h
+++ b/include/net/xdp.h
@@ -586,6 +586,10 @@ void xdp_attachment_setup(struct xdp_attachment_info *info,
 			   NETDEV_XDP_RX_METADATA_VLAN_TAG, \
 			   bpf_xdp_metadata_rx_vlan_tag, \
 			   xmo_rx_vlan_tag) \
+	XDP_METADATA_KFUNC(XDP_METADATA_KFUNC_RX_CHECKSUM, \
+			   NETDEV_XDP_RX_METADATA_CHECKSUM, \
+			   bpf_xdp_metadata_rx_checksum, \
+			   xmo_rx_checksum)
 
 enum xdp_rx_metadata {
 #define XDP_METADATA_KFUNC(name, _, __, ___) name,
@@ -643,12 +647,26 @@ enum xdp_rss_hash_type {
 	XDP_RSS_TYPE_L4_IPV6_SCTP_EX = XDP_RSS_TYPE_L4_IPV6_SCTP | XDP_RSS_L3_DYNHDR,
 };
 
+/* Please note the driver is required to invalidate the checksum if the NIC
+ * reports CHECKSUM_UNNECESSARY or CHECKSUM_COMPLETE and the eBPF program
+ * modifies the packet since it can change some fields validated by the
+ * checksum.
+ */
+enum xdp_checksum {
+	XDP_CHECKSUM_NONE		= BIT(CHECKSUM_NONE),
+	XDP_CHECKSUM_UNNECESSARY	= BIT(CHECKSUM_UNNECESSARY),
+	XDP_CHECKSUM_COMPLETE		= BIT(CHECKSUM_COMPLETE),
+};
+
 struct xdp_metadata_ops {
 	int	(*xmo_rx_timestamp)(const struct xdp_md *ctx, u64 *timestamp);
 	int	(*xmo_rx_hash)(const struct xdp_md *ctx, u32 *hash,
 			       enum xdp_rss_hash_type *rss_type);
 	int	(*xmo_rx_vlan_tag)(const struct xdp_md *ctx, __be16 *vlan_proto,
 				   u16 *vlan_tci);
+	int	(*xmo_rx_checksum)(const struct xdp_md *ctx,
+				   enum xdp_checksum *ip_summed,
+				   u32 *cksum, u8 *cksum_level);
 };
 
 #ifdef CONFIG_NET
diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h
index 2f3ab75e8cc0..f8caade93c8c 100644
--- a/include/uapi/linux/netdev.h
+++ b/include/uapi/linux/netdev.h
@@ -47,11 +47,14 @@ enum netdev_xdp_act {
  *   hash via bpf_xdp_metadata_rx_hash().
  * @NETDEV_XDP_RX_METADATA_VLAN_TAG: Device is capable of exposing receive
  *   packet VLAN tag via bpf_xdp_metadata_rx_vlan_tag().
+ * @NETDEV_XDP_RX_METADATA_CHECKSUM: Device is capable of exposing receive
+ *   checksum result via bpf_xdp_metadata_rx_checksum().
  */
 enum netdev_xdp_rx_metadata {
 	NETDEV_XDP_RX_METADATA_TIMESTAMP = 1,
 	NETDEV_XDP_RX_METADATA_HASH = 2,
 	NETDEV_XDP_RX_METADATA_VLAN_TAG = 4,
+	NETDEV_XDP_RX_METADATA_CHECKSUM = 8,
 };
 
 /**
diff --git a/net/core/xdp.c b/net/core/xdp.c
index 9890a30584ba..9bcaa423ad17 100644
--- a/net/core/xdp.c
+++ b/net/core/xdp.c
@@ -961,6 +961,38 @@ __bpf_kfunc int bpf_xdp_metadata_rx_vlan_tag(const struct xdp_md *ctx,
 	return -EOPNOTSUPP;
 }
 
+/**
+ * bpf_xdp_metadata_rx_checksum - Read XDP frame RX checksum.
+ * @ctx: XDP context pointer.
+ * @ip_summed: Return value pointer to a bitmask indicating available checksums.
+ * @cksum: Return value pointer indicating the hw checksum value.
+ * @cksum_level: Return value pointer indicating the checksum level result.
+ *
+ * In case of success, ``ip_summed`` is set to the RX checksum result. Possible
+ * values are:
+ * ``XDP_CHECKSUM_NONE``
+ * ``XDP_CHECKSUM_UNNECESSARY``
+ * ``XDP_CHECKSUM_COMPLETE``
+ * ``XDP_CHECKSUM_COMPLETE`` | ``XDP_CHECKSUM_UNNECESSARY``
+ *
+ * In case of success, ``cksum`` contains the checksum value calculated by the
+ * NIC. ``cksum`` is valid only if ``XDP_CHECKSUM_COMPLETE`` is set in
+ * ``ip_summed``. ``cksum_level`` contains the checksum level reported by the
+ * hw. ``cksum_level`` can be considered valid only if
+ * ``XDP_CHECKSUM_UNNECESSARY`` is set in ``ip_summed``.
+ *
+ * Return:
+ * * Returns 0 on success or ``-errno`` on error.
+ * * ``-EOPNOTSUPP`` : means device driver does not implement kfunc
+ * * ``-ENODATA``    : means no RX-checksum available for this frame
+ */
+__bpf_kfunc int bpf_xdp_metadata_rx_checksum(const struct xdp_md *ctx,
+					     enum xdp_checksum *ip_summed,
+					     u32 *cksum, u8 *cksum_level)
+{
+	return -EOPNOTSUPP;
+}
+
 __bpf_kfunc_end_defs();
 
 BTF_KFUNCS_START(xdp_metadata_kfunc_ids)
diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h
index 2f3ab75e8cc0..f8caade93c8c 100644
--- a/tools/include/uapi/linux/netdev.h
+++ b/tools/include/uapi/linux/netdev.h
@@ -47,11 +47,14 @@ enum netdev_xdp_act {
  *   hash via bpf_xdp_metadata_rx_hash().
  * @NETDEV_XDP_RX_METADATA_VLAN_TAG: Device is capable of exposing receive
  *   packet VLAN tag via bpf_xdp_metadata_rx_vlan_tag().
+ * @NETDEV_XDP_RX_METADATA_CHECKSUM: Device is capable of exposing receive
+ *   checksum result via bpf_xdp_metadata_rx_checksum().
  */
 enum netdev_xdp_rx_metadata {
 	NETDEV_XDP_RX_METADATA_TIMESTAMP = 1,
 	NETDEV_XDP_RX_METADATA_HASH = 2,
 	NETDEV_XDP_RX_METADATA_VLAN_TAG = 4,
+	NETDEV_XDP_RX_METADATA_CHECKSUM = 8,
 };
 
 /**
-- 
2.47.0


^ permalink raw reply related

* [PATCH bpf-next v4 0/6] Add the capability to load HW RX checksum in eBPF programs
From: Vladimir Vdovin @ 2026-07-08 20:34 UTC (permalink / raw)
  To: Lorenzo Bianconi, Donald Hunter, Jakub Kicinski, David S . Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Alexei Starovoitov,
	Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
	Stanislav Fomichev, Andrew Lunn, Tony Nguyen, Przemek Kitszel,
	Alexander Lobakin, Andrii Nakryiko, Martin KaFai Lau,
	Eduard Zingerman, Song Liu, Yonghong Song, KP Singh, Hao Luo,
	Jiri Olsa, Shuah Khan, Maciej Fijalkowski
  Cc: Jakub Sitnicki, Aleksandr Loktionov, netdev, bpf, intel-wired-lan,
	linux-kselftest, Vladimir Vdovin

Introduce bpf_xdp_metadata_rx_checksum() kfunc in order to load the HW
RX checksum results in the eBPF program bound to the NIC.
Implement xmo_rx_checksum callback for veth and ice drivers.

If the hardware detects a wrong/failed checksum, it will report
CHECKSUM_NONE in the packet metadata. Moreover, CHECKSUM_NONE will be
returned even if the NIC can't parse the packet (e.g. if it does not
support a specific protocol). A possible use case for
bpf_xdp_metadata_rx_checksum() would be to implement a XDP DDoS
application [1] combining the info from bpf_xdp_metadata_rx_checksum()
and bpf_xdp_metadata_rx_hash() kfuncs in order to filter packets with a
wrong/failed checksum.

This is a repost of Lorenzo's series, rebased on top of net-next, with
a new drv-net selftest for the RX checksum metadata added on top of it,
as Lorenzo suggested [2].

A note on the new selftest semantics: for traffic with a correct
checksum the test requires a usable verdict (CHECKSUM_UNNECESSARY
and/or CHECKSUM_COMPLETE) but reports SKIP on CHECKSUM_NONE, since the
API allows devices that do not verify the packets (e.g. veth reports
NONE for locally generated CHECKSUM_PARTIAL traffic). For corrupted
packets it only asserts that CHECKSUM_UNNECESSARY is not set, as
CHECKSUM_COMPLETE legitimately carries the raw checksum of bad packets
too.

[1] https://blog.cloudflare.com/unimog-cloudflares-edge-load-balancer/
[2] https://lore.kernel.org/r/ak5ox296M46FAcWX@lore-desk

---
Changes in v4:
- Report ip_summed as a bitmask of XDP_CHECKSUM_* values and return the
  hw checksum and the checksum level via dedicated
  bpf_xdp_metadata_rx_checksum() arguments
- Rebase on top of net-next
- Add a drv-net selftest for the XDP RX checksum metadata
- Link to v3: https://lore.kernel.org/r/20260217-bpf-xdp-meta-rxcksum-v3-0-30024c50ba71@kernel.org

Changes in v3:
- Remove leftover assignment from v2 in veth_xdp_rx_checksum()
- Fix typos
- Fix commit logs
- Link to v2: https://lore.kernel.org/r/20260213-bpf-xdp-meta-rxcksum-v2-0-a82c4802afbe@kernel.org

Changes in v2:
- Remove XDP_CHECKSUM_PARTIAL definition
- Improve veth_xdp_rx_checksum() callback
- Fix uninitialized case for cksum_meta in ice_get_rx_csum()
- Fix sparse warnings in ice driver
- Fix typos
- Link to v1: https://lore.kernel.org/r/20260210-bpf-xdp-meta-rxcksum-v1-0-e5d55caa0541@kernel.org

Changes in v1:
- Rebase on top of bpf-next
- Test ice driver using xdp_hw_metadata tool available in the bpf
  kernel selftest
- Improve cover letter with an use-case for
  bpf_xdp_metadata_rx_checksum()
- Link to RFC v2: https://lore.kernel.org/r/20250925-bpf-xdp-meta-rxcksum-v2-0-6b3fe987ce91@kernel.org

change-id: 20250925-bpf-xdp-meta-rxcksum-900685e2909d

Lorenzo Bianconi (5):
  netlink: specs: Add XDP RX checksum capability to XDP metadata specs
  net: veth: Add xmo_rx_checksum callback to veth driver
  net: ice: Add xmo_rx_checksum callback
  selftests/bpf: Add selftest support for bpf_xdp_metadata_rx_checksum
  selftests/bpf: Add bpf_xdp_metadata_rx_checksum support to
    xdp_hw_metadat prog

Vladimir Vdovin (1):
  selftests: drv-net: add XDP RX checksum metadata tests

 Documentation/netlink/specs/netdev.yaml       |   5 +
 drivers/net/ethernet/intel/ice/ice_txrx_lib.c | 123 ++++++++++++------
 drivers/net/veth.c                            |  32 +++++
 include/net/xdp.h                             |  18 +++
 include/uapi/linux/netdev.h                   |   3 +
 net/core/xdp.c                                |  32 +++++
 tools/include/uapi/linux/netdev.h             |   3 +
 .../selftests/bpf/prog_tests/xdp_metadata.c   |   9 ++
 .../selftests/bpf/progs/xdp_hw_metadata.c     |   7 +
 .../selftests/bpf/progs/xdp_metadata.c        |   2 +
 tools/testing/selftests/bpf/xdp_hw_metadata.c |  31 +++++
 tools/testing/selftests/bpf/xdp_metadata.h    |  13 ++
 .../selftests/drivers/net/hw/xdp_metadata.py  | 110 ++++++++++++++++
 .../selftests/net/lib/xdp_metadata.bpf.c      | 112 ++++++++++++++--
 14 files changed, 445 insertions(+), 55 deletions(-)


base-commit: 08030ddb87b4c6c6a2c03c82731b5e188f02f5b9
-- 
2.47.0


^ permalink raw reply

* Re: [PATCH net-next v11 7/7] selftests: netconsole: validate target resume
From: Andre Carvalho @ 2026-07-08 20:24 UTC (permalink / raw)
  To: Matthieu Baerts
  Cc: Breno Leitao, netdev, linux-kernel, linux-kselftest, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Shuah Khan, Simon Horman
In-Reply-To: <f398373e-2cb4-4649-a491-9763df94d98b@kernel.org>

Hello Matthieu,

On Wed, Jul 08, 2026 at 05:50:53PM +0200, Matthieu Baerts wrote:
> 
> > +function trigger_reactivation() {
> > +	# Add back low level module
> > +	modprobe netdevsim
> > +	# Recreate namespace and two interfaces
> > +	set_network
> > +	# Restore MACs
> > +	ip netns exec "${NAMESPACE}" ip link set "${DSTIF}" \
> > +		address "${SAVED_DSTMAC}"
> > +	if [ "${BINDMODE}" == "mac" ]; then
> > +		ip link set dev "${SRCIF}" down
> > +		ip link set dev "${SRCIF}" address "${SAVED_SRCMAC}"
> > +		# Rename device in order to trigger target resume, as initial
> > +		# when device was recreated it didn't have correct mac address.
> > +		ip link set dev "${SRCIF}" name "${TARGET}"
> 
> When I execute the test, the "ifname" bind mode works without issues,
> but the "mac" one not. From what I see, the socat process doesn't get
> any UDP packet when expected. I wonder if the problem might not come
> from here: the interface is disabled before changing the MAC address and
> renaming the interface, but not re-enabled at the end. Is it normal?

Yes, the expectation is that the target would be automatically re-enabled by
netconsole. That is why we don't 'up' the interface here explicitly.

The problem is that the test has an implict dependency on interface mac address
changing when we recreate them, which is why we have to do this whole down/update/rename
flow to trigger the reactivation in this case.

> If I add 'up' at the end of this last line here, or if I remove the
> whole if-statement block, the test passes.

For cases where the mac is persistent (e.g with systemd MACAddressPolicy=persistent),
the actual re-enablement should "just work", which I suspect is why it works when
you remove the whole if-statement block.

I think to make the test more robust to such scenarios, we should skip the
if-statement block when we detect that the interface came back with the same mac
as previously. Something like this:

if [ "${BINDMODE}" == "mac" ]; then
	CURR_SRCMAC=$(mac_get "${SRCIF}")
	if [ "${CURR_SRCMAC}" == "${SAVED_SRCMAC}" ]; then
		# Interface came back with same mac, no need to restore
		return
	fi
	ip link set dev "${SRCIF}" down
	ip link set dev "${SRCIF}" address "${SAVED_SRCMAC}"
	# Rename device in order to trigger target resume, as initial
	# when device was recreated it didn't have correct mac address.
	ip link set dev "${SRCIF}" name "${TARGET}"
fi

Could you confirm if this fixes the selftest in your environment?

> The "network logging resumed on interface" seems to suggest that the
> previous patch of this series here, commit 220dbe3c76ed ("netconsole:
> resume previously deactivated target"), managed to resume the previously
> activated target, but not in my case.

Thanks for including the logs, this helps quite a bit and I think confirms that
the interface comes back with the same mac address as previously, and is indeed
resumed, I believe the down/update/rename portion of the test then drops the target
and causes the failure.

> What I don't understand is why is it working on the Netdev CI, and not
> on my side. The main difference is that I might be missing some
> userspace packages -- but I don't see what can be missing here, all
> other netconsole tests pass -- a specific kernel config, or not applied
> patch. Or something different on the host -- in a container on my side
> -- but there shouldn't be any interactions with the host here,
> everything is happening in the VM. Any ideas? :)

Netdev CI executes with MACAddressPolicy=none (which is why I added the if-statement
block [1]) and I think it might explain the behaviour difference.

> 
> > +	echo "Running with bind mode: ${BINDMODE}" >&2
> > +	# Set current loglevel to KERN_INFO(6), and default to KERN_NOTICE(5)
> > +	echo "6 5" > /proc/sys/kernel/printk
> 
> Can we remove this? Without that, it is hard to understand what went
> wrong in case of issues.

Can you elaborate? This is typical for netconsole tests and I think required to
ensure the write will show up on netconsole. I'm not opposed to changing it if
there is a reason, but we probably want to make it consistent with other tests too.

> > +	# Send the message
> > +	echo "${MSG}: ${TARGET}" > /dev/kmsg
> > +	# Wait until socat saves the file to disk
> > +	busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}"
> 
> In my case, the script was stopping here, without any message. I can
> send a patch adding "|| true" to go to the next instruction, and display
> "FAIL: File was not generated.".
> 

This seems reasonable to me.

> > +	# Make sure the message was received in the dst part
> > +	# and exit
> > +	validate_msg "${OUTPUT_FILE}"
> > +
> > +	# kill socat in case it is still running
> > +	pkill_socat
> > +	# Cleanup & unload the module
> > +	cleanup
> > +
> > +	echo "${BINDMODE} : Test passed" >&2
> > +done
> > +
> > +trap - EXIT
> > +exit "${EXIT_STATUS}"
> > 
> 
> Cheers,
> Matt

[1] https://lore.kernel.org/all/20260117092227.741cba3b@kernel.org/

-- 
Andre Carvalho

^ permalink raw reply

* Re: [PATCH v4 1/3] drm/drm_ras: Add drm_ras netlink error event
From: Rodrigo Vivi @ 2026-07-08 20:21 UTC (permalink / raw)
  To: Tauro, Riana
  Cc: intel-xe, aravind.iddamsetty, kuba, anshuman.gupta,
	joonas.lahtinen, simona.vetter, airlied, pratik.bari,
	joshua.santosh.ranjan, ashwin.kumar.kulkarni, shubham.kumar,
	ravi.kishore.koppuravuri, raag.jadav, maarten.lankhorst,
	mallesh.koujalagi, soham.purkait, Zack McKevitt, Lijo Lazar,
	Hawking Zhang, David S. Miller, Paolo Abeni, Eric Dumazet,
	dri-devel, netdev
In-Reply-To: <202f6d3a-c29b-491b-979f-1d8223f59108@intel.com>

On Tue, Jul 07, 2026 at 12:02:11PM +0530, Tauro, Riana wrote:
> Hi Rodrigo/Jakub/Aravind
> 
> Please let me know if you have any feedback for this patch or can you please
> ack this if it looks good to you.

I looks good to me, but could you please double check the sashiko's comments?

Patch 3 still needs review I believe, then we get Jakub and drm maintainers acks
to get this through drm-xe-next.

Thanks,
Rodrigo.

> 
> Thanks
> Riana
> 
> On 01-07-2026 15:14, Riana Tauro wrote:
> > Define a new netlink event 'error-event' and a new multicast group
> > 'error-report' in drm_ras. Each event contains device name, node and
> > error information to identify the error triggering the event.
> > 
> > Add drm_ras_nl_error_event() to trigger an event from the driver.
> > Userspace must subscribe to 'error-report' to receive 'error-event'
> > notifications.
> > 
> > Usage:
> > 
> > $ sudo ynl --family drm_ras --subscribe error-report
> > 
> > Cc: Jakub Kicinski <kuba@kernel.org>
> > Cc: Zack McKevitt <zachary.mckevitt@oss.qualcomm.com>
> > Cc: Lijo Lazar <lijo.lazar@amd.com>
> > Cc: Hawking Zhang <Hawking.Zhang@amd.com>
> > Cc: David S. Miller <davem@davemloft.net>
> > Cc: Paolo Abeni <pabeni@redhat.com>
> > Cc: Eric Dumazet <edumazet@google.com>
> > Signed-off-by: Riana Tauro <riana.tauro@intel.com>
> > Reviewed-by: Raag Jadav <raag.jadav@intel.com>
> > ---
> > v2: remove redundant initialization
> >      remove unnecessary space
> >      use ynl in commit message and doc (Raag)
> >      simplify doc for error-event attrs
> > 
> > v3: rename error-notify to error-report
> >      Replace notify with report across the file (Raag)
> > ---
> >   Documentation/gpu/drm-ras.rst            | 21 ++++++
> >   Documentation/netlink/specs/drm_ras.yaml | 48 +++++++++++++
> >   drivers/gpu/drm/drm_ras.c                | 87 ++++++++++++++++++++++++
> >   drivers/gpu/drm/drm_ras_nl.c             |  6 ++
> >   drivers/gpu/drm/drm_ras_nl.h             |  4 ++
> >   include/drm/drm_ras.h                    |  5 ++
> >   include/uapi/drm/drm_ras.h               | 15 ++++
> >   7 files changed, 186 insertions(+)
> > 
> > diff --git a/Documentation/gpu/drm-ras.rst b/Documentation/gpu/drm-ras.rst
> > index 83c21853b74b..406e4c49bac1 100644
> > --- a/Documentation/gpu/drm-ras.rst
> > +++ b/Documentation/gpu/drm-ras.rst
> > @@ -56,6 +56,7 @@ User space tools can:
> >     ``node-id`` and ``error-id`` as parameters.
> >   * Clear specific error counters with the ``clear-error-counter`` command, using both
> >     ``node-id`` and ``error-id`` as parameters.
> > +* Subscribe to the ``error-report`` multicast group to receive ``error-event``.
> >   YAML-based Interface
> >   --------------------
> > @@ -111,3 +112,23 @@ Example: Clear an error counter for a given node
> >       sudo ynl --family drm_ras --do clear-error-counter --json '{"node-id":0, "error-id":1}'
> >       None
> > +
> > +Example: Subscribe to ``error-report`` multicast group
> > +
> > +.. code-block:: bash
> > +
> > +    sudo ynl --family drm_ras --output-json --subscribe error-report
> > +
> > +.. code-block:: json
> > +
> > +    {
> > +        "name": "error-event",
> > +        "msg": {
> > +            "device-name": "0000:03:00.0",
> > +            "node-id": 1,
> > +            "node-name": "uncorrectable-errors",
> > +            "error-id": 1,
> > +            "error-name": "error_name1",
> > +            "error-value": 1
> > +        }
> > +    }
> > diff --git a/Documentation/netlink/specs/drm_ras.yaml b/Documentation/netlink/specs/drm_ras.yaml
> > index e113056f8c01..8aed3d4515e5 100644
> > --- a/Documentation/netlink/specs/drm_ras.yaml
> > +++ b/Documentation/netlink/specs/drm_ras.yaml
> > @@ -69,6 +69,33 @@ attribute-sets:
> >           name: error-value
> >           type: u32
> >           doc: Current value of the requested error counter.
> > +  -
> > +    name: error-event-attrs
> > +    attributes:
> > +      -
> > +        name: device-name
> > +        type: string
> > +        doc: Device (PCI BDF, UUID) that reported the error.
> > +      -
> > +        name: node-id
> > +        type: u32
> > +        doc: ID of the node that reported the error.
> > +      -
> > +        name: node-name
> > +        type: string
> > +        doc: Name of the node that reported the error.
> > +      -
> > +        name: error-id
> > +        type: u32
> > +        doc: ID of the error counter.
> > +      -
> > +        name: error-name
> > +        type: string
> > +        doc: Name of the error.
> > +      -
> > +        name: error-value
> > +        type: u32
> > +        doc: Current value of the error counter.
> >   operations:
> >     list:
> > @@ -124,3 +151,24 @@ operations:
> >         do:
> >           request:
> >             attributes: *id-attrs
> > +    -
> > +      name: error-event
> > +      doc: >-
> > +           Report an error event to userspace.
> > +           The event includes the device, node and error information
> > +           of the error that triggered the event.
> > +      attribute-set: error-event-attrs
> > +      mcgrp: error-report
> > +      event:
> > +        attributes:
> > +          - device-name
> > +          - node-id
> > +          - node-name
> > +          - error-id
> > +          - error-name
> > +          - error-value
> > +
> > +mcast-groups:
> > +  list:
> > +    -
> > +      name: error-report
> > diff --git a/drivers/gpu/drm/drm_ras.c b/drivers/gpu/drm/drm_ras.c
> > index d6eab29a1394..77f912a4d101 100644
> > --- a/drivers/gpu/drm/drm_ras.c
> > +++ b/drivers/gpu/drm/drm_ras.c
> > @@ -41,6 +41,11 @@
> >    *    Userspace must provide Node ID, Error ID.
> >    *    Clears specific error counter of a node if supported.
> >    *
> > + * 4. ERROR_REPORT: Subscribe to this multicast group to receive error events
> > + *
> > + * 5. ERROR_EVENT: Report an error event to userspace. The event contains device, node
> > + *    and error information that triggered the event.
> > + *
> >    * Node registration:
> >    *
> >    * - drm_ras_node_register(): Registers a new node and assigns
> > @@ -186,6 +191,34 @@ static int msg_reply_value(struct sk_buff *msg, u32 error_id,
> >   			   value);
> >   }
> > +static int msg_put_error_event_attrs(struct sk_buff *msg, struct drm_ras_node *node,
> > +				     u32 error_id, const char *error_name, u32 value)
> > +{
> > +	int ret;
> > +
> > +	ret = nla_put_string(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_DEVICE_NAME, node->device_name);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = nla_put_u32(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_NODE_ID, node->id);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = nla_put_string(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_NODE_NAME, node->node_name);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = nla_put_u32(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_ID, error_id);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = nla_put_string(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_NAME, error_name);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return nla_put_u32(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_VALUE, value);
> > +}
> > +
> >   static int doit_reply_value(struct genl_info *info, u32 node_id,
> >   			    u32 error_id)
> >   {
> > @@ -222,6 +255,60 @@ static int doit_reply_value(struct genl_info *info, u32 node_id,
> >   	return genlmsg_reply(msg, info);
> >   }
> > +/**
> > + * drm_ras_nl_error_event() - Report an error event
> > + * @node: Node structure
> > + * @error_id: ID of the error
> > + * @error_name: Name of the error
> > + * @value: Value associated with the error
> > + * @flags: GFP flags for memory allocation
> > + *
> > + * Report an error-event to userspace using the error-report multicast group.
> > + *
> > + * Return: 0 on success, or negative errno on failure.
> > + */
> > +int drm_ras_nl_error_event(struct drm_ras_node *node, u32 error_id, const char *error_name,
> > +			   u32 value, gfp_t flags)
> > +{
> > +	struct genl_info info;
> > +	struct sk_buff *msg;
> > +	struct nlattr *hdr;
> > +	int ret;
> > +
> > +	if (!error_name)
> > +		return -EINVAL;
> > +
> > +	if (!genl_has_listeners(&drm_ras_nl_family, &init_net, DRM_RAS_NLGRP_ERROR_REPORT))
> > +		return 0;
> > +
> > +	genl_info_init_ntf(&info, &drm_ras_nl_family, DRM_RAS_CMD_ERROR_EVENT);
> > +
> > +	msg = genlmsg_new(NLMSG_GOODSIZE, flags);
> > +	if (!msg)
> > +		return -ENOMEM;
> > +
> > +	hdr = genlmsg_iput(msg, &info);
> > +	if (!hdr) {
> > +		ret = -EMSGSIZE;
> > +		goto free_msg;
> > +	}
> > +
> > +	ret = msg_put_error_event_attrs(msg, node, error_id, error_name, value);
> > +	if (ret)
> > +		goto cancel_msg;
> > +
> > +	genlmsg_end(msg, hdr);
> > +	genlmsg_multicast(&drm_ras_nl_family, msg, 0, DRM_RAS_NLGRP_ERROR_REPORT, flags);
> > +	return 0;
> > +
> > +cancel_msg:
> > +	genlmsg_cancel(msg, hdr);
> > +free_msg:
> > +	nlmsg_free(msg);
> > +	return ret;
> > +}
> > +EXPORT_SYMBOL(drm_ras_nl_error_event);
> > +
> >   /**
> >    * drm_ras_nl_get_error_counter_dumpit() - Dump all Error Counters
> >    * @skb: Netlink message buffer
> > diff --git a/drivers/gpu/drm/drm_ras_nl.c b/drivers/gpu/drm/drm_ras_nl.c
> > index dea1c1b2494e..9d3123cc9f9c 100644
> > --- a/drivers/gpu/drm/drm_ras_nl.c
> > +++ b/drivers/gpu/drm/drm_ras_nl.c
> > @@ -58,6 +58,10 @@ static const struct genl_split_ops drm_ras_nl_ops[] = {
> >   	},
> >   };
> > +static const struct genl_multicast_group drm_ras_nl_mcgrps[] = {
> > +	[DRM_RAS_NLGRP_ERROR_REPORT] = { "error-report", },
> > +};
> > +
> >   struct genl_family drm_ras_nl_family __ro_after_init = {
> >   	.name		= DRM_RAS_FAMILY_NAME,
> >   	.version	= DRM_RAS_FAMILY_VERSION,
> > @@ -66,4 +70,6 @@ struct genl_family drm_ras_nl_family __ro_after_init = {
> >   	.module		= THIS_MODULE,
> >   	.split_ops	= drm_ras_nl_ops,
> >   	.n_split_ops	= ARRAY_SIZE(drm_ras_nl_ops),
> > +	.mcgrps		= drm_ras_nl_mcgrps,
> > +	.n_mcgrps	= ARRAY_SIZE(drm_ras_nl_mcgrps),
> >   };
> > diff --git a/drivers/gpu/drm/drm_ras_nl.h b/drivers/gpu/drm/drm_ras_nl.h
> > index a398643572a5..03ec275aca92 100644
> > --- a/drivers/gpu/drm/drm_ras_nl.h
> > +++ b/drivers/gpu/drm/drm_ras_nl.h
> > @@ -21,6 +21,10 @@ int drm_ras_nl_get_error_counter_dumpit(struct sk_buff *skb,
> >   int drm_ras_nl_clear_error_counter_doit(struct sk_buff *skb,
> >   					struct genl_info *info);
> > +enum {
> > +	DRM_RAS_NLGRP_ERROR_REPORT,
> > +};
> > +
> >   extern struct genl_family drm_ras_nl_family;
> >   #endif /* _LINUX_DRM_RAS_GEN_H */
> > diff --git a/include/drm/drm_ras.h b/include/drm/drm_ras.h
> > index 0beede3ddc4e..8abfb7d2077b 100644
> > --- a/include/drm/drm_ras.h
> > +++ b/include/drm/drm_ras.h
> > @@ -80,9 +80,14 @@ struct drm_device;
> >   #if IS_ENABLED(CONFIG_DRM_RAS)
> >   int drm_ras_node_register(struct drm_ras_node *node);
> >   void drm_ras_node_unregister(struct drm_ras_node *node);
> > +int drm_ras_nl_error_event(struct drm_ras_node *node, u32 error_id, const char *error_name,
> > +			   u32 value, gfp_t flags);
> >   #else
> >   static inline int drm_ras_node_register(struct drm_ras_node *node) { return 0; }
> >   static inline void drm_ras_node_unregister(struct drm_ras_node *node) { }
> > +static inline int drm_ras_nl_error_event(struct drm_ras_node *node, u32 error_id,
> > +					 const char *error_name, u32 value, gfp_t flags)
> > +{ return 0; }
> >   #endif
> >   #endif
> > diff --git a/include/uapi/drm/drm_ras.h b/include/uapi/drm/drm_ras.h
> > index 218a3ee86805..eab8231aa87c 100644
> > --- a/include/uapi/drm/drm_ras.h
> > +++ b/include/uapi/drm/drm_ras.h
> > @@ -38,13 +38,28 @@ enum {
> >   	DRM_RAS_A_ERROR_COUNTER_ATTRS_MAX = (__DRM_RAS_A_ERROR_COUNTER_ATTRS_MAX - 1)
> >   };
> > +enum {
> > +	DRM_RAS_A_ERROR_EVENT_ATTRS_DEVICE_NAME = 1,
> > +	DRM_RAS_A_ERROR_EVENT_ATTRS_NODE_ID,
> > +	DRM_RAS_A_ERROR_EVENT_ATTRS_NODE_NAME,
> > +	DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_ID,
> > +	DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_NAME,
> > +	DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_VALUE,
> > +
> > +	__DRM_RAS_A_ERROR_EVENT_ATTRS_MAX,
> > +	DRM_RAS_A_ERROR_EVENT_ATTRS_MAX = (__DRM_RAS_A_ERROR_EVENT_ATTRS_MAX - 1)
> > +};
> > +
> >   enum {
> >   	DRM_RAS_CMD_LIST_NODES = 1,
> >   	DRM_RAS_CMD_GET_ERROR_COUNTER,
> >   	DRM_RAS_CMD_CLEAR_ERROR_COUNTER,
> > +	DRM_RAS_CMD_ERROR_EVENT,
> >   	__DRM_RAS_CMD_MAX,
> >   	DRM_RAS_CMD_MAX = (__DRM_RAS_CMD_MAX - 1)
> >   };
> > +#define DRM_RAS_MCGRP_ERROR_REPORT	"error-report"
> > +
> >   #endif /* _UAPI_LINUX_DRM_RAS_H */

^ permalink raw reply

* Re: [PATCH net 2/2] net/stmmac: Prevent dma queue NULL free on allocation failure
From: Jakub Raczynski @ 2026-07-08 20:16 UTC (permalink / raw)
  To: netdev
  Cc: k.tegowski, k.domagalski, andrew+netdev, davem, edumazet, kuba,
	pabeni, mcoquelin.stm32, alexandre.torgue, linux-stm32,
	linux-arm-kernel, linux-kernel, Sashiko AI
In-Reply-To: <20260707174115.1264466-3-j.raczynski@samsung.com>

[-- Attachment #1: Type: text/plain, Size: 1037 bytes --]

On Tue, Jul 07, 2026 at 07:41:15PM +0200, Jakub Raczynski wrote:
> During allocation of RX/TX descriptor resources and its DMA,
> there is verification of failed dma_alloc_coherent() due to lack of memory.
> In case of that failure, all allocated resources are freed instantly after,
> but there are no checks for dma_free_coherent() whether previous step has
> failed.
> This will generally result in panic due to freeing NULL address.
> 
> Fix it by adding NULL verification of memory that is to be freed.
> 
> Theoretically code should also set address of pointed memory to zero when
> freeing, but currently the only path of invalid address is non intialized zero,
> and there is no case possible of double-free of same memory.
> 
> Fixes: e73b19baa3b1c ("net: stmmac: simplify DMA descriptor allocation/init/freeing")
> Reported-by: Sashiko AI <sashiko-bot@kernel.org>
> Signed-off-by: Jakub Raczynski <j.raczynski@samsung.com>
> ---

Sashiko AI gave review that has valid point, patch needs changes.
Please drop it.

--
pw-bot: cr

[-- Attachment #2: Type: text/plain, Size: 0 bytes --]



^ permalink raw reply

* Re: [PATCH net v3 1/2] net/stmmac: Check for STMMAC_DOWN flag in all XDP paths
From: Jakub Raczynski @ 2026-07-08 20:12 UTC (permalink / raw)
  To: netdev
  Cc: k.tegowski, k.domagalski, andrew+netdev, davem, edumazet, kuba,
	pabeni, mcoquelin.stm32, alexandre.torgue, linux-stm32,
	linux-arm-kernel, linux-kernel
In-Reply-To: <20260707174551.1264558-2-j.raczynski@samsung.com>

[-- Attachment #1: Type: text/plain, Size: 836 bytes --]

On Tue, Jul 07, 2026 at 07:45:50PM +0200, Jakub Raczynski wrote:
> Currently STMMAC_DOWN flag is only set/cleared by stmmac_reset_subtask(),
> to notify driver to stop processing of TX/RX frames. One of these processing
> paths is for XDP, but it is only ever checked in stmmac_xdp_xmit(), which
> leaves all other XDP paths vulnerable to processing data while interface is
> restarting.
> 
> Make verification of STMMAC_DOWN flag consistent by applying check to
> all XDP paths.
> 
> Fixes: 8b278a5b69a22 ("net: stmmac: Add support for XDP_REDIRECT action")
> Co-developed-by: Chang-Sub Lee <cs0617.lee@samsung.com>
> Signed-off-by: Chang-Sub Lee <cs0617.lee@samsung.com>
> Signed-off-by: Jakub Raczynski <j.raczynski@samsung.com>
> ---

Sashiko AI gave review that has valid points, patch needs changes.
Please drop it

--
pw-bot: cr

[-- Attachment #2: Type: text/plain, Size: 0 bytes --]



^ permalink raw reply

* Re: [PATCH 3/3] net: ipa: Add IPA v5.1 data
From: Alex Elder @ 2026-07-08 20:06 UTC (permalink / raw)
  To: esteuwu, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alex Elder
  Cc: linux-arm-msm, devicetree, linux-kernel, netdev
In-Reply-To: <20260622-sm8450-ipa-v1-3-532f0299f96e@proton.me>

On 6/22/26 8:44 PM, Esteban Urrutia via B4 Relay wrote:
> From: Esteban Urrutia <esteuwu@proton.me>
> 
> Add the required ipa_data-v5.1.c file for IPA v5.1 along with changes
> that declare IPA v5.1 support.
> This version of IPA is used in both SM8450 and SM8475 SoCs.
> 
> Signed-off-by: Esteban Urrutia <esteuwu@proton.me>

OK I'm finally reviewing this.  Thank you again for sharing links to
the resources you used and developed while doing this work.


For the most part this looks entirely correct.  There is one
pair of memory table entries that I think should not be there,
otherwise everything looks just about perfect.

I'm not totally sure that reducing the SMEM size will work
correctly.


I'm taking this opportunity to explain a LOT of things about
IPA and the driver code.  It's much more than what's typical
for a review, but I thought this provided a good chance to
explain some things in context.  You can add it to your notes
file if you like...

> ---
>   drivers/net/ipa/Makefile             |   2 +-
>   drivers/net/ipa/data/ipa_data-v5.1.c | 477 +++++++++++++++++++++++++++++++++++
>   drivers/net/ipa/gsi_reg.c            |   1 +
>   drivers/net/ipa/ipa_data.h           |   1 +
>   drivers/net/ipa/ipa_main.c           |   4 +
>   drivers/net/ipa/ipa_reg.c            |   1 +
>   6 files changed, 485 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/ipa/Makefile b/drivers/net/ipa/Makefile
> index e148ec3c1a10..d4995c2e8ca0 100644
> --- a/drivers/net/ipa/Makefile
> +++ b/drivers/net/ipa/Makefile
> @@ -7,7 +7,7 @@ IPA_REG_VERSIONS	:=	3.1 3.5.1 4.2 4.5 4.7 4.9 4.11 5.0 5.5
>   # Some IPA versions can reuse another set of GSI register definitions.
>   GSI_REG_VERSIONS	:=	3.1 3.5.1 4.0 4.5 4.9 4.11 5.0
>   
> -IPA_DATA_VERSIONS	:=	3.1 3.5.1 4.2 4.5 4.7 4.9 4.11 5.0 5.2 5.5
> +IPA_DATA_VERSIONS	:=	3.1 3.5.1 4.2 4.5 4.7 4.9 4.11 5.0 5.1 5.2 5.5
>   
>   obj-$(CONFIG_QCOM_IPA)	+=	ipa.o
>   
> diff --git a/drivers/net/ipa/data/ipa_data-v5.1.c b/drivers/net/ipa/data/ipa_data-v5.1.c
> new file mode 100644
> index 000000000000..85b21efa1224
> --- /dev/null
> +++ b/drivers/net/ipa/data/ipa_data-v5.1.c
> @@ -0,0 +1,477 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (C) 2023-2024 Linaro Ltd. */
> +/* Copyright (C) 2026 Esteban Urrutia <esteuwu@proton.me> */
> +
> +#include <linux/array_size.h>
> +#include <linux/log2.h>
> +
> +#include "../ipa_data.h"
> +#include "../ipa_endpoint.h"
> +#include "../ipa_mem.h"
> +#include "../ipa_version.h"
> +
> +/** enum ipa_resource_type - IPA resource types for an SoC having IPA v5.1 */
> +enum ipa_resource_type {
> +	/* Source resource types; first must have value 0 */
> +	IPA_RESOURCE_TYPE_SRC_PKT_CONTEXTS		= 0,
> +	IPA_RESOURCE_TYPE_SRC_DESCRIPTOR_LISTS,
> +	IPA_RESOURCE_TYPE_SRC_DESCRIPTOR_BUFF,
> +	IPA_RESOURCE_TYPE_SRC_HPS_DMARS,
> +	IPA_RESOURCE_TYPE_SRC_ACK_ENTRIES,
> +
> +	/* Destination resource types; first must have value 0 */
> +	IPA_RESOURCE_TYPE_DST_DATA_SECTORS		= 0,
> +	IPA_RESOURCE_TYPE_DST_DPS_DMARS,
> +	IPA_RESOURCE_TYPE_DST_ULSO_SEGMENTS,
> +};

The above looks correct to me.  They come from downstream
"ipa_utils.c", in the ipa3_rsrc_src_grp_config[IPA_5_1][][]
array and the ipa3_rsrc_dst_grp_config[IPA_5_1][][] array.

The *_SRC_* symbols are the index values used in the
ipa_resource_src[] array upstream, and the *_DST_* symbols
are indexes in the upstream ipa_resource_dst[] array.

> +/* Resource groups used for an SoC having IPA v5.1 */
> +enum ipa_rsrc_group_id {
> +	/* Source resource group identifiers */
> +	IPA_RSRC_GROUP_SRC_UL				= 0,
> +	IPA_RSRC_GROUP_SRC_DL,
> +	IPA_RSRC_GROUP_SRC_UNUSED_2,
> +	IPA_RSRC_GROUP_SRC_UNUSED_3,
> +	IPA_RSRC_GROUP_SRC_URLLC,
> +	IPA_RSRC_GROUP_SRC_U_RX_QC,
> +	IPA_RSRC_GROUP_SRC_COUNT,	/* Last in set; not a source group */
> +
> +	/* Destination resource group identifiers */
> +	IPA_RSRC_GROUP_DST_UL				= 0,
> +	IPA_RSRC_GROUP_DST_DL,
> +	IPA_RSRC_GROUP_DST_UNUSED_2,
> +	IPA_RSRC_GROUP_DST_UNUSED_3,
> +	IPA_RSRC_GROUP_DST_UNUSED_4,
> +	IPA_RSRC_GROUP_DST_UC,
> +	IPA_RSRC_GROUP_DST_DRB_IP,
> +	IPA_RSRC_GROUP_DST_COUNT,	/* Last; not a destination group */
> +};

These look correct.  They correspond to the second index values
in the downstream arrays mentioned earlier, and are used as
indexes into the limits[] array within an ipa_resource structure.

As you probably now know, the symbols correspond to these comments
in the downstream code:
                 /* UL  DL  unused  unused  URLLC UC_RX_Q N/A */
                 /* UL  DL  unused  unused unused  UC_RX_Q DRBIP N/A */
> +/* QSB configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_qsb_data ipa_qsb_data[] = {
> +	[IPA_QSB_MASTER_DDR] = {
> +		.max_writes		= 0,
> +		.max_reads		= 0,	/* no limit (hardware max) */
> +		.max_reads_beats	= 0,
> +	},
> +	[IPA_QSB_MASTER_PCIE] = {
> +		.max_writes		= 0,
> +		.max_reads		= 0,	/* no limit (hardware max) */
> +		.max_reads_beats	= 0,
> +	},
> +};

I think the DDR values might be wrong, but it's difficult to be
sure.  In some cases, in arrays like this in the downstream code,
if there is no entry found in an array, the *earlier* version
values should be used.  (Unless someone better informed states
that this is wrong, I think it's fine as-is.)

This information is found in the ipa3_qmb_outstanding[IPA_5_1][]
array in the downstream code.  However there is no entry for that
version.  Given that, all zeroes (as you have it) makes sense.
But it's possible this applies instead:

         [IPA_5_0][IPA_QMB_INSTANCE_DDR]         = {12, 12, 0},
         [IPA_5_0][IPA_QMB_INSTANCE_PCIE]        = {0, 0, 0},

I have no way of knowing; perhaps someone from Qualcomm can
get confirmation that all zeroes is correct.

(Note the order of values presented in the downstream code
differs from upstream.)



Most of the information in the structure below comes from the
ipa3_ep_mapping[IPA_V5_1][] array in the downstream code.
Many of the entries in that array are unused in the upstream
code, because we only use a small subset of the available
endpoints.

> +/* Endpoint configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_gsi_endpoint_data ipa_gsi_endpoint_data[] = {
> +	[IPA_ENDPOINT_AP_COMMAND_TX] = {

IPA_ENDPOINT_AP_COMMAND_TX corresponds to IPA_CLIENT_APPS_CMD_PROD
in the downstream code.  The downstream code doesn't label the
assignments within the ipa3_ep_mapping[][] array, so I think it's
a little harder to understand.  Anyway I'll show how they map
between downstream and upstream below.

The downstream structure is named ipa_ep_configuration.  The
upstream structure is named ipa_gsi_endpoint_data.

struct ipa_ep_configuration {
         bool valid;
         int group_num;
         bool support_flt;
         int sequencer_type;
         u8 qmb_master_sel;
         struct ipa_gsi_ep_config ipa_gsi_ep_info;
         u8 tx_instance;
};

And although ipa_gsi_ep_config is not defined in this code
base, here is what it looks like:

struct ipa_gsi_ep_config {
         int ipa_ep_num;
         int ipa_gsi_chan_num;
         int ipa_if_tlv;
         int ipa_if_aos;
         int ee;
         enum gsi_prefetch_mode prefetch_mode;
         uint8_t prefetch_threshold;
};

This might not be current; I'm using code found here:
   https://git.codelinaro.org/clo/la/kernel/msm-5.15.git


Here is the upstream structure, and I indicate where the
information comes from in the downstream code:

struct ipa_gsi_endpoint_data {
         u8 ee_id;	/* ipa_ep_configuration->ee */
         u8 channel_id;  /* ipa_ep_configuration->ipa_gsi_chan_num */
         u8 endpoint_id;	/* ipa_ep_configuration->ipa_ep_num */
         bool toward_ipa;

         struct gsi_channel_data channel;
         struct ipa_endpoint_data endpoint;
};

And here is the first sub-structure:

struct gsi_channel_data {
         u16 tre_count;	/* Computed based on other code (see below) */
         u16 event_count;
         u8 tlv_count;	/* ipa_ep_configuration->ipa_if_tlv */
};

> +		.ee_id		= GSI_EE_AP,

This is the "execution environment" that the endpoint is
associated with.  For upstream, that's either the AP or
the modem.  The "_AP_" sitting where it does in the
IPA_ENDPOINT_AP_COMMAND_TX endpoint ID also indicates
this is an AP endpoint.  It also matches what's seen
in the downstream ipa_gsi_ep_config->ee field.

> +		.channel_id	= 12,
> +		.endpoint_id	= 14,
> +		.toward_ipa	= true,
> +		.channel = {
> +			.tre_count	= 256,
> +			.event_count	= 256,
> +			.tlv_count	= 20,

The tre_count number was derived from code in ipa3_setup_apps_pipes()
in downstream "ipa/ipa_v3/ipa.c".  There a ipa_sys_connect_params
structure contains a field desc_fifo_size, which is the size in bytes
of the transfer ring buffer.  The tre_count in upsteram code is in
units of a TRE (transfer ring element), i.e. it's the number of such
entries (that fit in that number of bytes).

The downstream IPA_CLIENT_APPS_CMD_PROD corresponds to upstream
IPA_ENDPOINT_AP_COMMAND_TX (the array entry we're in the middle
of here), and the downstream size is IPA_SYS_DESC_FIFO_SZ, or
0x800=2048 bytes.  Each TRE (struct gsi_tre) is 16 bytes.

In the downstream code--confusingly--ipa_gsi_setup_channel()
doubles the desc_fifo_sz value (for GSI, versus the older BAM
interface).  So the ring size becomes 4096 bytes, and that
works out to 256 16-byte GSI TRE entries.  I'm not sure why
512 is used for IPA v3.5.1, but it probably just means it's
bigger than it needs to be.

The event_count should be the same as the tre_count.  Again
I no longer know why that's not the case for IPA v3.5.1.


> +		},

Below is the second sub-structure in the upstream structure
ipa_gsi_endpoint_data, and the other structures it
incorporates.

struct ipa_endpoint_data {
         bool filter_support;
         struct ipa_endpoint_config config;
};

struct ipa_endpoint_config {
         u32 resource_group;
         bool checksum;
         bool qmap;
         bool aggregation;
         bool status_enable;
         bool dma_mode;
         enum ipa_endpoint_name dma_endpoint;
         union {
                 struct ipa_endpoint_tx tx;
                 struct ipa_endpoint_rx rx;
         };
};

struct ipa_endpoint_tx {
         enum ipa_seq_type seq_type;
         enum ipa_seq_rep_type seq_rep_type;
         enum ipa_endpoint_name status_endpoint;
};

struct ipa_endpoint_rx {
         u32 buffer_size;
         u32 pad_align;
         u32 aggr_time_limit;
         bool aggr_hard_limit;
         bool aggr_close_eof;
         bool holb_drop;
};

> +		.endpoint = {
> +			.config = {
> +				.resource_group	= IPA_RSRC_GROUP_SRC_UL,

This resource group corresponds to IPA_v5_0_GROUP_UL in
the downstream code.

> +				.dma_mode	= true,

The dma_mode is always true for the AP->IPA command TX
endpoint, false for others.

> +				.dma_endpoint	= IPA_ENDPOINT_AP_LAN_RX,

This is always the DMA endpoint id for the command
endpoint. I think it's where the status messages
related to transmitted commands get sent.  The AP<-LAN
(RX) endpoint is the "default" endpoint.

> +				.tx = {
> +					.seq_type = IPA_SEQ_DMA,

This is the sequencer type, always DMA for the command
endpoint.  The sequencer types are set based on what the
downstream code does.

> +				},
> +			},
> +		},
> +	},
> +	[IPA_ENDPOINT_AP_LAN_RX] = {

This is the default RX endpoint on the AP.  If a LAN
interface were supported it would also be the RX
endpoint for the LAN.  This corresponds to
IPA_CLIENT_APPS_LAN_CONS

> +		.ee_id		= GSI_EE_AP,
> +		.channel_id	= 13,
> +		.endpoint_id	= 16,
> +		.toward_ipa	= false,
> +		.channel = {
> +			.tre_count	= 256,
> +			.event_count	= 256,
> +			.tlv_count	= 9,
> +		},
> +		.endpoint = {
> +			.config = {
> +				.resource_group	= IPA_RSRC_GROUP_DST_UL,
> +				.aggregation	= true,

Aggregation enabled means multiple received messages will
be placed by the IPA hardware into a single receive buffer
before forwarding the buffer to the host for processing.

> +				.status_enable	= true,

This setting means every transfer causes a status header to be
generated for each received message.  ipa_endpoint_status_parse()
splits them apart using information in the status header and
hands each de-aggregated message to the network stack.

> +				.rx = {
> +					.buffer_size	= 8192,

Each receive buffer is this big (in bytes).

> +					.pad_align	= ilog2(sizeof(u32)),

Before a received message is placed in the receive buffer,
IPA updates current buffer pointer to be aligned to this
boundary (in this case, 2^2 bytes).

> +					.aggr_time_limit = 500,

If aggregation hasn't exhausted the receive buffer in this many
microseconds, it forwards the buffer to the host anyway.

The time limit comes from IPA_GENERIC_AGGR_TIME_LIMIT in the
downstream code.

> +				},
> +			},
> +		},
> +	},
> +	[IPA_ENDPOINT_AP_MODEM_TX] = {

The AP_MODEM_TX here says that this is an AP endpoint,
whose destination is the modem (WAN in the downstream
code), and it is a TX endpoint (from the AP to the modem).
This corresponds to IPA_CLIENT_APPS_WAN_PROD.

> +		.ee_id		= GSI_EE_AP,
> +		.channel_id	= 11,
> +		.endpoint_id	= 2,
> +		.toward_ipa	= true,
> +		.channel = {
> +			.tre_count	= 512,
> +			.event_count	= 512,
> +			.tlv_count	= 25,
> +		},
> +		.endpoint = {
> +			.filter_support	= true,
> +			.config = {
> +				.resource_group	= IPA_RSRC_GROUP_SRC_UL,
> +				.checksum       = true,

The checksum true flag means IPA performs checksumming
on messages being sent (so the host doesn't have to).

> +				.qmap		= true,

The qmap true flag says that this channel uses QMAP
protocol (ETH_P_MAP).  A single message contains one
or more QMAP messages, which multiplexes multiple
logical channels over a single connection.

> +				.status_enable	= true,
> +				.tx = {
> +					.seq_type = IPA_SEQ_2_PASS_SKIP_LAST_UC,
> +					.status_endpoint =
> +						IPA_ENDPOINT_MODEM_AP_RX,

This says that status messages generated as a result
of messages received on this channel (i.e., using
this endpoint) are delivered to the *modem* endpoint
that recieves data from the AP.

> +				},
> +			},
> +		},
> +	},
> +	[IPA_ENDPOINT_AP_MODEM_RX] = {

AP endpoint, *from* the modem.  This corresponds to
IPA_CLIENT_APPS_WAN_CONS.

> +		.ee_id		= GSI_EE_AP,
> +		.channel_id	= 1,
> +		.endpoint_id	= 23,
> +		.toward_ipa	= false,
> +		.channel = {
> +			.tre_count	= 256,
> +			.event_count	= 256,
> +			.tlv_count	= 9,
> +		},
> +		.endpoint = {
> +			.config = {
> +				.resource_group	= IPA_RSRC_GROUP_DST_UL,
> +				.checksum       = true,
> +				.qmap		= true,
> +				.aggregation	= true,
> +				.rx = {
> +					.buffer_size	= 8192,
> +					.aggr_time_limit = 500,
> +					.aggr_close_eof	= true,

The aggr_close_eof flag determines which of two ways
aggregation in a receive buffer "closes".  (Closing
means th receive buffer is delivered to the host for
processing, and a new receive buffer begins to be
used.)

One policy closes aggregation when there is not enough
space left to hold an entire incoming message in the
buffer.  The other policy closes aggregation when the
data from a received message crosses a certain mark
(byte count) in the receive buffer.  (I no longer
recall which is which.)

> +				},
> +			},
> +		},
> +	},
> +	[IPA_ENDPOINT_MODEM_AP_TX] = {

Modem endpoint, transmitting (from the modem) *to* the AP.
Downstream calls the modem "Q6".  Configuring these endpoints
is the modem's responsibility, but the AP IPA driver needs
to be aware of these, so they're included in this data.
(I don't remember why; maybe it's to ensure endpoints and
channels are accounted for, and/or not reused?)

This endpoint id corresponds to IPA_CLIENT_Q6_WAN_CONS.

> +		.ee_id		= GSI_EE_MODEM,
> +		.channel_id	= 0,
> +		.endpoint_id	= 12,
> +		.toward_ipa	= true,
> +		.endpoint = {
> +			.filter_support	= true,
> +		},
> +	},
> +	[IPA_ENDPOINT_MODEM_AP_RX] = {

This corresponds to IPA_CLIENT_Q6_WAN_CONS.

> +		.ee_id		= GSI_EE_MODEM,
> +		.channel_id	= 7,
> +		.endpoint_id	= 21,
> +		.toward_ipa	= false,
> +	},
> +	[IPA_ENDPOINT_MODEM_DL_NLO_TX] = {

This has to do with a feature we don't use, but we still
need to configure it (I think so we take into account that
it implements filtering).  This endpoint corresponds to
IPA_CLIENT_Q6_DL_NLO_DATA_PROD.

> +		.ee_id		= GSI_EE_MODEM,
> +		.channel_id	= 2,
> +		.endpoint_id	= 15,
> +		.toward_ipa	= true,
> +		.endpoint = {
> +			.filter_support	= true,
> +		},
> +	},
> +};
"Resources" are data structures managed by the IPA/GSI
firmware.  We must configure these at initialization
time, and once configured, that firmware operates
using these resources.  I don't know much more than
that, and basically we just configure things the way
the downstream code does.

> +
> +/* Source resource configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_resource ipa_resource_src[] = {

Again, this array is filled with information that comes from the
ipa3_rsrc_src_grp_config[IPA_5_1][][] array in the downstream
code, in "ipa_utils.c".  Everything you have here looks correct.

> +	[IPA_RESOURCE_TYPE_SRC_PKT_CONTEXTS] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 7,	.max = 12,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 1,	.max = 63,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_U_RX_QC] = {
> +			.min = 0,	.max = 63,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_SRC_DESCRIPTOR_LISTS] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 21,	.max = 21,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 10,	.max = 10,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_SRC_DESCRIPTOR_BUFF] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 33,	.max = 33,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 20,	.max = 20,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_SRC_HPS_DMARS] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 0,	.max = 63,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 1,	.max = 63,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_U_RX_QC] = {
> +			.min = 0,	.max = 63,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_SRC_ACK_ENTRIES] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 38,	.max = 38,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 16,	.max = 16,
> +		},
> +	},
> +};
> +
> +/* Destination resource configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_resource ipa_resource_dst[] = {

And the content of this array comes from ipa3_rsrc_dst_grp_config[][].
Everything you have here looks correct as well.

> +	[IPA_RESOURCE_TYPE_DST_DATA_SECTORS] = {
> +		.limits[IPA_RSRC_GROUP_DST_UL] = {
> +			.min = 6,	.max = 6,
> +		},
> +		.limits[IPA_RSRC_GROUP_DST_DL] = {
> +			.min = 5,	.max = 5,
> +		},
> +		.limits[IPA_RSRC_GROUP_DST_DRB_IP] = {
> +			.min = 39,	.max = 39,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_DST_DPS_DMARS] = {
> +		.limits[IPA_RSRC_GROUP_DST_UL] = {
> +			.min = 0,	.max = 3,
> +		},
> +		.limits[IPA_RSRC_GROUP_DST_DL] = {
> +			.min = 0,	.max = 3,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_DST_ULSO_SEGMENTS] = {
> +		.limits[IPA_RSRC_GROUP_DST_UL] = {
> +			.min = 0,	.max = 63,
> +		},
> +		.limits[IPA_RSRC_GROUP_DST_DL] = {
> +			.min = 0,	.max = 63,
> +		},
> +	},
> +};
> +
> +/* Resource configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_resource_data ipa_resource_data = {
> +	.rsrc_group_dst_count	= IPA_RSRC_GROUP_DST_COUNT,
> +	.rsrc_group_src_count	= IPA_RSRC_GROUP_SRC_COUNT,
> +	.resource_src_count	= ARRAY_SIZE(ipa_resource_src),
> +	.resource_src		= ipa_resource_src,
> +	.resource_dst_count	= ARRAY_SIZE(ipa_resource_dst),
> +	.resource_dst		= ipa_resource_dst,
> +};
> +
> +/* IPA-resident memory region data for an SoC having IPA v5.1 */

Memory regions are sort of similar to resources, in that
there are ranges of available (IPA-local) memory that are
used by IPA for various purposes.  We need to configure
these, and this configuration (base and size of various
memory regions) is shared with the modem via a QMI message
exchange during initialization.

> +static const struct ipa_mem ipa_mem_local_data[] = {

IPA has local memory that is partitioned as defined by this
array.  The regions are used by IPA/GSI firmware and/or
hardware.  The configuration defined here is sent to
the modem in an ipa_init_modem_driver_req QMI message
so both the modem and AP have a consistent view of
how the memory is used.

Many memory regions are preceded by 0-2 "canaries", which
are 32-byte values initialized to IPA_MEM_CANARY_VAL.

In the downstream code there is structure ipa3_mem_partition
that defines these things, and structures of this type are
defined in "ipa_utils.c".  For IPA v5.1, ipa_5_1_mem_part
defines them all.  The mapping between downstream and
upstream is not trivial and direct, but it should be
obvious how they get translated.


With two exceptions, what I see here looks like you
correctly transferred everything.  (The two exceptions
are entries that from what I can tell, should not be
present.)

> +	{
> +		.id		= IPA_MEM_UC_EVENT_RING,
> +		.offset		= 0x0000,
> +		.size		= 0x1000,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_UC_SHARED,
> +		.offset		= 0x1000,
> +		.size		= 0x0080,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_UC_INFO,
> +		.offset		= 0x1080,
> +		.size		= 0x0200,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_V4_FILTER_HASHED,
> +		.offset		= 0x1288,
> +		.size		= 0x0078,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V4_FILTER,
> +		.offset		= 0x1308,
> +		.size		= 0x0078,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V6_FILTER_HASHED,
> +		.offset		= 0x1388,
> +		.size		= 0x0078,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V6_FILTER,
> +		.offset		= 0x1408,
> +		.size		= 0x0078,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V4_ROUTE_HASHED,
> +		.offset		= 0x1488,
> +		.size		= 0x0098,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V4_ROUTE,
> +		.offset		= 0x1528,
> +		.size		= 0x0098,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V6_ROUTE_HASHED,
> +		.offset		= 0x15c8,
> +		.size		= 0x0098,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V6_ROUTE,
> +		.offset		= 0x1668,
> +		.size		= 0x0098,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_MODEM_HEADER,
> +		.offset		= 0x1708,
> +		.size		= 0x0240,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_AP_HEADER,
> +		.offset		= 0x1948,
> +		.size		= 0x01e0,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_MODEM_PROC_CTX,
> +		.offset		= 0x1b40,
> +		.size		= 0x0b20,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_AP_PROC_CTX,
> +		.offset		= 0x2660,
> +		.size		= 0x0200,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_STATS_QUOTA_MODEM,
> +		.offset		= 0x2868,
> +		.size		= 0x0060,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_STATS_QUOTA_AP,
> +		.offset		= 0x28c8,
> +		.size		= 0x0048,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_STATS_TETHERING,
> +		.offset		= 0x2910,
> +		.size		= 0x03c0,
> +		.canary_count	= 0,
> +	},

The next two entries look wrong to me.  Can you explain where
you got these offsets and sizes?  Is it from "ipa_data-v5.0.c"?

Here are the relevant entries I see in ipa_5_1_mem_part
in the downstream code:
         .stats_flt_v4_ofst = 0,
         .stats_flt_v4_size = 0,
         .stats_flt_v6_ofst = 0,
         .stats_flt_v6_size = 0,
         .stats_rt_v4_ofst = 0,
         .stats_rt_v4_size = 0,
         .stats_rt_v6_ofst = 0,
         .stats_rt_v6_size = 0,
(Since their size is zero, their entries can be omitted.)

> +	{
> +		.id		= IPA_MEM_AP_V4_FILTER,
> +		.offset		= 0x29b8,
> +		.size		= 0x0188,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_AP_V6_FILTER,
> +		.offset		= 0x2b40,
> +		.size		= 0x0228,
> +		.canary_count	= 0,
> +	},

The remaining entries (below) look good.

> +	{
> +		.id		= IPA_MEM_STATS_FILTER_ROUTE,
> +		.offset		= 0x2cd0,
> +		.size		= 0x0ba0,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_STATS_DROP,
> +		.offset		= 0x3870,
> +		.size		= 0x0020,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_MODEM,
> +		.offset		= 0x3898,
> +		.size		= 0x0d48,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_NAT_TABLE,
> +		.offset		= 0x45e0,
> +		.size		= 0x0900,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_PDN_CONFIG,
> +		.offset		= 0x4ee8,
> +		.size		= 0x0100,
> +		.canary_count	= 2,
> +	},
> +};
> +
> +/* Memory configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_mem_data ipa_mem_data = {
> +	.local_count	= ARRAY_SIZE(ipa_mem_local_data),
> +	.local		= ipa_mem_local_data,
> +	.imem_addr	= 0x146a8000,

I think I needed to look up the imem offset value
in Qualcomm documentation I no longer have access
to.  Perhaps someone from there could confirm you
are using the right values here.

> +	.imem_size	= 0x00002000,
> +	/*
> +	 * While this value is 0xb000 on SM8450 and 0x9000 on SM8475,
> +	 * it has been left set to 0x9000 for compatibility with SM8475
> +	 */

As I said earlier, I'm not completely sure this will still
work on the SM8450.  Someone should confirm this, and it
really ought to be tested somehow.

> +	.smem_size	= 0x00009000,
> +};
> +
> +/* Interconnect rates are in 1000 byte/second units */
> +static const struct ipa_interconnect_data ipa_interconnect_data[] = {
> +	{
> +		.name			= "memory",
> +		.peak_bandwidth		= 1900000,	/* 1.9 GBps */
> +		.average_bandwidth	= 590000,	/* 590 MBps */

I no longer recall where to get these bandwidth values
for the interconnects.  Perhaps someone from Qualcomm
can find this out/confirm what you have.

Really nice work figuring out all this stuff...

					-Alex

> +	},
> +	/* Average rate is unused for the next interconnect */
> +	{
> +		.name			= "config",
> +		.peak_bandwidth		= 76800,	/* 76.8 MBps */
> +		.average_bandwidth	= 0,		/* unused */
> +	},
> +};
> +
> +/* Clock and interconnect configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_power_data ipa_power_data = {
> +	.core_clock_rate	= 120 * 1000 * 1000,	/* Hz */
> +	.interconnect_count	= ARRAY_SIZE(ipa_interconnect_data),
> +	.interconnect_data	= ipa_interconnect_data,
> +};
> +
> +/* Configuration data for an SoC having IPA v5.1. */
> +const struct ipa_data ipa_data_v5_1 = {
> +	.version		= IPA_VERSION_5_1,
> +	.qsb_count		= ARRAY_SIZE(ipa_qsb_data),
> +	.qsb_data		= ipa_qsb_data,
> +	.modem_route_count	= 11,
> +	.endpoint_count		= ARRAY_SIZE(ipa_gsi_endpoint_data),
> +	.endpoint_data		= ipa_gsi_endpoint_data,
> +	.resource_data		= &ipa_resource_data,
> +	.mem_data		= &ipa_mem_data,
> +	.power_data		= &ipa_power_data,
> +};
> diff --git a/drivers/net/ipa/gsi_reg.c b/drivers/net/ipa/gsi_reg.c
> index e13cf835a013..a57072ba4bef 100644
> --- a/drivers/net/ipa/gsi_reg.c
> +++ b/drivers/net/ipa/gsi_reg.c
> @@ -110,6 +110,7 @@ static const struct regs *gsi_regs(struct gsi *gsi)
>   		return &gsi_regs_v4_11;
>   
>   	case IPA_VERSION_5_0:
> +	case IPA_VERSION_5_1:
>   	case IPA_VERSION_5_2:
>   	case IPA_VERSION_5_5:
>   		return &gsi_regs_v5_0;
> diff --git a/drivers/net/ipa/ipa_data.h b/drivers/net/ipa/ipa_data.h
> index 3eb9dc2ce339..fe6f7d5bfe88 100644
> --- a/drivers/net/ipa/ipa_data.h
> +++ b/drivers/net/ipa/ipa_data.h
> @@ -253,6 +253,7 @@ extern const struct ipa_data ipa_data_v4_7;
>   extern const struct ipa_data ipa_data_v4_9;
>   extern const struct ipa_data ipa_data_v4_11;
>   extern const struct ipa_data ipa_data_v5_0;
> +extern const struct ipa_data ipa_data_v5_1;
>   extern const struct ipa_data ipa_data_v5_2;
>   extern const struct ipa_data ipa_data_v5_5;
>   
> diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c
> index 788dd99af2a4..6c449032ae45 100644
> --- a/drivers/net/ipa/ipa_main.c
> +++ b/drivers/net/ipa/ipa_main.c
> @@ -669,6 +669,10 @@ static const struct of_device_id ipa_match[] = {
>   		.compatible	= "qcom,sdx65-ipa",
>   		.data		= &ipa_data_v5_0,
>   	},
> +	{
> +		.compatible	= "qcom,sm8450-ipa",
> +		.data		= &ipa_data_v5_1,
> +	},
>   	{
>   		.compatible	= "qcom,milos-ipa",
>   		.data		= &ipa_data_v5_2,
> diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
> index 30bd69f4c147..5f22ca6295b1 100644
> --- a/drivers/net/ipa/ipa_reg.c
> +++ b/drivers/net/ipa/ipa_reg.c
> @@ -125,6 +125,7 @@ static const struct regs *ipa_regs(enum ipa_version version)
>   	case IPA_VERSION_4_11:
>   		return &ipa_regs_v4_11;
>   	case IPA_VERSION_5_0:
> +	case IPA_VERSION_5_1:
>   	case IPA_VERSION_5_2:
>   		return &ipa_regs_v5_0;
>   	case IPA_VERSION_5_5:
> 


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox