Punch line for Linus from Jason:
"Enhance detecting if the PCI fabric can support a peer to peer path
by evaluating the full bi-directional fabric path over each of the
three traffic type ACS controls. Expand the number of fabric
configurations where the P2P algorithm makes the correct determination
of support to include non-uniform ACS and situations where translated
takes different routes from untranslated."
--------------------------------------------------------------------------
PCI P2PDMA applies Request and Completion Redirect throughout both paths.
This misclassifies asymmetric and nested switches, and reports one answer
for every kind of TLP.
Three ACS controls act on TLP attributes the Requester chooses rather
than on the topology: Translation Blocking and Direct Translated P2P act on
a Request's Address Type, and Completion Redirect skips Completions
carrying Relaxed Ordering.
Evaluate each direction at the path divergence, decide every class from the
one walk, and treat a client with ATS enabled as translating unless its
driver declares per-mapping ATS.
This completes the P2PDMA side; dma-buf and mlx5 follow separately.
Signed-off-by: Leon Romanovsky <leonro(a)nvidia.com>
---
Changes in v13:
- Read the Intel SPT PCH ACS Control dword at its nonstandard offset so
Translation Blocking is honored on host routes, addressing the
Sashiko review of patch 10.
- Distinguished unreadable forwarding controls from missing forwarding
and retained redirect advice when clearing controls restores a direct
route, addressing the Sashiko review of patch 5.
- Updated the directional routing documentation for shared ancestors.
- Reused the ACS isolation-test mock setup
- Link to v12: https://patch.msgid.link/20261103-fix-p2p-acs-v4-0-v12-0-912101f93517@nvidi…
Changes in v12:
- Rebaed to v7.4-rc1
- Link to v11: https://patch.msgid.link/20261013-fix-p2p-acs-v4-0-v11-0-72cf8c96e617@nvidi…
Changes in v11:
- Applied the client ATS policy in pci_p2pdma_distance_many() too, as
local Sashiko pointed out.
- Read the ATS Enable bit once per query, so the class and the ATS policy
always agree.
- Documented that pcim_p2pdma_set_ats_per_mapping() needs a successful
pcim_p2pdma_init().
- The path diagnostics no longer read ACS Control or the bridge bus range,
and they report a failed cache store.
- Added a KUnit case for Direct Translated P2P without a redirect.
- Clarified the Relaxed Ordering and diagnostics commit messages.
- Honored device-specific ACS redirection, such as Zhaoxin Downstream
Ports, in a new first patch with a KUnit case, as local Sashiko
pointed out. Relaxed Ordering Completions are redirected at such a
port too.
- Clarified in the documentation that a quirk also redirects Relaxed
Ordering Completions and that Translation Blocking applies only to
upstream Requests, and labelled the ACS routing debug line with its
class, as local Sashiko pointed out.
- Treated ACS registers that read as all ones as unreadable, and named
ports that a quirk redirects instead of suggesting
pci=disable_acs_redir= for them, as local Sashiko pointed out.
- Ignored ACS quirks of endpoints, which only describe traffic between
their own functions.
- Link to v10: https://patch.msgid.link/20261012-fix-p2p-acs-v4-0-v10-0-6ffe7b7a4517@nvidi…
Changes in v10:
- Fixed the Relaxed Ordering description: a Completion carries the
attribute of its Request (PCIe r7.0 sec 2.2.9.1).
- Used the PCIe terms Requester and Completer for TLPs, ACS and ports;
client and provider remain for P2PDMA objects.
- Rebased on pci/p2pdma, which includes the prerequisite P2PDMA fixes.
- Added a topology example to the path divergence patch.
- Removed a repeated sentence from the disable_acs_redir documentation.
- Explained the path divergence in the documentation.
- Link to v9: https://patch.msgid.link/20261001-fix-p2p-acs-v4-0-v9-0-1a8e0f50ddd9@nvidia…
Changes in v9:
- Removed dmabuf patches for now.
- Removed mlx5 per-mapping ATS declaration for now; it comes back with
the dmabuf patches.
- Link to v8: https://patch.msgid.link/20260928-fix-p2p-acs-v4-0-v8-0-404453b9c435@nvidia…
Changes in v8:
- Added extra Reviewed-by from Logan.
- Added patches to take care of ATS per-device vs. per-mapping option
- Link to v7: https://patch.msgid.link/20260920-fix-p2p-acs-v4-0-v7-0-ca0828ab697c@nvidia…
Changes in v7:
- Removed "Document pdev->p2pdma lifetime rules" patch, it gives nothing
after p2pmem fix.
- Split dmabuf patch.
- Tushar retested the series, so added his Tested-by.
- Added Logan's ROB tags and fixed minor documentation issues pointed
by him.
- Link to v6: https://lore.kernel.org/all/20260914-fix-p2p-acs-v4-0-v6-0-5ef07ec9ef06@nvi…
Changes in v6:
- Changed DMABUF to use callbacks and not directly stored pointer.
- Link to v5: https://patch.msgid.link/20260910-fix-p2p-acs-v4-0-v5-0-856087f63c0d@nvidia…
Changes in v5:
- Rebase on the posted fixes.
- Dropped tags from changed patches.
- Remove Egress Control Vector interpretation and coverage.
- Keep enabled Egress Control conservative as a Request redirect.
- Use pci_dbg()/dev_dbg() for diagnostics and drop the "debug" prefix.
- Removed code comments from "Document the pdev->p2pdma lifetime and RCU
rules" patch and reduced description to actual lifetime explanation.
- Added note that Linux assumes that TLPs are in strict-ordering and
untranslated.
- Added code to calculate p2p paths per-TLP type.
- Converted mlx5 to use that new proposed API.
- Link to https://patch.msgid.link/20260821-fix-p2p-acs-v4-0-v4-0-94426b96de73@nvidia…
Changes in v4:
- Reject ACS Violations and unreadable routing state instead of treating
them as host-bridge redirects
- Added Tested-by tags from Tushar Dave
- Added support to asymmetric ACS routing
- Limited redirect checks to the two ports at the path divergence
- Added standalone ACS routing diagnostics for hardware retesting
- Dropped " PCI: Account for Direct Translated P2P in ACS isolation checks" patch
- Link to v3: https://patch.msgid.link/20260811-fix-p2p-acs-v3-0-efc488ee7c03@nvidia.com
Changes in v3:
- Fixed pci_p2pdma_add_resource() error unwinding
- Made pdev->p2pdma teardown wait unconditionally for RCU readers
- Restricted pci_p2pmem_find_many() to pool-backed providers
- Documented the pdev->p2pdma lifetime and RCU rules
- Fixed calc_map_type_and_dist() handling of the verbose argument
- Required the ACS port and target to share a bus before indexing the
Egress Control Vector
- Gave pci_acs_enabled() and pci_acs_path_enabled() a scope, so the ACS
Direct Translated P2P rule no longer stops pci_enable_pasid() from
enabling PASID
- Dropped "Report ACS ports when the paths share no upstream bridge":
the mapping type cannot change without a shared upstream bridge, so
the pci=disable_acs_redir= hint was not actionable there and the ACS
walk only cost config space reads
- Folded the Request Redirect rule into pci_acs_rr_ineffective(), so
pci_acs_flags_enabled() and the Intel SPT PCH quirk share one copy
- Renamed pci_acs_egress_ctrl_set() to pci_acs_egress_ctrl_is_set(), it
reads the bit rather than setting it
- Reworded the blocked-path warning: ACS may also leave the direct route
indeterminate rather than blocked
- Added KUnit coverage for the shared-bus guard, a device with no ACS
capability and an unreadable ACS Control register
- Added the missing Fixes: tags, a second one on the
pci_p2pdma_add_resource() unwinding fix (the dangling devres action
dates to f58ef9d1d135) and one on the Egress Control isolation change
- Link to v2: https://patch.msgid.link/20260806-fix-p2p-acs-v2-0-0cec14812965@nvidia.com
Changes in v2:
- Added Logan's ROB tags
- Added commas in Documentation patch
- Link to v1: https://patch.msgid.link/20260802-fix-p2p-acs-v1-0-a7c5eb64fff6@nvidia.com
---
Leon Romanovsky (19):
PCI/P2PDMA: Honor device-specific ACS redirection
PCI/P2PDMA: Document the TLP attribute assumptions
PCI/P2PDMA: Derive routing from directional ACS controls
PCI: Reject unreadable ACS controls in isolation checks
PCI/P2PDMA: Evaluate ACS controls at the path divergence
PCI/P2PDMA: Document directional ACS routing
PCI/P2PDMA: Collect the path's ACS controls before deciding
PCI/P2PDMA: Answer routing per TLP class
PCI/P2PDMA: Route Relaxed Ordering Completions directly
PCI/P2PDMA: Reject Translated Requests blocked by Translation Blocking
PCI/P2PDMA: Route Translated Requests under Direct Translated P2P
PCI/P2PDMA: Log detailed ACS routing diagnostics
PCI/P2PDMA: Add KUnit tests for the ACS routing decisions
PCI/P2PDMA: Test the ACS P2P routing walk
PCI: Add KUnit coverage for ACS isolation checks
PCI/P2PDMA: Document TLP-class routing
PCI/P2PDMA: Let a client declare that it selects ATS per mapping
PCI/P2PDMA: Evaluate the ATS path for clients with ATS enabled
PCI/P2PDMA: Test the routing of clients with ATS enabled
Documentation/admin-guide/kernel-parameters.txt | 11 +-
Documentation/driver-api/pci/p2pdma.rst | 106 ++
drivers/pci/Kconfig | 15 +
drivers/pci/Makefile | 1 +
drivers/pci/p2pdma.c | 961 ++++++++++++++--
drivers/pci/pci.c | 8 +-
drivers/pci/pci.h | 63 +
drivers/pci/pci_acs_test.c | 1409 +++++++++++++++++++++++
drivers/pci/quirks.c | 23 +-
include/linux/pci-p2pdma.h | 12 +-
10 files changed, 2517 insertions(+), 92 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20260821-fix-p2p-acs-v4-0-e72455e3a261
Best regards,
--
Leon Romanovsky <leonro(a)nvidia.com>
On Thu, Nov 12, 2026 at 7:57 PM Kang Chen <k.chen(a)smail.nju.edu.cn> wrote:
>
> The BPF iterator docs in bpf_iterators.rst specify that the next
> function of an open-coded iterator should keep returning NULL after it
> has been returned once.
>
> Add a subtest that checks this for the dmabuf iterator. It drains an
> iterator, calls bpf_iter_dmabuf_next() once more and checks that the
> result is NULL. The dma-bufs the test creates keep the list from being
> empty, so an iterator that starts over would return one of them.
>
> The existing open_coded subtest uses bpf_for_each(), which stops at the
> first NULL and never calls next() on a drained iterator.
>
> The selftest was originally posted by T.J. Mercier in the v1 thread:
>
> Link: https://lore.kernel.org/bpf/CABdmKX09vLG4U-7FwF-vm_iTy1AjGUPXka08KhWE+qJ0J=…
>
> Co-developed-by: T.J. Mercier <tjmercier(a)google.com>
> Signed-off-by: T.J. Mercier <tjmercier(a)google.com>
> Signed-off-by: Kang Chen <k.chen(a)smail.nju.edu.cn>
This should be:
From: T.J. Mercier <tjmercier(a)google.com>
<unmodified original commit message>
Signed-off-by: T.J. Mercier <tjmercier(a)google.com>
Acked-by: Christian König <christian.koenig(a)amd.com>
Signed-off-by: Kang Chen <k.chen(a)smail.nju.edu.cn>
For example see:
https://lore.kernel.org/all/20261104-v2_20230123_tjmercier_google_com-v4-1-…
and: https://docs.kernel.org/process/submitting-patches.html#from-line
> ---
> v3:
> - Add T.J. Mercier's Co-developed-by and matching Signed-off-by.
> - No code change.
>
> .../selftests/bpf/prog_tests/dmabuf_iter.c | 19 +++++++++++++++++++
> .../testing/selftests/bpf/progs/dmabuf_iter.c | 19 +++++++++++++++++++
> 2 files changed, 38 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c b/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c
> index 1a5dc1adae73..c1f81b5dccdc 100644
> --- a/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c
> +++ b/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c
> @@ -265,6 +265,23 @@ static void subtest_dmabuf_iter_check_open_coded(struct dmabuf_iter *skel, int m
> } while (bpf_map_get_next_key(map_fd, key, key));
> }
>
> +static void subtest_dmabuf_iter_check_next_after_drain(struct dmabuf_iter *skel)
> +{
> + LIBBPF_OPTS(bpf_test_run_opts, topts);
> + int err, fd;
> +
> + fd = bpf_program__fd(skel->progs.iter_dmabuf_next_after_drain);
> +
> + err = bpf_prog_test_run_opts(fd, &topts);
> + if (!ASSERT_OK(err, "test_run_opts err"))
> + return;
> + if (!ASSERT_OK(topts.retval, "test_run_opts retval"))
> + return;
> +
> + /* The test buffers exist, so a restarted iteration would find one */
> + ASSERT_EQ(skel->data->next_after_drain, 0, "next_after_drain");
> +}
> +
> void test_dmabuf_iter(void)
> {
> struct dmabuf_iter *skel = NULL;
> @@ -315,6 +332,8 @@ void test_dmabuf_iter(void)
> }
> if (test__start_subtest("open_coded"))
> subtest_dmabuf_iter_check_open_coded(skel, map_fd);
> + if (test__start_subtest("next_after_drain"))
> + subtest_dmabuf_iter_check_next_after_drain(skel);
>
> destroy:
> destroy_test_buffers();
> diff --git a/tools/testing/selftests/bpf/progs/dmabuf_iter.c b/tools/testing/selftests/bpf/progs/dmabuf_iter.c
> index 9cbb7442646e..d5421b75c39c 100644
> --- a/tools/testing/selftests/bpf/progs/dmabuf_iter.c
> +++ b/tools/testing/selftests/bpf/progs/dmabuf_iter.c
> @@ -99,3 +99,22 @@ int iter_dmabuf_for_each(const void *ctx)
>
> return 0;
> }
> +
> +/* Result of a bpf_iter_dmabuf_next() call after the iterator was drained */
> +__u64 next_after_drain = -1;
> +
> +SEC("syscall")
> +int iter_dmabuf_next_after_drain(const void *ctx)
> +{
> + struct bpf_iter_dmabuf it;
> +
> + bpf_iter_dmabuf_new(&it);
> + while (bpf_iter_dmabuf_next(&it))
> + ;
> +
> + /* Store the raw value; the verifier would remove a branch on it */
> + next_after_drain = (__u64)bpf_iter_dmabuf_next(&it);
> + bpf_iter_dmabuf_destroy(&it);
> +
> + return 0;
> +}
> --
> 2.34.1
>
On Thu, Nov 12, 2026 at 7:57 PM Kang Chen <k.chen(a)smail.nju.edu.cn> wrote:
>
> bpf_iter_dmabuf_next() uses kit->dmabuf == NULL to mean "iteration has
> not started". dma_buf_iter_next() also returns NULL at the end of the
> list, so a next() call after exhaustion restarts from the list head.
>
> The verifier relies on the iterator contract that once next() returns
> NULL it keeps returning NULL: process_iter_next_call() marks the
> iterator DRAINED and treats the result of later next() calls as the
> constant zero. A restarted dmabuf iteration therefore hands the BPF
> program a kernel pointer while the verifier believes R0 == 0.
>
> NULL now means drained. Initialize the state to DMABUF_ITER_START and
> translate it to dma_buf_iter_begin() on the first next(). The iterator
> state is BPF-visible ABI (one pointer wide), so use an invalid pointer
> sentinel instead of growing it.
>
> Fixes: 6eab7ac7c5ee ("bpf: Add open coded dmabuf iterator")
> Signed-off-by: Kang Chen <k.chen(a)smail.nju.edu.cn>
Reviewed-by: T.J. Mercier <tjmercier(a)google.com>
> ---
> v3:
> - No change from v2; reposted with the updated 2/2 attribution.
>
> kernel/bpf/dmabuf_iter.c | 17 ++++++++++++-----
> 1 file changed, 12 insertions(+), 5 deletions(-)
>
> diff --git a/kernel/bpf/dmabuf_iter.c b/kernel/bpf/dmabuf_iter.c
> index cd500248abd9..390f6d730a67 100644
> --- a/kernel/bpf/dmabuf_iter.c
> +++ b/kernel/bpf/dmabuf_iter.c
> @@ -156,6 +156,9 @@ struct bpf_iter_dmabuf_kern {
> struct dma_buf *dmabuf;
> } __aligned(8);
>
> +/* NULL means drained; START means "not started yet". */
> +#define DMABUF_ITER_START ((struct dma_buf *)1L)
> +
> __bpf_kfunc_start_defs();
>
> __bpf_kfunc int bpf_iter_dmabuf_new(struct bpf_iter_dmabuf *it)
> @@ -165,7 +168,7 @@ __bpf_kfunc int bpf_iter_dmabuf_new(struct bpf_iter_dmabuf *it)
> BUILD_BUG_ON(sizeof(*kit) > sizeof(*it));
> BUILD_BUG_ON(__alignof__(*kit) != __alignof__(*it));
>
> - kit->dmabuf = NULL;
> + kit->dmabuf = DMABUF_ITER_START;
> return 0;
> }
>
> @@ -173,10 +176,14 @@ __bpf_kfunc struct dma_buf *bpf_iter_dmabuf_next(struct bpf_iter_dmabuf *it)
> {
> struct bpf_iter_dmabuf_kern *kit = (void *)it;
>
> - if (kit->dmabuf)
> - kit->dmabuf = dma_buf_iter_next(kit->dmabuf);
> - else
> + /* Once NULL is returned, next() has to keep returning NULL. */
> + if (!kit->dmabuf)
> + return NULL;
> +
> + if (kit->dmabuf == DMABUF_ITER_START)
> kit->dmabuf = dma_buf_iter_begin();
> + else
> + kit->dmabuf = dma_buf_iter_next(kit->dmabuf);
>
> return kit->dmabuf;
> }
> @@ -185,7 +192,7 @@ __bpf_kfunc void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it)
> {
> struct bpf_iter_dmabuf_kern *kit = (void *)it;
>
> - if (kit->dmabuf)
> + if (kit->dmabuf && kit->dmabuf != DMABUF_ITER_START)
> dma_buf_put(kit->dmabuf);
> }
>
> --
> 2.34.1
>
The BPF iterator docs in bpf_iterators.rst specify that the next
function of an open-coded iterator should keep returning NULL after it
has been returned once.
Add a subtest that checks this for the dmabuf iterator. It drains an
iterator, calls bpf_iter_dmabuf_next() once more and checks that the
result is NULL. The dma-bufs the test creates keep the list from being
empty, so an iterator that starts over would return one of them.
The existing open_coded subtest uses bpf_for_each(), which stops at the
first NULL and never calls next() on a drained iterator.
Signed-off-by: T.J. Mercier <tjmercier(a)google.com>
---
This depends on Kang Chen's fix and fails without it:
https://lore.kernel.org/bpf/20261110125106.2935677-1-k.chen@smail.nju.edu.c…
Kang, feel free to carry it as 2/2 in your v2.
.../selftests/bpf/prog_tests/dmabuf_iter.c | 19 +++++++++++++++++++
.../testing/selftests/bpf/progs/dmabuf_iter.c | 19 +++++++++++++++++++
2 files changed, 38 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c b/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c
index 1a5dc1adae73..c1f81b5dccdc 100644
--- a/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c
+++ b/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c
@@ -265,6 +265,23 @@ static void subtest_dmabuf_iter_check_open_coded(struct dmabuf_iter *skel, int m
} while (bpf_map_get_next_key(map_fd, key, key));
}
+static void subtest_dmabuf_iter_check_next_after_drain(struct dmabuf_iter *skel)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ int err, fd;
+
+ fd = bpf_program__fd(skel->progs.iter_dmabuf_next_after_drain);
+
+ err = bpf_prog_test_run_opts(fd, &topts);
+ if (!ASSERT_OK(err, "test_run_opts err"))
+ return;
+ if (!ASSERT_OK(topts.retval, "test_run_opts retval"))
+ return;
+
+ /* The test buffers exist, so a restarted iteration would find one */
+ ASSERT_EQ(skel->data->next_after_drain, 0, "next_after_drain");
+}
+
void test_dmabuf_iter(void)
{
struct dmabuf_iter *skel = NULL;
@@ -315,6 +332,8 @@ void test_dmabuf_iter(void)
}
if (test__start_subtest("open_coded"))
subtest_dmabuf_iter_check_open_coded(skel, map_fd);
+ if (test__start_subtest("next_after_drain"))
+ subtest_dmabuf_iter_check_next_after_drain(skel);
destroy:
destroy_test_buffers();
diff --git a/tools/testing/selftests/bpf/progs/dmabuf_iter.c b/tools/testing/selftests/bpf/progs/dmabuf_iter.c
index 9cbb7442646e..d5421b75c39c 100644
--- a/tools/testing/selftests/bpf/progs/dmabuf_iter.c
+++ b/tools/testing/selftests/bpf/progs/dmabuf_iter.c
@@ -99,3 +99,22 @@ int iter_dmabuf_for_each(const void *ctx)
return 0;
}
+
+/* Result of a bpf_iter_dmabuf_next() call after the iterator was drained */
+__u64 next_after_drain = -1;
+
+SEC("syscall")
+int iter_dmabuf_next_after_drain(const void *ctx)
+{
+ struct bpf_iter_dmabuf it;
+
+ bpf_iter_dmabuf_new(&it);
+ while (bpf_iter_dmabuf_next(&it))
+ ;
+
+ /* Store the raw value; the verifier would remove a branch on it */
+ next_after_drain = (__u64)bpf_iter_dmabuf_next(&it);
+ bpf_iter_dmabuf_destroy(&it);
+
+ return 0;
+}
--
2.56.0.567.gdb80ae8cd5-goog
Shared-memory allocator services (e.g., Android's gralloc)
allocate dma-bufs on behalf of client processes in different
cgroups. Today those allocations are charged to the allocator's
own cgroup, leaving client cgroups with no visibility into their
actual memory consumption and defeating resource limits.
This series adds memory cgroup accounting for dma-buf allocations,
building on T.J. Mercier's earlier series [1] and incorporating
feedback from previous versions and LPC discussions [2].
Patch 1 introduces the MEMCG_DMABUF stat and reserve/cancel
and uncharge helpers in mm/memcontrol.c
Patch 2 wires up the charging policy: dma_buf_export() stores
exp_info.memcg and updates the stat, a new charge_fd field in
DMA_HEAP_IOCTL_ALLOC accepts a pidfd or cgroupfd to identify
the target cgroup, and __GFP_ACCOUNT is removed from system-heap
allocations to avoid double counting.
Patch 3 adds DMA_BUF_IOCTL_XFER_CHARGE, a general ioctl on any
dma-buf fd to transfer charges post-allocation. This covers the
double-hop allocator case (SurfaceFlinger -> gralloc -> kernel)
and works for buffers from any exporter (DRM, V4L2, etc.). Charging
is gated by dmabuf->memcg being set at export time, ensuring that
transfer is only applied for buffers backed by system memory where
size reflects actual bytes. If the receiving cgroup lacks capacity
for the charge, the ioctl returns -ENOMEM and the original charge
is preserved.
Patches 4-7 add access control for cross-cgroup charging. Letting
userspace decide who to charge needs to be a privileged operation
because it has material impact on OOM decission and resource limits.
We gate it via LSM hooks for the two charge_fd flavours:
security_dma_buf_charge_task() gates the pidfd path (target
identified by task credentials) and security_dma_buf_charge_cgroup()
gates the cgroupfd path (target identified by cgroup directory label),
with SELinux implementations under a dma_buf object class.
Patch 8 adds selftests.
Known limitations:
- Single-owner model: a dma-buf carries one memcg charge regardless
of how many processes share it.
- Only memcg accounting. CMA heaps would additionally need dmem
controller integration.
- If a transfer of an uncharged buffer fails, the buffer remains
charged to the original cgroup. The allocator (userspace) is
expected to handle this (retry, free, ...).
- The LSM hooks are intentionally narrow. Broader cgroup access controls
(e.g. for the memfd/kexec handover case [2]) could be a separate effort
reusing the same pattern.
- Only system heap sets exp_info.memcg today. Other exporters
(DRM, V4L2, etc.) can opt in by setting it, but are not
covered by this series.
- Related to the above: lazy backing-store allocation is not supported.
This can be extended later, e.g., by adding a flag on the dma-buf
struct for explicitly marking when memory is committed.
[1] https://lore.kernel.org/cgroups/20230109213809.418135-1-tjmercier@google.co…
[2] https://lpc.events/event/20/contributions/2416/
Signed-off-by: Albert Esteve <aesteve(a)redhat.com>
---
Changes in v4:
- Remove memcg_account flag; exp_info.memcg != NULL is the opt-in
- Centralize memcg bookkeeping in dma_buf_export()
- Drop mem_cgroup_commit_dmabuf()
- Make mem_cgroup_transfer_charge() generic (not dma-buf specific)
- Add EXPORT_SYMBOL_GPL for reserve/cancel (system_heap is tristate)
- Reorder: move xfer ioctl before LSM patches
- Serialize dmabuf->memcg updates with the dma-buf resv lock instead of cmpxchg
- Link to v3: https://lore.kernel.org/r/20261027-v2_20230123_tjmercier_google_com-v3-0-78…
Changes in v3:
- Split mem_cgroup_charge_dmabuf() into reserve/commit/cancel three-phase API
- Extract mem_cgroup_get_from_fd() helper shared by allocate and xfer paths
- Rename __padding to flags in struct dma_buf_transfer_charge
- Fix UAF race: move mod_memcg_state() before cmpxchg in transfer
- Add memcg_account opt-in flag; xfer returns -EOPNOTSUPP if unset
- Wrap dma_buf_charge_cgroup hook in #ifdef CONFIG_CGROUPS
- Fix TOCTOU in SELinux charge_cgroup xattr length handling
- Link to v2: https://lore.kernel.org/r/20261021-v2_20230123_tjmercier_google_com-v2-0-ef…
Changes in v2:
- Move charge_pid_fd to charge_fd, accepting cgroupfd in addition
to pidfd per Christian König.
- New DMA_BUF_IOCTL_XFER_CHARGE ioctl for post-allocation charge
transfer on any dma-buf, not just dma-heap buffers per Christian König
and T.J. Mercier.
- Moved __GFP_ACCOUNT removal to patch 1 per Barry Song.
- dma-buf charge/uncharge helpers take struct dma_buf * directly
to avoid the need for caller to manage size calculations.
- Added test_xfer_charge_cgroupfd_self to dmabuf-heap selftests.
- Added LSM and SELinux hooks for cgroupfd-based charging.
- Rebased to v7.3-rc7.
- Link to v1: https://lore.kernel.org/r/20260512-v2_20230123_tjmercier_google_com-v1-0-63…
---
Albert Esteve (7):
dma-heap: charge dma-buf memory via explicit memcg
dma-buf: transfer memcg charges via DMA_BUF_IOCTL_XFER_CHARGE
security: dma-buf: Add dma_buf_charge_task LSM hook
security: dma-buf: Add dma_buf_charge_cgroup LSM hook
selinux: Restrict cross-cgroup dma-buf charging
selinux: Restrict dma-buf direct cgroup charging
selftests/dmabuf-heaps: Add dma-buf memcg accounting tests
T.J. Mercier (1):
memcg: Add MEMCG_DMABUF accounting infrastructure
Documentation/admin-guide/cgroup-v2.rst | 5 +
drivers/dma-buf/dma-buf.c | 50 ++++
drivers/dma-buf/dma-heap.c | 28 +-
drivers/dma-buf/heaps/cma_heap.c | 3 +-
drivers/dma-buf/heaps/system_heap.c | 21 +-
drivers/tee/tee_heap.c | 3 +-
include/linux/dma-buf.h | 9 +
include/linux/dma-heap.h | 3 +-
include/linux/lsm_hook_defs.h | 4 +
include/linux/memcontrol.h | 47 ++++
include/linux/security.h | 18 ++
include/uapi/linux/dma-buf.h | 26 ++
include/uapi/linux/dma-heap.h | 7 +
mm/memcontrol.c | 176 +++++++++++++
security/security.c | 34 +++
security/selinux/hooks.c | 44 ++++
security/selinux/include/classmap.h | 1 +
tools/testing/selftests/cgroup/Makefile | 2 +-
tools/testing/selftests/cgroup/test_memcontrol.c | 290 ++++++++++++++++++++-
tools/testing/selftests/dmabuf-heaps/config | 1 +
tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c | 227 +++++++++++++++-
tools/testing/selftests/dmabuf-heaps/vmtest.sh | 226 ++++++++++++++++
22 files changed, 1210 insertions(+), 15 deletions(-)
---
base-commit: 24a19b953e2a3d0ea5a8c4951a0ab8f32720c907
change-id: 20260508-v2_20230123_tjmercier_google_com-f44fcfb16530
Best regards,
--
Albert Esteve <aesteve(a)redhat.com>
On 11/12/26 04:16, Karl Mehltretter wrote:
> On Sat, Nov 07, 2026 at 12:59:46PM +0100, Laurent Pinchart wrote:
>> Hi Karl,
>>
>> We discussed in v1 how this is not the right approach. Why are you
>> posting a v2 without addressing that ? You need to go back to the design
>> table.
>>
>
> v1 ended with two conflicting positions on non-coherent DMA-BUFs:
>
> - Laurent [1] and Nicolas [2]: end_cpu_access(WRITE) only marks the CPU
> cache dirty, and non-coherent DMA importers clean before starting DMA.
> Persistent DRM/KMS imports then need a sync call on each submission.
> - Christian [3]: devices must access DMA-BUFs coherently with the CPU,
> using begin/end_cpu_access() around CPU accesses. Importing a buffer
> with dirty CPU caches onto a non-coherent device is intentionally
> unsupported. Today cma_heap and system_heap sync all mapped
> attachments inside begin/end_cpu_access().
>
> On my Pi 500+ with a USB UVC camera (640x480 YUYV) and V3D on CMA
> DMA-BUFs:
>
> - Without vb2 CPU sync, V3D reads stale data on 8/8 zero-copy frames.
> - With vb2 begin/end_cpu_access() on cma_heap, zero-copy V3D is clean
> (0/8 stale). CPU capture takes 91 us/frame without a V3D import, and
> 144 us/frame when an idle V3D PRIME import is attached.
> - With a test heap that tracks CPU cache state and syncs imported BOs
> at V3D job submission, zero-copy V3D stays clean (0/8 stale) and the
> idle-V3D CPU read drops back to 89 us/frame.
>
> Question to all: should I look at opt-in cache-state tracking in
> dma-buf (with a sync call before DMA submission), or keep
> begin/end_cpu_access() as is and handle CPU consumers via V4L2 cache
> hints?
A sync call before DMA operation is something we intentionally rejected.
Background is that exporters should export the buffers as they need/use it and not have specialized handling just to serve importers which would be not testable without the importer etc...
What we could do is to consistently use begin/end_cpu_access() and/or let exporters expose if their buffers might be dirty in the CPU cache so that importers can properly reject them.
Regards,
Christian.
>
> [1] https://lore.kernel.org/r/20261021175900.GG15441@killaraus.ideasonboard.com/
> [2] https://lore.kernel.org/r/7e4c7707880e9092180d28484ecf674be29b40a0.camel@nd…
> [3] https://lore.kernel.org/r/1e798600-5a76-412a-959c-94313629baf4@amd.com/
>
> Thanks,
> Karl
On Wed, 11 Nov 2026 16:47:38 +0000
Vincent Donnefort <vdonnefort(a)google.com> wrote:
> On Fri, Nov 06, 2026 at 09:18:26PM +0800, Tao Cui wrote:
> > From: Tao Cui <cuitao(a)kylinos.cn>
> >
> > Creating an instance immediately allocates two full sets of per-cpu
> > ring buffers, array buffer plus snapshot, about 1.375MB per cpu per set
> > on the default config, plus the trace_array and its name. None of it
> > is charged: neither trace.c nor ring_buffer.c passes __GFP_ACCOUNT,
> > while user_events in the same directory accounts everything it
> > allocates.
As the name suggests, "user_events" are for users and are special.
> >
> > Instance creation is currently gated by tracefs file permissions
> > alone, so a
> > rootful container with the host tracefs bound in can create instances
> > until the host runs out of memory.
You mean, "works as expected"?
> Charge the ring buffer pages and
> > the trace_array metadata to the creating cgroup.
> >
> > Signed-off-by: Tao Cui <cuitao(a)kylinos.cn>
> > ---
> > kernel/trace/ring_buffer.c | 8 +++++++-
> > kernel/trace/trace.c | 6 +++---
> > 2 files changed, 10 insertions(+), 4 deletions(-)
> >
> > diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
> > index 1fd28a72e6ee..676c059c112c 100644
> > --- a/kernel/trace/ring_buffer.c
> > +++ b/kernel/trace/ring_buffer.c
> > @@ -441,7 +441,13 @@ static struct buffer_data_page *alloc_cpu_data(int cpu, int order)
> > * gracefully without invoking oom-killer and the system is not
> > * destabilized.
> > */
> > - mflags = GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_COMP | __GFP_ZERO;
> > + /*
> > + * The subbuffers are charged to the memcg of whoever caused the
> > + * allocation: the creator of a trace instance, or root at boot for
> > + * the global trace.
> > + */
> > + mflags = GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_COMP | __GFP_ZERO |
> > + __GFP_ACCOUNT;
> >
> > page = alloc_pages_node(cpu_to_node(cpu), mflags, order);
> > if (!page)
>
> Hum, by default tracefs is root only.
>
> And even if made accessible to other users:
>
> 1. The life span of a trace instance is not tied to the caller.
> 2. The ring buffer is shared by multiple users.
Right. Is there a use case out there that allows non-admin to create an
instance? Tracing ring buffers can expose a lot and should not be given
to unprivileged users.
What exactly is this trying to achieve?
-- Steve
>
> Overall I am not convinced charging the creator makes much sense.
>
> Eventually a few allocations for iterators could be GFP_ACCOUNT.
>
On Tue, Nov 10, 2026 at 4:57 AM Kang Chen <k.chen(a)smail.nju.edu.cn> wrote:
>
> bpf_iter_dmabuf_next() uses kit->dmabuf == NULL to mean "iteration has
> not started". dma_buf_iter_next() also returns NULL at the end of the
> list, so a next() call after exhaustion restarts from the list head.
>
> The verifier relies on the iterator contract that once next() returns
> NULL it keeps returning NULL: process_iter_next_call() marks the
> iterator DRAINED and treats the result of later next() calls as the
> constant zero. A restarted dmabuf iteration therefore hands the BPF
> program a kernel pointer while the verifier believes R0 == 0.
>
> Use an invalid pointer value as the drained sentinel so the iterator
> state keeps its one-pointer ABI, and skip dma_buf_put() for it.
>
> Fixes: 6eab7ac7c5ee ("bpf: Add open coded dmabuf iterator")
> Signed-off-by: Kang Chen <k.chen(a)smail.nju.edu.cn>
Thanks for this fix. The cgroup (bpf_iter_css_next) and scx_dsq
(bpf_iter_scx_dsq_next) iterators look like they also have this
problem.
> ---
> kernel/bpf/dmabuf_iter.c | 23 ++++++++++++++++++++++-
> 1 file changed, 22 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/bpf/dmabuf_iter.c b/kernel/bpf/dmabuf_iter.c
> index cd500248abd9..a2b7c38f9ae6 100644
> --- a/kernel/bpf/dmabuf_iter.c
> +++ b/kernel/bpf/dmabuf_iter.c
> @@ -156,6 +156,13 @@ struct bpf_iter_dmabuf_kern {
> struct dma_buf *dmabuf;
> } __aligned(8);
>
> +/*
> + * The iterator state is one pointer wide because struct bpf_iter_dmabuf is
> + * part of the generated vmlinux.h ABI. Use an invalid pointer value as the
> + * "drained" sentinel instead of growing the state.
> + */
This comment explains why this patch uses a sentinel instead of
growing the state, but that doesn't help anyone reading this code in
the future. This belongs in the commit message, not a code comment.
The real constraint is that the size of bpf_iter_dmabuf is
user-visible API, so it can't grow without breaking backwards
compatibility.
> +#define DMABUF_ITER_DONE ((struct dma_buf *)1L)
I would prefer this to be DMABUF_ITER_START to mirror the kmem_cache
iterator. Then the second if (!kit->dmabuf) block in
bpf_iter_dmabuf_next() below could also be removed.
> +
> __bpf_kfunc_start_defs();
>
> __bpf_kfunc int bpf_iter_dmabuf_new(struct bpf_iter_dmabuf *it)
> @@ -173,11 +180,25 @@ __bpf_kfunc struct dma_buf *bpf_iter_dmabuf_next(struct bpf_iter_dmabuf *it)
> {
> struct bpf_iter_dmabuf_kern *kit = (void *)it;
>
> + /*
> + * The verifier assumes that an iterator which returned NULL keeps
> + * returning NULL (see process_iter_next_call()). Without this check
> + * the NULL left by the last dma_buf_iter_next() would be interpreted
> + * as "not started" and the iteration would restart from the list head.
> + */
Same here, this is commit message material. If you want to add a code
comment, referencing the rule from bpf_iterators.rst should be enough:
/* Once NULL is returned, subsequent next calls should keep returning NULL. */
> + if (kit->dmabuf == DMABUF_ITER_DONE)
> + return NULL;
> +
> if (kit->dmabuf)
> kit->dmabuf = dma_buf_iter_next(kit->dmabuf);
> else
> kit->dmabuf = dma_buf_iter_begin();
>
> + if (!kit->dmabuf) {
> + kit->dmabuf = DMABUF_ITER_DONE;
> + return NULL;
> + }
> +
> return kit->dmabuf;
> }
>
> @@ -185,7 +206,7 @@ __bpf_kfunc void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it)
> {
> struct bpf_iter_dmabuf_kern *kit = (void *)it;
>
> - if (kit->dmabuf)
> + if (kit->dmabuf && kit->dmabuf != DMABUF_ITER_DONE)
> dma_buf_put(kit->dmabuf);
> }
>
> --
> 2.34.1
I think we should also add a test for this. I've sent one as a reply
to this thread. It fails without your fix and passes with it. Feel
free to add it as 2/2 in v2.
Adding T.J. to comment.
Regards,
Christian.
On 11/10/26 13:51, Kang Chen wrote:
> bpf_iter_dmabuf_next() uses kit->dmabuf == NULL to mean "iteration has
> not started". dma_buf_iter_next() also returns NULL at the end of the
> list, so a next() call after exhaustion restarts from the list head.
>
> The verifier relies on the iterator contract that once next() returns
> NULL it keeps returning NULL: process_iter_next_call() marks the
> iterator DRAINED and treats the result of later next() calls as the
> constant zero. A restarted dmabuf iteration therefore hands the BPF
> program a kernel pointer while the verifier believes R0 == 0.
>
> Use an invalid pointer value as the drained sentinel so the iterator
> state keeps its one-pointer ABI, and skip dma_buf_put() for it.
>
> Fixes: 6eab7ac7c5ee ("bpf: Add open coded dmabuf iterator")
> Signed-off-by: Kang Chen <k.chen(a)smail.nju.edu.cn>
> ---
> kernel/bpf/dmabuf_iter.c | 23 ++++++++++++++++++++++-
> 1 file changed, 22 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/bpf/dmabuf_iter.c b/kernel/bpf/dmabuf_iter.c
> index cd500248abd9..a2b7c38f9ae6 100644
> --- a/kernel/bpf/dmabuf_iter.c
> +++ b/kernel/bpf/dmabuf_iter.c
> @@ -156,6 +156,13 @@ struct bpf_iter_dmabuf_kern {
> struct dma_buf *dmabuf;
> } __aligned(8);
>
> +/*
> + * The iterator state is one pointer wide because struct bpf_iter_dmabuf is
> + * part of the generated vmlinux.h ABI. Use an invalid pointer value as the
> + * "drained" sentinel instead of growing the state.
> + */
> +#define DMABUF_ITER_DONE ((struct dma_buf *)1L)
> +
> __bpf_kfunc_start_defs();
>
> __bpf_kfunc int bpf_iter_dmabuf_new(struct bpf_iter_dmabuf *it)
> @@ -173,11 +180,25 @@ __bpf_kfunc struct dma_buf *bpf_iter_dmabuf_next(struct bpf_iter_dmabuf *it)
> {
> struct bpf_iter_dmabuf_kern *kit = (void *)it;
>
> + /*
> + * The verifier assumes that an iterator which returned NULL keeps
> + * returning NULL (see process_iter_next_call()). Without this check
> + * the NULL left by the last dma_buf_iter_next() would be interpreted
> + * as "not started" and the iteration would restart from the list head.
> + */
> + if (kit->dmabuf == DMABUF_ITER_DONE)
> + return NULL;
> +
> if (kit->dmabuf)
> kit->dmabuf = dma_buf_iter_next(kit->dmabuf);
> else
> kit->dmabuf = dma_buf_iter_begin();
>
> + if (!kit->dmabuf) {
> + kit->dmabuf = DMABUF_ITER_DONE;
> + return NULL;
> + }
> +
> return kit->dmabuf;
> }
>
> @@ -185,7 +206,7 @@ __bpf_kfunc void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it)
> {
> struct bpf_iter_dmabuf_kern *kit = (void *)it;
>
> - if (kit->dmabuf)
> + if (kit->dmabuf && kit->dmabuf != DMABUF_ITER_DONE)
> dma_buf_put(kit->dmabuf);
> }
>
> --
> 2.34.1
>
On Wed, Nov 11, 2026 at 12:40:05PM +0530, Jayant Shekhar wrote:
>
> On 27-10-2026 01:14, Rob Herring (Arm) wrote:
> > On Mon, 26 Oct 2026 11:51:11 +0530, Jayant Shekhar wrote:
> > > DCP platforms place the display pipeline under firmware control, so Linux
> > > cannot discover display resources through direct register access. The DRM
> > > driver therefore needs device-tree descriptions of the Qualcomm transport
> > > wrapper, display topology, IOMMU contexts, and panel connections.
> > >
> > > Add bindings for the Qualcomm DCP transport wrapper and VirtIO display
> > > subsystem. Document the IPCC power-notify resources, transport and
> > > framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and
> > > DSI panel connections. The wrapper creates a standard VirtIO-MMIO child
> > > after the IPCC power-notify sequence has completed.
> > >
> > > Assisted-by: LLM
> > > Signed-off-by: Jayant Shekhar <jayant.shekhar(a)oss.qualcomm.com>
> > > ---
> > > .../bindings/display/msm/qcom,dcp-dsi.yaml | 79 ++++++++++++
> > > .../devicetree/bindings/display/msm/qcom,dcp.yaml | 123 +++++++++++++++++++
> > > .../bindings/virtio/qcom,dcp-virtio.yaml | 132 +++++++++++++++++++++
> > > 3 files changed, 334 insertions(+)
> > >
> > My bot found errors running 'make dt_binding_check' on your patch:
> >
> > yamllint warnings/errors:
> >
> > dtschema/dtc warnings/errors:
> > /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml: iommu-ranges: multiple incompatible types: uint32-array
> > /usr/local/lib/python3.13/dist-packages/dtschema/schemas/iommu/iommu.yaml: iommu-ranges: multiple incompatible types: uint32-matrix
> > Could not decode node virtio@9be8000 property iommu-ranges with value b'\x00\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00'
> >
> > Traceback (most recent call last):
> > File "/usr/local/bin/dt-validate", line 8, in <module>
> > sys.exit(main())
> > ~~~~^^
> > File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb_validate.py", line 443, in main
> > dtb_diagnostics = sg.check_dtb(filename)
> > File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb_validate.py", line 331, in check_dtb
> > dt = self.validator.decode_dtb(f.read(), decode_errors)
> > File "/usr/local/lib/python3.13/dist-packages/dtschema/validator.py", line 631, in decode_dtb
> > return [dtschema.dtb.fdt_unflatten(self, dtb)]
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
> > File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 591, in fdt_unflatten
> > dt = fdt_scan_node(validator, fdt, '/', offset)
> > File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 320, in fdt_scan_node
> > node = fdt_scan_node(validator, fdt, nodename, offset)
> > File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 320, in fdt_scan_node
> > node = fdt_scan_node(validator, fdt, nodename, offset)
> > File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 312, in fdt_scan_node
> > node_dict = node_props(validator, fdt, nodename, offset)
> > File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 258, in node_props
> > props_dict[p.name] = prop_value(validator, nodename, p)
> > ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
> > File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 185, in prop_value
> > if len(p) / type_format[fmt].size >= min_dim:
> > ~~~~~~~~~~~^^^^^
> > KeyError: 'uint32-array'
> >
> > doc reference errors (make refcheckdocs):
> >
> > See https://patchwork.kernel.org/project/devicetree/patch/20261026-jshekhar-dcp…
> >
> > The base for the series is generally the latest rc1. A different dependency
> > should be noted in *this* patch.
> >
> > If you already ran 'make dt_binding_check' and didn't see the above
> > error(s), then make sure 'yamllint' is installed and dt-schema is up to
> > date:
> >
> > pip3 install dtschema --upgrade
> >
> > Please check and re-submit after running the above command yourself. Note
> > that DT_SCHEMA_FILES can be set to your schema file to speed up checking
> > your schema. However, it must be unset to test all examples with your schema.
>
>
> There is a dependent patch which adds iommu-ranges, I had tried with that
> locally and it passes.
Where in the patch does it say that? The maintainer applying this series
needs to know about the dependency, hence the report.
b4 provides for ways to define the base and any dependencies. Use that
and then you won't get this report.
Rob