[PATCH RFC v2 2/8] dma-heap: charge dma-buf memory via explicit memcg
From: Albert Esteve
Date: Wed Oct 21 2026 - 11:50:48 EST
On embedded platforms a central process often allocates dma-buf
memory on behalf of client applications. Without a way to
attribute the charge to the requesting client's cgroup, the
cost lands on the allocator, making per-cgroup memory limits
ineffective for the actual consumers.
Add charge_fd to struct dma_heap_allocation_data. When set to
a valid pidfd or cgroupfd, DMA_HEAP_IOCTL_ALLOC resolves the target
task's or cgroup's memcg and charges the buffer there via
mem_cgroup_charge_dmabuf() inside dma_heap_ioctl_allocate().
Without charge_fd, and with the mem_accounting module parameter enabled,
the buffer is charged to the allocator's own cgroup.
Usage examples:
1. Central allocator charging to a client at allocation time.
The allocator knows the client's PID (e.g., from binder's
sender_pid) and uses pidfd to attribute the charge:
pid_t client_pid = txn->sender_pid;
int pidfd = pidfd_open(client_pid, 0);
struct dma_heap_allocation_data alloc = {
.len = buffer_size,
.fd_flags = O_RDWR | O_CLOEXEC,
.charge_fd = pidfd,
};
ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &alloc);
close(pidfd);
/* alloc.fd is now charged to client's cgroup */
2. Default allocation (no pidfd, mem_accounting=1).
When charge_fd is not set and the mem_accounting module
parameter is enabled, the buffer is charged to the allocator's
own cgroup:
struct dma_heap_allocation_data alloc = {
.len = buffer_size,
.fd_flags = O_RDWR | O_CLOEXEC,
};
ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &alloc);
/* charged to current process's cgroup */
Current limitations:
- Single-owner model: a dma-buf carries one memcg charge regardless of
how many processes share it. Means only the first owner (and exporter)
of the shared buffer bears the charge.
- Only memcg accounting supported. While this makes sense for system
heap buffers, other heaps (e.g., CMA heaps) will require selectively
charging also for the dmem controller.
- Charging CMA-backed heaps to memcg charges unreclaimable memory:
when a cgroup hits its limit, try_charge_memcg() may trigger a
reclaim pass that cannot free CMA pages. This is consistent with
other unreclaimable memcg charges. A refinement for the next version
could move charging into per-heap callbacks and skip the futile
reclaim.
Signed-off-by: Albert Esteve <aesteve@xxxxxxxxxx>
---
Documentation/admin-guide/cgroup-v2.rst | 5 +--
drivers/dma-buf/dma-heap.c | 54 +++++++++++++++++++++++++++++++--
include/uapi/linux/dma-heap.h | 7 +++++
3 files changed, 62 insertions(+), 4 deletions(-)
diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index a3265a4a953e..66ddba678784 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1663,8 +1663,9 @@ The following nested keys are defined.
structures.
dmabuf (npn)
- Amount of memory used for exported DMA buffers allocated by the cgroup.
- Stays with the allocating cgroup regardless of how the buffer is shared.
+ Amount of memory used for exported DMA buffers allocated by or on
+ behalf of the cgroup. Stays with the allocating cgroup regardless
+ of how the buffer is shared.
workingset_refault_anon
Number of refaults of previously evicted anonymous pages.
diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c
index 8a6c2b6e0cc6..be77a4c168ab 100644
--- a/drivers/dma-buf/dma-heap.c
+++ b/drivers/dma-buf/dma-heap.c
@@ -7,14 +7,18 @@
*/
#include <linux/cdev.h>
+#include <linux/cgroup.h>
#include <linux/device.h>
#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
+#include <linux/memcontrol.h>
+#include <linux/sched/mm.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/list.h>
#include <linux/minmax.h>
#include <linux/nospec.h>
+#include <linux/pidfd.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <linux/xarray.h>
@@ -75,9 +79,14 @@ static int dma_heap_open(struct inode *inode, struct file *file)
static struct dma_buf *dma_heap_ioctl_allocate(struct file *file, void *data)
{
+ struct cgroup *cgrp;
+ struct cgroup_subsys_state *css;
struct dma_heap_allocation_data *heap_allocation = data;
struct dma_heap *heap = file->private_data;
struct dma_buf *dmabuf;
+ struct mem_cgroup *memcg = NULL;
+ struct task_struct *task;
+ unsigned int pidfd_flags;
int fd;
size_t len;
@@ -90,15 +99,56 @@ static struct dma_buf *dma_heap_ioctl_allocate(struct file *file, void *data)
if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS)
return ERR_PTR(-EINVAL);
+ if (heap_allocation->charge_fd) {
+ cgrp = cgroup_get_from_fd(heap_allocation->charge_fd);
+ if (!IS_ERR(cgrp)) {
+ /* charge_fd is a cgroupfd */
+ rcu_read_lock();
+ css = cgroup_css(cgrp, &memory_cgrp_subsys);
+ if (!css || !css_tryget_online(css)) {
+ rcu_read_unlock();
+ cgroup_put(cgrp);
+ return ERR_PTR(-ENOENT);
+ }
+
+ memcg = mem_cgroup_from_css(css);
+ rcu_read_unlock();
+ cgroup_put(cgrp);
+ } else {
+ /* charge_fd is a pidfd */
+ task = pidfd_get_task(heap_allocation->charge_fd, &pidfd_flags);
+ if (IS_ERR(task))
+ return ERR_CAST(task);
+
+ memcg = get_mem_cgroup_from_mm(task->mm);
+ put_task_struct(task);
+ }
+ }
+
len = PAGE_ALIGN(heap_allocation->len);
- if (!len)
+ if (!len) {
+ mem_cgroup_put(memcg);
return ERR_PTR(-EINVAL);
+ }
dmabuf = heap->ops->allocate(heap, len, heap_allocation->fd_flags,
heap_allocation->heap_flags);
- if (IS_ERR(dmabuf))
+ if (IS_ERR(dmabuf)) {
+ mem_cgroup_put(memcg);
return dmabuf;
+ }
+
+ if (!memcg && mem_accounting)
+ memcg = get_mem_cgroup_from_mm(current->mm);
+
+ if (memcg) {
+ if (!mem_cgroup_charge_dmabuf(dmabuf, memcg, GFP_KERNEL)) {
+ mem_cgroup_put(memcg);
+ dma_buf_put(dmabuf);
+ return ERR_PTR(-ENOMEM);
+ }
+ }
fd = get_unused_fd_flags(heap_allocation->fd_flags);
if (fd < 0) {
diff --git a/include/uapi/linux/dma-heap.h b/include/uapi/linux/dma-heap.h
index a4cf716a49fa..bfcd1c25bc7e 100644
--- a/include/uapi/linux/dma-heap.h
+++ b/include/uapi/linux/dma-heap.h
@@ -29,6 +29,11 @@
* handle to the allocated dma-buf
* @fd_flags: file descriptor flags used when allocating
* @heap_flags: flags passed to heap
+ * @charge_fd: optional pidfd or cgroupfd whose cgroup should be
+ * charged for this allocation; 0 means charge the calling
+ * process's cgroup when the mem_accounting module parameter
+ * is enabled, or skip charging otherwise
+ * @__padding: reserved, must be zero
*
* Provided by userspace as an argument to the ioctl
*/
@@ -37,6 +42,8 @@ struct dma_heap_allocation_data {
__u32 fd;
__u32 fd_flags;
__u64 heap_flags;
+ __u32 charge_fd;
+ __u32 __padding;
};
#define DMA_HEAP_IOC_MAGIC 'H'
--
2.55.0