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@redhat.com --- 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'