From: Md Rayhanul Islam <r97yhan@gmail.com>
To: dev@dpdk.org
Cc: bruce.richardson@intel.com, Chuanyu Xue <chuanyu.xue@uconn.edu>,
Song Han <song.han@uconn.edu>,
Md Rayhanul Islam <md_rayhanul.islam@uconn.edu>,
Md Rayhanul Islam <r97yhan@gmail.com>,
Thomas Monjalon <thomas@monjalon.net>,
Anatoly Burakov <anatoly.burakov@intel.com>
Subject: [PATCH 1/2] eal/linux: apply PCIe inbound DMA translation
Date: Fri, 18 Sep 2026 18:50:43 -0400 [thread overview]
Message-ID: <20260918225045.177125-2-r97yhan@gmail.com> (raw)
In-Reply-To: <20260918225045.177125-1-r97yhan@gmail.com>
On some platforms a device does not see memory at the CPU's physical
addresses: the PCIe host bridge translates inbound traffic. An IOVA in
RTE_IOVA_PA mode is then an address the device cannot reach, so its
reads and writes fall outside the bridge's inbound window and are
dropped with no error reported.
On a Raspberry Pi Compute Module 4 (BCM2711) with an Intel I210, the
host bridge declares in its device tree "dma-ranges" that CPU physical
address 0 is reached at bus address 0x4_0000_0000. Without that offset
the NIC reported link up but never completed a DMA: testpmd in txonly
mode left TX-packets at 0, and received data never reached the mbufs.
Read the translation from the PCI host bridge "dma-ranges" in the device
tree and add it to IOVAs in rte_mem_virt2iova() and the legacy memory
path. Platforms that declare no translation, and those with no device
tree, keep an identity mapping. DPDK_IOVA_PA_OFFSET overrides it.
Signed-off-by: Md Rayhanul Islam <r97yhan@gmail.com>
---
.mailmap | 1 +
doc/guides/linux_gsg/bcm2711_platform.rst | 45 ++++
doc/guides/linux_gsg/index.rst | 1 +
doc/guides/rel_notes/release_26_11.rst | 8 +
lib/eal/linux/eal_memory.c | 265 +++++++++++++++++++++-
5 files changed, 318 insertions(+), 2 deletions(-)
create mode 100644 doc/guides/linux_gsg/bcm2711_platform.rst
diff --git a/.mailmap b/.mailmap
index 9e45cdce8f..9fd78db7e2 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1032,6 +1032,7 @@ Marek Mical <marekx.mical@intel.com>
Marek Zalfresso-jundzillo <marekx.zalfresso-jundzillo@intel.com>
Maria Lingemark <maria.lingemark@ericsson.com>
Mario Carrillo <mario.alfredo.c.arevalo@intel.com>
+Md Rayhanul Islam <r97yhan@gmail.com>
Mário Kuka <kuka@cesnet.cz>
Mariusz Drost <mariuszx.drost@intel.com>
Mark Asselstine <mark.asselstine@windriver.com>
diff --git a/doc/guides/linux_gsg/bcm2711_platform.rst b/doc/guides/linux_gsg/bcm2711_platform.rst
new file mode 100644
index 0000000000..5a774607da
--- /dev/null
+++ b/doc/guides/linux_gsg/bcm2711_platform.rst
@@ -0,0 +1,45 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Md Rayhanul Islam
+
+Running DPDK on Broadcom BCM2711 platforms
+==========================================
+
+The Broadcom BCM2711, used on the Raspberry Pi 4 and Compute Module 4,
+has a platform property that must be taken into account before a PCIe
+device can DMA correctly. It is handled automatically; this page
+describes what happens and how to check it.
+
+PCIe inbound address translation
+--------------------------------
+
+The PCIe host bridge does not present system memory to devices at the CPU
+physical addresses. On a Compute Module 4 the host bridge node declares::
+
+ $ hexdump -C /proc/device-tree/scb/pcie@7d500000/dma-ranges
+ 02000000 00000004 00000000 00000000 00000000 00000001 00000000
+
+which places CPU physical address 0 at PCIe bus address ``0x4_0000_0000``.
+
+In ``RTE_IOVA_PA`` mode, an IOVA must therefore be the CPU physical
+address plus that offset. EAL reads the translation from the host
+bridge's ``dma-ranges`` property and applies it. Platforms that declare
+no translation, and platforms without a device tree, are unaffected.
+
+A DPDK application logs the offset it found at startup::
+
+ EAL: PCIe bus addresses are offset by 0x400000000 from CPU physical
+ addresses (/proc/device-tree/scb/pcie@7d500000/dma-ranges); applying
+ it to IOVAs
+
+The value can be overridden with the ``DPDK_IOVA_PA_OFFSET`` environment
+variable, given in hexadecimal. Without the translation, a device
+reports link up and counts packets in its own registers while never
+completing a DMA.
+
+Recommended settings
+--------------------
+
+* Add ``pcie_aspm=off`` to the kernel command line, so the PCIe link does
+ not enter a low-power state during a run.
+* Bind the device to ``uio_pci_generic``. There is no IOMMU on this
+ SoC, so ``vfio-pci`` can only be used in unsafe no-IOMMU mode.
diff --git a/doc/guides/linux_gsg/index.rst b/doc/guides/linux_gsg/index.rst
index f739edd6ca..480909ec2c 100644
--- a/doc/guides/linux_gsg/index.rst
+++ b/doc/guides/linux_gsg/index.rst
@@ -20,3 +20,4 @@ Getting Started Guide for Linux
enable_func
nic_perf_intel_platform
amd_platform
+ bcm2711_platform
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 4b3e5d995c..37452b2a3f 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,14 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added PCIe inbound DMA address translation on Linux.**
+
+ EAL now reads the PCIe host bridge "dma-ranges" property from the device
+ tree and applies the translation it declares to IOVAs, so devices on a
+ platform whose inbound window is not identity-mapped, such as the
+ Broadcom BCM2711 on Raspberry Pi 4 and Compute Module 4, can reach
+ system memory.
+
Removed Items
-------------
diff --git a/lib/eal/linux/eal_memory.c b/lib/eal/linux/eal_memory.c
index d9d505d865..75c6ba07c8 100644
--- a/lib/eal/linux/eal_memory.c
+++ b/lib/eal/linux/eal_memory.c
@@ -3,6 +3,7 @@
* Copyright(c) 2013 6WIND S.A.
*/
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
@@ -25,6 +26,7 @@
#include <numaif.h>
#endif
+#include <rte_byteorder.h>
#include <rte_errno.h>
#include <rte_log.h>
#include <rte_memory.h>
@@ -83,6 +85,262 @@ uint64_t eal_get_baseaddr(void)
#endif
}
+/*
+ * On some platforms the address space seen by PCIe devices is not the CPU
+ * physical address space: the host bridge applies a fixed translation to
+ * inbound (device-to-memory) traffic. There, an IOVA in RTE_IOVA_PA mode is
+ * *not* a CPU physical address -- a device DMAing to CPU physical address P
+ * must be programmed with P + offset, or its reads and writes fall outside the
+ * bridge's inbound window and are silently discarded.
+ *
+ * This was found on a Broadcom BCM2711 (Raspberry Pi Compute Module 4, 4GB),
+ * whose host bridge node pcie@7d500000 declares in "dma-ranges" that CPU
+ * physical 0 is reached by devices at PCIe address 0x4_0000_0000 (its outbound
+ * window, in "ranges", starts at PCIe address 0xc000_0000). Without the
+ * translation every DPDK descriptor ring and mbuf address handed to the NIC is
+ * unreachable, and the observed symptom was a device that reported link up and
+ * counted packets in its own registers while never completing a single DMA.
+ *
+ * The translation is read from the "dma-ranges" property of the PCI host
+ * bridge in the device tree, so each platform gets the value it declares
+ * rather than one assumed here. It is zero on identity-mapped platforms and
+ * absent where there is no device tree (x86), and everything below is then a
+ * no-op.
+ */
+#define DT_ROOT_PATH "/proc/device-tree"
+#define IOVA_PA_OFFSET_ENV "DPDK_IOVA_PA_OFFSET"
+#define IOVA_PA_OFFSET_UNSET UINT64_MAX
+#define DT_MAX_SCAN_DEPTH 4
+
+/*
+ * Cached PCIe-bus-address-minus-CPU-physical-address translation. Written at
+ * most once with a value that does not depend on who computes it, and it is a
+ * single aligned 64-bit store, so the benign race between threads racing to
+ * fill it in cannot produce a torn or inconsistent result.
+ */
+static uint64_t iova_pa_offset = IOVA_PA_OFFSET_UNSET;
+
+/* Read a device-tree property into buf, storing its length in *outlen. */
+static int
+dt_read_prop(const char *dir, const char *prop, void *buf, size_t buflen,
+ size_t *outlen)
+{
+ char path[PATH_MAX];
+ size_t n;
+ FILE *f;
+
+ if (snprintf(path, sizeof(path), "%s/%s", dir, prop) >= (int)sizeof(path))
+ return -1;
+ f = fopen(path, "rb");
+ if (f == NULL)
+ return -1;
+ n = fread(buf, 1, buflen, f);
+ if (ferror(f)) {
+ fclose(f);
+ return -1;
+ }
+ fclose(f);
+ *outlen = n;
+ return 0;
+}
+
+/* Read a single-cell device-tree property, e.g. #address-cells. */
+static int
+dt_read_u32(const char *dir, const char *prop, uint32_t *out)
+{
+ uint32_t val;
+ size_t len;
+
+ if (dt_read_prop(dir, prop, &val, sizeof(val), &len) < 0 ||
+ len != sizeof(val))
+ return -1;
+ *out = rte_be_to_cpu_32(val);
+ return 0;
+}
+
+/* Device-tree addresses are big-endian sequences of 32-bit cells. */
+static uint64_t
+dt_read_cells(const uint32_t *cells, uint32_t n)
+{
+ uint64_t val = 0;
+ uint32_t i;
+
+ for (i = 0; i < n; i++)
+ val = (val << 32) | rte_be_to_cpu_32(cells[i]);
+ return val;
+}
+
+/*
+ * Derive the inbound translation from the "dma-ranges" of one PCI host bridge.
+ * Each entry is <pci-address> <parent-address> <size>, where a PCI address is
+ * always 3 cells (phys.hi holds flags, phys.mid/phys.lo hold the address), the
+ * parent address is the parent bus' #address-cells, and the size is this node's
+ * #size-cells.
+ */
+static int
+dt_pci_dma_offset(const char *node, const char *parent, uint64_t *offset)
+{
+ uint32_t cells[256];
+ uint32_t parent_ac, size_c;
+ size_t len, ncells, per_entry, i;
+ uint64_t off = 0;
+ bool first = true;
+
+ if (dt_read_prop(node, "dma-ranges", cells, sizeof(cells), &len) < 0)
+ return -1;
+
+ /* An empty "dma-ranges" declares the bus to be identity mapped. */
+ if (len == 0) {
+ *offset = 0;
+ return 0;
+ }
+
+ if (dt_read_u32(parent, "#address-cells", &parent_ac) < 0 ||
+ dt_read_u32(node, "#size-cells", &size_c) < 0)
+ return -1;
+ /* More than 2 cells cannot be held in a uint64_t. */
+ if (parent_ac == 0 || parent_ac > 2 || size_c > 2)
+ return -1;
+
+ per_entry = 3 + parent_ac + size_c;
+ ncells = len / sizeof(uint32_t);
+ if (ncells == 0 || ncells % per_entry != 0)
+ return -1;
+
+ for (i = 0; i < ncells; i += per_entry) {
+ uint64_t pci_addr = dt_read_cells(&cells[i + 1], 2);
+ uint64_t cpu_addr = dt_read_cells(&cells[i + 3], parent_ac);
+ uint64_t entry_off = pci_addr - cpu_addr;
+
+ if (first) {
+ off = entry_off;
+ first = false;
+ } else if (entry_off != off) {
+ /*
+ * The bridge translates different regions differently,
+ * which a single offset cannot express. Refuse to
+ * guess rather than corrupt every IOVA.
+ */
+ EAL_LOG(WARNING,
+ "%s: non-uniform dma-ranges, cannot derive an IOVA offset",
+ node);
+ return -1;
+ }
+ }
+
+ *offset = off;
+ return 0;
+}
+
+/*
+ * Walk the device tree looking for a PCI host bridge that describes a non-zero
+ * inbound translation, and report the first one found. Systems with several
+ * host bridges translating differently would need per-device IOVAs, which the
+ * IOVA-as-PA model cannot express; we log the node we used so a mismatch is
+ * visible rather than silent.
+ */
+static int
+dt_scan_pci_dma_offset(const char *dir, const char *parent, int depth,
+ uint64_t *offset, char *node, size_t node_len)
+{
+ struct dirent *ent;
+ int ret = -1;
+ DIR *d;
+
+ if (depth > DT_MAX_SCAN_DEPTH)
+ return -1;
+
+ if (parent != NULL) {
+ char type[16];
+ size_t len;
+
+ if (dt_read_prop(dir, "device_type", type, sizeof(type) - 1,
+ &len) == 0) {
+ type[len] = '\0';
+ if (strcmp(type, "pci") == 0 &&
+ dt_pci_dma_offset(dir, parent,
+ offset) == 0 && *offset != 0) {
+ strlcpy(node, dir, node_len);
+ return 0;
+ }
+ }
+ }
+
+ d = opendir(dir);
+ if (d == NULL)
+ return -1;
+ while ((ent = readdir(d)) != NULL) {
+ char child[PATH_MAX];
+
+ /* Skip ".", "..", and the device tree's own dot-properties. */
+ if (ent->d_name[0] == '.')
+ continue;
+ /* procfs may not fill in d_type; opendir() filters non-dirs. */
+ if (ent->d_type != DT_DIR && ent->d_type != DT_UNKNOWN)
+ continue;
+ if (snprintf(child, sizeof(child), "%s/%s", dir,
+ ent->d_name) >= (int)sizeof(child))
+ continue;
+ if (dt_scan_pci_dma_offset(child, dir, depth + 1, offset,
+ node, node_len) == 0) {
+ ret = 0;
+ break;
+ }
+ }
+ closedir(d);
+ return ret;
+}
+
+/* Offset to add to a CPU physical address to obtain the address a PCIe device
+ * must use to reach it. Zero on identity-mapped platforms.
+ */
+static uint64_t
+eal_iova_pa_offset(void)
+{
+ char node[PATH_MAX] = "";
+ uint64_t offset = 0;
+ const char *env;
+ char *end;
+
+ if (iova_pa_offset != IOVA_PA_OFFSET_UNSET)
+ return iova_pa_offset;
+
+ env = getenv(IOVA_PA_OFFSET_ENV);
+ if (env != NULL && env[0] != '\0') {
+ errno = 0;
+ offset = strtoull(env, &end, 0);
+ if (errno != 0 || *end != '\0') {
+ EAL_LOG(ERR, "Invalid %s value '%s', assuming no offset",
+ IOVA_PA_OFFSET_ENV, env);
+ offset = 0;
+ } else {
+ EAL_LOG(NOTICE,
+ "Using IOVA offset 0x%" PRIx64 " from %s",
+ offset, IOVA_PA_OFFSET_ENV);
+ }
+ } else if (dt_scan_pci_dma_offset(DT_ROOT_PATH, NULL, 0, &offset,
+ node, sizeof(node)) == 0) {
+ EAL_LOG(NOTICE,
+ "PCIe bus addresses are offset by 0x%" PRIx64
+ " from CPU physical addresses (%s/dma-ranges); applying it to IOVAs",
+ offset, node);
+ } else {
+ offset = 0;
+ }
+
+ iova_pa_offset = offset;
+ return offset;
+}
+
+/* Convert a CPU physical address into the IOVA a device must be given. */
+static rte_iova_t
+eal_pa_to_iova(phys_addr_t pa)
+{
+ if (pa == RTE_BAD_IOVA)
+ return RTE_BAD_IOVA;
+ return pa + eal_iova_pa_offset();
+}
+
/*
* Get physical address of any mapped virtual address in the current process.
*/
@@ -150,7 +408,7 @@ rte_mem_virt2iova(const void *virtaddr)
{
if (rte_eal_iova_mode() == RTE_IOVA_VA)
return (uintptr_t)virtaddr;
- return rte_mem_virt2phy(virtaddr);
+ return eal_pa_to_iova(rte_mem_virt2phy(virtaddr));
}
/*
@@ -805,7 +1063,10 @@ remap_segment(struct hugepage_file *hugepages, int seg_start, int seg_end)
ms->addr = addr;
ms->hugepage_sz = page_sz;
ms->len = memseg_len;
- ms->iova = hfile->physaddr;
+ /* In IOVA-as-VA mode physaddr has been rewritten to the VA. */
+ ms->iova = rte_eal_iova_mode() == RTE_IOVA_VA ?
+ hfile->physaddr :
+ eal_pa_to_iova(hfile->physaddr);
ms->socket_id = hfile->socket_id;
ms->nchannel = rte_memory_get_nchannel();
ms->nrank = rte_memory_get_nrank();
--
2.34.1
next prev parent reply other threads:[~2026-09-19 8:17 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-18 22:50 [PATCH 0/2] make PCIe DMA work on Broadcom BCM2711 Md Rayhanul Islam
2026-09-18 22:50 ` Md Rayhanul Islam [this message]
2026-09-18 22:50 ` [PATCH 2/2] net/e1000: maintain caches on non-coherent DMA Md Rayhanul Islam
2026-09-19 21:05 ` [PATCH 0/2] make PCIe DMA work on Broadcom BCM2711 Stephen Hemminger
2026-09-21 8:56 ` Bruce Richardson
2026-09-21 15:42 ` Stephen Hemminger
2026-09-24 19:22 ` Md Rayhanul Islam
2026-10-02 20:09 ` [RFC PATCH 0/2] per-device DMA properties, with igb as the first user Md Rayhanul Islam
2026-10-02 20:09 ` [RFC PATCH 1/2] eal/pci: add per-device DMA translation and synchronization Md Rayhanul Islam
2026-10-02 20:09 ` [RFC PATCH 2/2] net/e1000: support non-coherent DMA Md Rayhanul Islam
2026-10-03 16:51 ` [RFC PATCH 0/2] per-device DMA properties, with igb as the first user Stephen Hemminger
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260918225045.177125-2-r97yhan@gmail.com \
--to=r97yhan@gmail.com \
--cc=anatoly.burakov@intel.com \
--cc=bruce.richardson@intel.com \
--cc=chuanyu.xue@uconn.edu \
--cc=dev@dpdk.org \
--cc=md_rayhanul.islam@uconn.edu \
--cc=song.han@uconn.edu \
--cc=thomas@monjalon.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.