* Re: [PATCH RFC 2/5] dma-heap: charge dma-buf memory via explicit memcg
From: Albert Esteve @ 2026-05-13 18:39 UTC (permalink / raw)
To: T.J. Mercier
Cc: Christian König, Tejun Heo, Johannes Weiner,
Michal Koutný, Jonathan Corbet, Shuah Khan, Sumit Semwal,
Michal Hocko, Roman Gushchin, Shakeel Butt, Muchun Song,
Andrew Morton, Benjamin Gaignard, Brian Starkey, John Stultz,
Christian Brauner, Paul Moore, James Morris, Serge E. Hallyn,
Stephen Smalley, Ondrej Mosnacek, Shuah Khan, cgroups, linux-doc,
linux-kernel, linux-media, dri-devel, linaro-mm-sig, linux-mm,
linux-security-module, selinux, linux-kselftest, mripard,
echanude
In-Reply-To: <CABdmKX3R5faNgFva-HHVhtTcxJ0_BK9Rei3iTQcA+SRwdKv1Aw@mail.gmail.com>
On Wed, May 13, 2026 at 6:39 PM T.J. Mercier <tjmercier@google.com> wrote:
>
> On Wed, May 13, 2026 at 5:41 AM Albert Esteve <aesteve@redhat.com> wrote:
> >
> > On Tue, May 12, 2026 at 12:14 PM Christian König
> > <christian.koenig@amd.com> wrote:
> > >
> > > On 5/12/26 11:10, Albert Esteve wrote:
> > > > 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_pid_fd to struct dma_heap_allocation_data. When set to
> > > > a valid pidfd, DMA_HEAP_IOCTL_ALLOC resolves the target task's
> > > > memcg and charges the buffer there via mem_cgroup_charge_dmabuf()
> > > > inside dma_heap_buffer_alloc(). Without charge_pid_fd, and with
> > > > the mem_accounting module parameter enabled, the buffer is charged
> > > > to the allocator's own cgroup.
> > > >
> > > > Additionally, commit 3c227be90659 ("dma-buf: system_heap: account for
> > > > system heap allocation in memcg") adds __GFP_ACCOUNT to system-heap
> > > > page allocations. Keeping __GFP_ACCOUNT would charge the same pages
> > > > twice (once to kmem, once to MEMCG_DMABUF), thus remove it and route
> > > > all accounting through a single MEMCG_DMABUF path.
> > > >
> > > > 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_pid_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_pid_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.
> > >
> > > Well that doesn't looks soo bad, it at least seems to tackle the problem at hand for Android and some of other embedded use cases.
> > >
> > > I'm just not sure if this is future prove and will work for all use cases, e.g. cloud gaming, native context for automotive etc...
> > >
> > > Essentially the problem boils down to two limitations:
> > > 1) a piece of memory can only be charged to one cgroup, the framework doesn't has a concept of charging shared memory to multiple groups
> > > 2) when memory references in the form of file descriptors are passed between applications we have no way of changing the accounting to a different cgroup
> > >
> > > The passing of the memory reference already has a well defined uAPI and if we could solve those two limitations we not only solve the problem without introducing new uAPI (with potential new security risks) but also solve it for all other use cases which uses file descriptors as well as. E.g. memfd, accel and GPU drivers etc...
> >
> > Honestly, adding a hook to fd-passing uAPI to manage charge transfers
> > sounds like a promising solution requiring no uAPI changes. However,
> > it still does not cover all paths, e.g., dup() or fork(). And shared
> > memory sounds like a hard one to tackle, where deciding the best
> > policy is more a per-usecase thing and would probably require
> > userspace configuration.
>
> I'm curious if anyone knows of a use case where FDs aren't involved at
> all? It's possible to fork() or clone() with only a dmabuf mapping and
> no FD. That sounds strange, and I'm not sure there's a real usecase
> for transferring ownership with that approach, but figured I'd at
> least pose the question.
Yeah, that's a good point. I do not really have a usecase myself for
fork(), just thought of it as a posible gap/uncovered path.
>
> > All in all, charge_pid_fd covers a
> > well-defined and immediately practical subset. The UAPI cost is small
> > and the mechanism is explicit about what it does and doesn't solve. A
> > general solution, if it ever converges, would likely supersede
> > charge_pid_fd for most cases, which is a fine outcome if it solves the
> > problem more completely.
> >
> > Either way, if you have a specific approach in mind for solving any of
> > the above limitations, I'd be happy to look into it further.
> >
> > BR,
> > Albert.
> >
> > >
> > > On the other hand it is really nice to finally see this tackled for at least DMA-buf heaps. On the GPU side I have seen just another try of a driver doing some kind of special driver specific accounting to solve this just a few weeks ago. And to be honest such single driver island approach have the tendency to break more often that they are working correctly.
> > >
> > > Regards,
> > > Christian.
> > >
> > > >
> > > > Signed-off-by: Albert Esteve <aesteve@redhat.com>
> > > > ---
> > > > Documentation/admin-guide/cgroup-v2.rst | 5 ++--
> > > > drivers/dma-buf/dma-buf.c | 16 ++++---------
> > > > drivers/dma-buf/dma-heap.c | 42 ++++++++++++++++++++++++++++++---
> > > > drivers/dma-buf/heaps/system_heap.c | 2 --
> > > > include/uapi/linux/dma-heap.h | 6 +++++
> > > > 5 files changed, 53 insertions(+), 18 deletions(-)
> > > >
> > > > diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> > > > index 8bdbc2e866430..824d269531eb1 100644
> > > > --- a/Documentation/admin-guide/cgroup-v2.rst
> > > > +++ b/Documentation/admin-guide/cgroup-v2.rst
> > > > @@ -1636,8 +1636,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-buf.c b/drivers/dma-buf/dma-buf.c
> > > > index ce02377f48908..23fb758b78297 100644
> > > > --- a/drivers/dma-buf/dma-buf.c
> > > > +++ b/drivers/dma-buf/dma-buf.c
> > > > @@ -181,8 +181,11 @@ static void dma_buf_release(struct dentry *dentry)
> > > > */
> > > > BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active);
> > > >
> > > > - mem_cgroup_uncharge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > > > - mem_cgroup_put(dmabuf->memcg);
> > > > + if (dmabuf->memcg) {
> > > > + mem_cgroup_uncharge_dmabuf(dmabuf->memcg,
> > > > + PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > > > + mem_cgroup_put(dmabuf->memcg);
> > > > + }
> > > >
> > > > dmabuf->ops->release(dmabuf);
> > > >
> > > > @@ -764,13 +767,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> > > > dmabuf->resv = resv;
> > > > }
> > > >
> > > > - dmabuf->memcg = get_mem_cgroup_from_mm(current->mm);
> > > > - if (!mem_cgroup_charge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE,
> > > > - GFP_KERNEL)) {
> > > > - ret = -ENOMEM;
> > > > - goto err_memcg;
> > > > - }
> > > > -
> > > > file->private_data = dmabuf;
> > > > file->f_path.dentry->d_fsdata = dmabuf;
> > > > dmabuf->file = file;
> > > > @@ -781,8 +777,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> > > >
> > > > return dmabuf;
> > > >
> > > > -err_memcg:
> > > > - mem_cgroup_put(dmabuf->memcg);
> > > > err_file:
> > > > fput(file);
> > > > err_module:
> > > > diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c
> > > > index ac5f8685a6494..ff6e259afcdc0 100644
> > > > --- a/drivers/dma-buf/dma-heap.c
> > > > +++ b/drivers/dma-buf/dma-heap.c
> > > > @@ -7,13 +7,17 @@
> > > > */
> > > >
> > > > #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/nospec.h>
> > > > +#include <linux/pidfd.h>
> > > > #include <linux/syscalls.h>
> > > > #include <linux/uaccess.h>
> > > > #include <linux/xarray.h>
> > > > @@ -55,10 +59,12 @@ MODULE_PARM_DESC(mem_accounting,
> > > > "Enable cgroup-based memory accounting for dma-buf heap allocations (default=false).");
> > > >
> > > > static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > > > - u32 fd_flags,
> > > > - u64 heap_flags)
> > > > + u32 fd_flags, u64 heap_flags,
> > > > + struct mem_cgroup *charge_to)
> > > > {
> > > > struct dma_buf *dmabuf;
> > > > + unsigned int nr_pages;
> > > > + struct mem_cgroup *memcg = charge_to;
> > > > int fd;
> > > >
> > > > /*
> > > > @@ -73,6 +79,22 @@ static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > > > if (IS_ERR(dmabuf))
> > > > return PTR_ERR(dmabuf);
> > > >
> > > > + nr_pages = len / PAGE_SIZE;
> > > > +
> > > > + if (memcg)
> > > > + css_get(&memcg->css);
> > > > + else if (mem_accounting)
> > > > + memcg = get_mem_cgroup_from_mm(current->mm);
> > > > +
> > > > + if (memcg) {
> > > > + if (!mem_cgroup_charge_dmabuf(memcg, nr_pages, GFP_KERNEL)) {
> > > > + mem_cgroup_put(memcg);
> > > > + dma_buf_put(dmabuf);
> > > > + return -ENOMEM;
> > > > + }
> > > > + dmabuf->memcg = memcg;
> > > > + }
> > > > +
> > > > fd = dma_buf_fd(dmabuf, fd_flags);
> > > > if (fd < 0) {
> > > > dma_buf_put(dmabuf);
> > > > @@ -102,6 +124,9 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > > > {
> > > > struct dma_heap_allocation_data *heap_allocation = data;
> > > > struct dma_heap *heap = file->private_data;
> > > > + struct mem_cgroup *memcg = NULL;
> > > > + struct task_struct *task;
> > > > + unsigned int pidfd_flags;
> > > > int fd;
> > > >
> > > > if (heap_allocation->fd)
> > > > @@ -113,9 +138,20 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > > > if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS)
> > > > return -EINVAL;
> > > >
> > > > + if (heap_allocation->charge_pid_fd) {
> > > > + task = pidfd_get_task(heap_allocation->charge_pid_fd, &pidfd_flags);
> > > > + if (IS_ERR(task))
> > > > + return PTR_ERR(task);
> > > > +
> > > > + memcg = get_mem_cgroup_from_mm(task->mm);
> > > > + put_task_struct(task);
> > > > + }
> > > > +
> > > > fd = dma_heap_buffer_alloc(heap, heap_allocation->len,
> > > > heap_allocation->fd_flags,
> > > > - heap_allocation->heap_flags);
> > > > + heap_allocation->heap_flags,
> > > > + memcg);
> > > > + mem_cgroup_put(memcg);
> > > > if (fd < 0)
> > > > return fd;
> > > >
> > > > diff --git a/drivers/dma-buf/heaps/system_heap.c b/drivers/dma-buf/heaps/system_heap.c
> > > > index 03c2b87cb1112..95d7688167b93 100644
> > > > --- a/drivers/dma-buf/heaps/system_heap.c
> > > > +++ b/drivers/dma-buf/heaps/system_heap.c
> > > > @@ -385,8 +385,6 @@ static struct page *alloc_largest_available(unsigned long size,
> > > > if (max_order < orders[i])
> > > > continue;
> > > > flags = order_flags[i];
> > > > - if (mem_accounting)
> > > > - flags |= __GFP_ACCOUNT;
> > > > page = alloc_pages(flags, orders[i]);
> > > > if (!page)
> > > > continue;
> > > > diff --git a/include/uapi/linux/dma-heap.h b/include/uapi/linux/dma-heap.h
> > > > index a4cf716a49fa6..e02b0f8cbc6a1 100644
> > > > --- a/include/uapi/linux/dma-heap.h
> > > > +++ b/include/uapi/linux/dma-heap.h
> > > > @@ -29,6 +29,10 @@
> > > > * handle to the allocated dma-buf
> > > > * @fd_flags: file descriptor flags used when allocating
> > > > * @heap_flags: flags passed to heap
> > > > + * @charge_pid_fd: optional pidfd of the process whose cgroup should be
> > > > + * charged for this allocation; 0 means charge the calling
> > > > + * process's cgroup
> > > > + * @__padding: reserved, must be zero
> > > > *
> > > > * Provided by userspace as an argument to the ioctl
> > > > */
> > > > @@ -37,6 +41,8 @@ struct dma_heap_allocation_data {
> > > > __u32 fd;
> > > > __u32 fd_flags;
> > > > __u64 heap_flags;
> > > > + __u32 charge_pid_fd;
> > > > + __u32 __padding;
> > > > };
> > > >
> > > > #define DMA_HEAP_IOC_MAGIC 'H'
> > > >
> > >
> >
>
^ permalink raw reply
* Re: [PATCH v3 12/12] mm, swap: merge zeromap into swap table
From: YoungJun Park @ 2026-05-13 18:27 UTC (permalink / raw)
To: kasong
Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Barry Song, Hugh Dickins, Chris Li, Kemeng Shi, Nhat Pham,
Baoquan He, Johannes Weiner, Chengming Zhou, Roman Gushchin,
Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <20260421-swap-table-p4-v3-12-2f23759a76bc@tencent.com>
On Tue, Apr 21, 2026 at 02:16:56PM +0800, Kairui Song via B4 Relay wrote:
Nice! LGTM
Reviewed-by: Youngjun Park <youngjun.park@lge.com>
A few nitpicks follow. take them if you find them useful. :)
> +static inline void __swap_table_clear_zero(struct swap_cluster_info *ci,
> + unsigned int ci_off)
> +{
> +
trailing blank line.
> +#if SWAP_TABLE_HAS_ZEROFLAG
> + unsigned long swp_tb = __swap_table_get(ci, ci_off);
> +
> + VM_WARN_ON(!swp_tb_is_countable(swp_tb));
> + swp_tb &= ~SWP_TB_ZERO_FLAG;
> + __swap_table_set(ci, ci_off, swp_tb);
> +#else
> + lockdep_assert_held(&ci->lock);
> + __clear_bit(ci_off, ci->zero_bitmap);
> +#endif
> +}
...
> +
> table = (struct swap_table *)rcu_access_pointer(ci->table);
> if (!table)
> return;
> @@ -470,6 +475,13 @@ static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
> if (!ci->memcg_table)
> ret = -ENOMEM;
> #endif
> +
> +#if !SWAP_TABLE_HAS_ZEROFLAG
> + ci->zero_bitmap = bitmap_zalloc(SWAPFILE_CLUSTER, gfp);
> + if (!ci->zero_bitmap)
> + ret = -ENOMEM;
> +#endif
> +
memcg_table above uses `if (!ci->memcg_table)` before
kzalloc, but ci->zero_bitmap is assigned unconditionally. Both
are NULL on entry today (swap_cluster_free_table nullifies them),
so either form works but the asymmetry reads oddly. Either
drop the memcg guard (with an entry VM_WARN_ON asserting both
NULL by design), or mirror the guard here.
^ permalink raw reply
* [PATCH v1] landlock: Account all audit data allocations to user space
From: Mickaël Salaün @ 2026-05-13 18:03 UTC (permalink / raw)
To: Günther Noack
Cc: Mickaël Salaün, linux-security-module, cgroups,
linux-mm, Paul Moore, stable
Mark the kzalloc_flex() of struct landlock_details with
GFP_KERNEL_ACCOUNT so the allocation is charged to the calling task,
like the other Landlock per-domain allocations which have used
GFP_KERNEL_ACCOUNT forever.
Every property of landlock_details is caller-attributable: allocated by
landlock_restrict_self(2), owned by the caller's landlock_hierarchy,
contents are the caller's pid, uid, comm, and exe_path, lifetime bounded
by the caller's domain. While the caller may not know nor control the
size of this allocation (i.e. exe_path), this data should still be
accounted for it.
The deciding factor is whether userspace can trigger the allocation, not
whether the size of the data is known nor controlled by the caller.
This aligns with the kmemcg accounting policy established by commit
5d097056c9a0 ("kmemcg: account certain kmem allocations to memcg").
No new failure modes: the hierarchy and ruleset are allocated before
details and are already accounted, so landlock_restrict_self(2) already
returns -ENOMEM under memcg pressure. This change widens that existing
failure window slightly; it does not introduce a new error code.
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Cc: stable@vger.kernel.org
Fixes: 1d636984e088 ("landlock: Add AUDIT_LANDLOCK_DOMAIN and log domain status")
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
security/landlock/domain.c | 9 +++++----
security/landlock/domain.h | 5 +----
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 06b6bd845060..5dd06f7c2312 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -90,11 +90,12 @@ static struct landlock_details *get_current_details(void)
return ERR_CAST(buffer);
/*
- * Create the new details according to the path's length. Do not
- * allocate with GFP_KERNEL_ACCOUNT because it is independent from the
- * caller.
+ * Create the new details according to the path's length. Account
+ * to the calling task's memcg, like the other Landlock per-domain
+ * allocations, even if it may not control the related size.
*/
- details = kzalloc_flex(*details, exe_path, path_size);
+ details =
+ kzalloc_flex(*details, exe_path, path_size, GFP_KERNEL_ACCOUNT);
if (!details)
return ERR_PTR(-ENOMEM);
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index a9d57db0120d..35cac8f6daee 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -33,10 +33,7 @@ enum landlock_log_status {
* Rarely accessed, mainly when logging the first domain's denial.
*
* The contained pointers are initialized at the domain creation time and never
- * changed again. Contrary to most other Landlock object types, this one is
- * not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the
- * caller's control (e.g. unknown exe_path) and the data is not explicitly
- * requested nor used by tasks.
+ * changed again.
*/
struct landlock_details {
/**
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v3 12/12] mm, swap: merge zeromap into swap table
From: Kairui Song @ 2026-05-13 17:47 UTC (permalink / raw)
To: Chris Li
Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Barry Song, Hugh Dickins, Kemeng Shi, Nhat Pham, Baoquan He,
Johannes Weiner, Youngjun Park, Chengming Zhou, Roman Gushchin,
Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <CACePvbUSqkw+5MFFfvwWe9RUs4xUuAzzEkBYOcQ4eB6e7kNonQ@mail.gmail.com>
On Tue, May 12, 2026 at 1:04 AM Chris Li <chrisl@kernel.org> wrote:
>
> On Tue, Apr 21, 2026 at 8:16 AM Kairui Song via B4 Relay
> <devnull+kasong.tencent.com@kernel.org> wrote:
> >
> > From: Kairui Song <kasong@tencent.com>
> >
> > By allocating one additional bit in the swap table entry's flags field
> > alongside the count, we can store the zeromap inline
> >
> > For certain 32-bit archs, there might not be enough bits in the swap
> > table to contain both PFN and flags. Therefore, conditionally let each
> > cluster have a zeromap field at build time, and use that instead of the
> > swap table for these archs. A few macros were moved to different headers
> > for build time struct definition.
>
> It might be worthwhile to mention the user-visible impact. For 64 bit
> systems. The zeromap will store in the swap table, avoiding zeromap
> allocation. It reduces the allocated memory. That is the happy path.
> For certain 32-bit architectures, if the swapfile cluster is not fully
> used, it will use less memory for zeromap. The empty cluster does not
> allocate a zeromap. We still save memory. In the worst case, all
> cluster are fully populated. We will use memory similar to the
> previous zeromap implementation.
Will add this to commit message.
> > +/*
> > + * Return the count of contiguous swap entries that share the same
> > + * zeromap status as the starting entry. If is_zerop is not NULL,
> > + * it will return the zeromap status of the starting entry.
> > + *
> > + * Context: Caller must ensure the cluster containing the entries
> > + * that will be checked won't be freed.
> > + */
> > +static int swap_zeromap_batch(swp_entry_t entry, int max_nr,
> > + bool *is_zerop)
> > +{
> > + bool is_zero;
> > + struct swap_cluster_info *ci = __swap_entry_to_cluster(entry);
> > + unsigned int ci_start = swp_cluster_offset(entry), ci_off, ci_end;
> > +
> > + ci_off = ci_start;
> > + ci_end = ci_off + max_nr;
>
> Should we check ci_end less than the cluster's end and complain if not?
Currently, we only call this function for folio->swap with
folio_nr_pages as the max_nr, so this scenario should never occur, but
a sanity check is always good to have. It might be even better to
convert this whole function to work on a folio basis later, since a
locked swap cache folio has much better restraint on its swap entries.
This can be done later as a clean up.
>
> It seems using a for loop can be simpler. The loop index serves as a
> counter as well.
> Totally untested code:
>
> int i;
> rcu_read_lock();
> is_zero = __swap_table_test_zero(ci, ci_start);
> for (i =1; i < max_nr ; i++)
> if (is_zero != __swap_table_test_zero(ci, ci_start + i))
> break;
> rcu_read_unlock();
> if (is_zerop)
> *is_zerop = is_zero;
> return i;
>
Thanks! Looks good, it's more compact indeed.
^ permalink raw reply
* Re: [PATCH v3 04/12] mm, swap: add support for stable large allocation in swap cache directly
From: YoungJun Park @ 2026-05-13 17:26 UTC (permalink / raw)
To: kasong
Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Barry Song, Hugh Dickins, Chris Li, Kemeng Shi, Nhat Pham,
Baoquan He, Johannes Weiner, Chengming Zhou, Roman Gushchin,
Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <20260421-swap-table-p4-v3-4-2f23759a76bc@tencent.com>
On Tue, Apr 21, 2026 at 02:16:48PM +0800, Kairui Song via B4 Relay wrote:
...
> static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
> struct mempolicy *mpol, pgoff_t ilx,
> struct swap_iocb **plug, bool readahead)
> {
> - struct swap_info_struct *si = __swap_entry_to_info(entry);
> struct folio *folio;
>
> /* Check the swap cache again for readahead path. */
> @@ -594,16 +700,12 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
> if (folio)
> return folio;
> - /* Skip allocation for unused and bad swap slot for readahead. */
> - if (!swap_entry_swapped(si, entry))
> - return NULL;
> -
Hello Kairui
With the swap_entry_swapped() check gone, the swap_cache_get_folio()
above the do-while is now just a duplicate of the loop's first
iteration. Might as well drop it (and the now-stale "again for readahead
path" comment) here.
Best regrads
Youngjun Park
> do {
> folio = swap_cache_get_folio(entry);
> if (folio)
> return folio;
>
> - folio = swap_cache_alloc_folio(entry, gfp, mpol, ilx);
> + folio = swap_cache_alloc_folio(entry, gfp, 0, NULL, mpol, ilx);
> } while (IS_ERR(folio) && PTR_ERR(folio) == -EEXIST);
^ permalink raw reply
* Re: [PATCH RFC 2/5] dma-heap: charge dma-buf memory via explicit memcg
From: T.J. Mercier @ 2026-05-13 16:39 UTC (permalink / raw)
To: Albert Esteve
Cc: Christian König, Tejun Heo, Johannes Weiner,
Michal Koutný, Jonathan Corbet, Shuah Khan, Sumit Semwal,
Michal Hocko, Roman Gushchin, Shakeel Butt, Muchun Song,
Andrew Morton, Benjamin Gaignard, Brian Starkey, John Stultz,
Christian Brauner, Paul Moore, James Morris, Serge E. Hallyn,
Stephen Smalley, Ondrej Mosnacek, Shuah Khan, cgroups, linux-doc,
linux-kernel, linux-media, dri-devel, linaro-mm-sig, linux-mm,
linux-security-module, selinux, linux-kselftest, mripard,
echanude
In-Reply-To: <CADSE00KZMJFYJ92XZa=r9EeJJRGT=SNChwOW-_jTznc7F79xGw@mail.gmail.com>
On Wed, May 13, 2026 at 5:41 AM Albert Esteve <aesteve@redhat.com> wrote:
>
> On Tue, May 12, 2026 at 12:14 PM Christian König
> <christian.koenig@amd.com> wrote:
> >
> > On 5/12/26 11:10, Albert Esteve wrote:
> > > 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_pid_fd to struct dma_heap_allocation_data. When set to
> > > a valid pidfd, DMA_HEAP_IOCTL_ALLOC resolves the target task's
> > > memcg and charges the buffer there via mem_cgroup_charge_dmabuf()
> > > inside dma_heap_buffer_alloc(). Without charge_pid_fd, and with
> > > the mem_accounting module parameter enabled, the buffer is charged
> > > to the allocator's own cgroup.
> > >
> > > Additionally, commit 3c227be90659 ("dma-buf: system_heap: account for
> > > system heap allocation in memcg") adds __GFP_ACCOUNT to system-heap
> > > page allocations. Keeping __GFP_ACCOUNT would charge the same pages
> > > twice (once to kmem, once to MEMCG_DMABUF), thus remove it and route
> > > all accounting through a single MEMCG_DMABUF path.
> > >
> > > 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_pid_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_pid_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.
> >
> > Well that doesn't looks soo bad, it at least seems to tackle the problem at hand for Android and some of other embedded use cases.
> >
> > I'm just not sure if this is future prove and will work for all use cases, e.g. cloud gaming, native context for automotive etc...
> >
> > Essentially the problem boils down to two limitations:
> > 1) a piece of memory can only be charged to one cgroup, the framework doesn't has a concept of charging shared memory to multiple groups
> > 2) when memory references in the form of file descriptors are passed between applications we have no way of changing the accounting to a different cgroup
> >
> > The passing of the memory reference already has a well defined uAPI and if we could solve those two limitations we not only solve the problem without introducing new uAPI (with potential new security risks) but also solve it for all other use cases which uses file descriptors as well as. E.g. memfd, accel and GPU drivers etc...
>
> Honestly, adding a hook to fd-passing uAPI to manage charge transfers
> sounds like a promising solution requiring no uAPI changes. However,
> it still does not cover all paths, e.g., dup() or fork(). And shared
> memory sounds like a hard one to tackle, where deciding the best
> policy is more a per-usecase thing and would probably require
> userspace configuration.
I'm curious if anyone knows of a use case where FDs aren't involved at
all? It's possible to fork() or clone() with only a dmabuf mapping and
no FD. That sounds strange, and I'm not sure there's a real usecase
for transferring ownership with that approach, but figured I'd at
least pose the question.
> All in all, charge_pid_fd covers a
> well-defined and immediately practical subset. The UAPI cost is small
> and the mechanism is explicit about what it does and doesn't solve. A
> general solution, if it ever converges, would likely supersede
> charge_pid_fd for most cases, which is a fine outcome if it solves the
> problem more completely.
>
> Either way, if you have a specific approach in mind for solving any of
> the above limitations, I'd be happy to look into it further.
>
> BR,
> Albert.
>
> >
> > On the other hand it is really nice to finally see this tackled for at least DMA-buf heaps. On the GPU side I have seen just another try of a driver doing some kind of special driver specific accounting to solve this just a few weeks ago. And to be honest such single driver island approach have the tendency to break more often that they are working correctly.
> >
> > Regards,
> > Christian.
> >
> > >
> > > Signed-off-by: Albert Esteve <aesteve@redhat.com>
> > > ---
> > > Documentation/admin-guide/cgroup-v2.rst | 5 ++--
> > > drivers/dma-buf/dma-buf.c | 16 ++++---------
> > > drivers/dma-buf/dma-heap.c | 42 ++++++++++++++++++++++++++++++---
> > > drivers/dma-buf/heaps/system_heap.c | 2 --
> > > include/uapi/linux/dma-heap.h | 6 +++++
> > > 5 files changed, 53 insertions(+), 18 deletions(-)
> > >
> > > diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> > > index 8bdbc2e866430..824d269531eb1 100644
> > > --- a/Documentation/admin-guide/cgroup-v2.rst
> > > +++ b/Documentation/admin-guide/cgroup-v2.rst
> > > @@ -1636,8 +1636,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-buf.c b/drivers/dma-buf/dma-buf.c
> > > index ce02377f48908..23fb758b78297 100644
> > > --- a/drivers/dma-buf/dma-buf.c
> > > +++ b/drivers/dma-buf/dma-buf.c
> > > @@ -181,8 +181,11 @@ static void dma_buf_release(struct dentry *dentry)
> > > */
> > > BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active);
> > >
> > > - mem_cgroup_uncharge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > > - mem_cgroup_put(dmabuf->memcg);
> > > + if (dmabuf->memcg) {
> > > + mem_cgroup_uncharge_dmabuf(dmabuf->memcg,
> > > + PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > > + mem_cgroup_put(dmabuf->memcg);
> > > + }
> > >
> > > dmabuf->ops->release(dmabuf);
> > >
> > > @@ -764,13 +767,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> > > dmabuf->resv = resv;
> > > }
> > >
> > > - dmabuf->memcg = get_mem_cgroup_from_mm(current->mm);
> > > - if (!mem_cgroup_charge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE,
> > > - GFP_KERNEL)) {
> > > - ret = -ENOMEM;
> > > - goto err_memcg;
> > > - }
> > > -
> > > file->private_data = dmabuf;
> > > file->f_path.dentry->d_fsdata = dmabuf;
> > > dmabuf->file = file;
> > > @@ -781,8 +777,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> > >
> > > return dmabuf;
> > >
> > > -err_memcg:
> > > - mem_cgroup_put(dmabuf->memcg);
> > > err_file:
> > > fput(file);
> > > err_module:
> > > diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c
> > > index ac5f8685a6494..ff6e259afcdc0 100644
> > > --- a/drivers/dma-buf/dma-heap.c
> > > +++ b/drivers/dma-buf/dma-heap.c
> > > @@ -7,13 +7,17 @@
> > > */
> > >
> > > #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/nospec.h>
> > > +#include <linux/pidfd.h>
> > > #include <linux/syscalls.h>
> > > #include <linux/uaccess.h>
> > > #include <linux/xarray.h>
> > > @@ -55,10 +59,12 @@ MODULE_PARM_DESC(mem_accounting,
> > > "Enable cgroup-based memory accounting for dma-buf heap allocations (default=false).");
> > >
> > > static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > > - u32 fd_flags,
> > > - u64 heap_flags)
> > > + u32 fd_flags, u64 heap_flags,
> > > + struct mem_cgroup *charge_to)
> > > {
> > > struct dma_buf *dmabuf;
> > > + unsigned int nr_pages;
> > > + struct mem_cgroup *memcg = charge_to;
> > > int fd;
> > >
> > > /*
> > > @@ -73,6 +79,22 @@ static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > > if (IS_ERR(dmabuf))
> > > return PTR_ERR(dmabuf);
> > >
> > > + nr_pages = len / PAGE_SIZE;
> > > +
> > > + if (memcg)
> > > + css_get(&memcg->css);
> > > + else if (mem_accounting)
> > > + memcg = get_mem_cgroup_from_mm(current->mm);
> > > +
> > > + if (memcg) {
> > > + if (!mem_cgroup_charge_dmabuf(memcg, nr_pages, GFP_KERNEL)) {
> > > + mem_cgroup_put(memcg);
> > > + dma_buf_put(dmabuf);
> > > + return -ENOMEM;
> > > + }
> > > + dmabuf->memcg = memcg;
> > > + }
> > > +
> > > fd = dma_buf_fd(dmabuf, fd_flags);
> > > if (fd < 0) {
> > > dma_buf_put(dmabuf);
> > > @@ -102,6 +124,9 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > > {
> > > struct dma_heap_allocation_data *heap_allocation = data;
> > > struct dma_heap *heap = file->private_data;
> > > + struct mem_cgroup *memcg = NULL;
> > > + struct task_struct *task;
> > > + unsigned int pidfd_flags;
> > > int fd;
> > >
> > > if (heap_allocation->fd)
> > > @@ -113,9 +138,20 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > > if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS)
> > > return -EINVAL;
> > >
> > > + if (heap_allocation->charge_pid_fd) {
> > > + task = pidfd_get_task(heap_allocation->charge_pid_fd, &pidfd_flags);
> > > + if (IS_ERR(task))
> > > + return PTR_ERR(task);
> > > +
> > > + memcg = get_mem_cgroup_from_mm(task->mm);
> > > + put_task_struct(task);
> > > + }
> > > +
> > > fd = dma_heap_buffer_alloc(heap, heap_allocation->len,
> > > heap_allocation->fd_flags,
> > > - heap_allocation->heap_flags);
> > > + heap_allocation->heap_flags,
> > > + memcg);
> > > + mem_cgroup_put(memcg);
> > > if (fd < 0)
> > > return fd;
> > >
> > > diff --git a/drivers/dma-buf/heaps/system_heap.c b/drivers/dma-buf/heaps/system_heap.c
> > > index 03c2b87cb1112..95d7688167b93 100644
> > > --- a/drivers/dma-buf/heaps/system_heap.c
> > > +++ b/drivers/dma-buf/heaps/system_heap.c
> > > @@ -385,8 +385,6 @@ static struct page *alloc_largest_available(unsigned long size,
> > > if (max_order < orders[i])
> > > continue;
> > > flags = order_flags[i];
> > > - if (mem_accounting)
> > > - flags |= __GFP_ACCOUNT;
> > > page = alloc_pages(flags, orders[i]);
> > > if (!page)
> > > continue;
> > > diff --git a/include/uapi/linux/dma-heap.h b/include/uapi/linux/dma-heap.h
> > > index a4cf716a49fa6..e02b0f8cbc6a1 100644
> > > --- a/include/uapi/linux/dma-heap.h
> > > +++ b/include/uapi/linux/dma-heap.h
> > > @@ -29,6 +29,10 @@
> > > * handle to the allocated dma-buf
> > > * @fd_flags: file descriptor flags used when allocating
> > > * @heap_flags: flags passed to heap
> > > + * @charge_pid_fd: optional pidfd of the process whose cgroup should be
> > > + * charged for this allocation; 0 means charge the calling
> > > + * process's cgroup
> > > + * @__padding: reserved, must be zero
> > > *
> > > * Provided by userspace as an argument to the ioctl
> > > */
> > > @@ -37,6 +41,8 @@ struct dma_heap_allocation_data {
> > > __u32 fd;
> > > __u32 fd_flags;
> > > __u64 heap_flags;
> > > + __u32 charge_pid_fd;
> > > + __u32 __padding;
> > > };
> > >
> > > #define DMA_HEAP_IOC_MAGIC 'H'
> > >
> >
>
^ permalink raw reply
* Re: [PATCH RFC 2/5] dma-heap: charge dma-buf memory via explicit memcg
From: T.J. Mercier @ 2026-05-13 16:35 UTC (permalink / raw)
To: Albert Esteve
Cc: Christian König, Tejun Heo, Johannes Weiner,
Michal Koutný, Jonathan Corbet, Shuah Khan, Sumit Semwal,
Michal Hocko, Roman Gushchin, Shakeel Butt, Muchun Song,
Andrew Morton, Benjamin Gaignard, Brian Starkey, John Stultz,
Christian Brauner, Paul Moore, James Morris, Serge E. Hallyn,
Stephen Smalley, Ondrej Mosnacek, Shuah Khan, cgroups, linux-doc,
linux-kernel, linux-media, dri-devel, linaro-mm-sig, linux-mm,
linux-security-module, selinux, linux-kselftest, mripard,
echanude
In-Reply-To: <CADSE00Jq_uvNgvxgPze0mEdUd+hF4-DPZkHy0KroWHZzygf4WA@mail.gmail.com>
On Wed, May 13, 2026 at 4:39 AM Albert Esteve <aesteve@redhat.com> wrote:
>
> On Tue, May 12, 2026 at 8:53 PM T.J. Mercier <tjmercier@google.com> wrote:
> >
> > On Tue, May 12, 2026 at 3:14 AM Christian König
> > <christian.koenig@amd.com> wrote:
> > >
> > > On 5/12/26 11:10, Albert Esteve wrote:
> > > > 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_pid_fd to struct dma_heap_allocation_data. When set to
> > > > a valid pidfd, DMA_HEAP_IOCTL_ALLOC resolves the target task's
> > > > memcg and charges the buffer there via mem_cgroup_charge_dmabuf()
> > > > inside dma_heap_buffer_alloc(). Without charge_pid_fd, and with
> > > > the mem_accounting module parameter enabled, the buffer is charged
> > > > to the allocator's own cgroup.
> > > >
> > > > Additionally, commit 3c227be90659 ("dma-buf: system_heap: account for
> > > > system heap allocation in memcg") adds __GFP_ACCOUNT to system-heap
> > > > page allocations. Keeping __GFP_ACCOUNT would charge the same pages
> > > > twice (once to kmem, once to MEMCG_DMABUF), thus remove it and route
> > > > all accounting through a single MEMCG_DMABUF path.
> > > >
> > > > 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_pid_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_pid_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.
> > >
> > > Well that doesn't looks soo bad, it at least seems to tackle the problem at hand for Android and some of other embedded use cases.
> >
> > Yeah I think this might work. I know of 3 cases, and it trivially
> > solves the first two. The third requires some work on our end to
> > extend our userspace interfaces to include the pidfd but it seems
> > doable. I'm checking with our graphics folks.
> >
> > 1) Direct allocation from user (e.g. app -> allocation ioctl on
> > /dev/dma_heap/foo)
> > No changes required to userspace. mem_accounting=1 charges the app.
> >
> > 2) Single hop remote allocation (e.g. app -> AHardwareBuffer_allocate
> > -> gralloc)
> > gralloc has the caller's pid as described in the commit message. Open
> > a pidfd and pass it in the dma_heap_allocation_data.
> >
> > 3) Double hop remote allocation (e.g. app -> dequeueBuffer ->
> > SurfaceFlinger -> gralloc)
> > In this case gralloc knows SurfaceFlinger's pid, but not the app's. So
> > we need to add the app's pidfd to the SurfaceFlinger -> gralloc
> > interface, or transfer the memcg charge from SurfaceFlinger to the app
> > after the allocation.
> > It'd be nice to avoid the charge transfer option entirely, but if we
> > need it that doesn't seem so bad in this case because it's a bulk
> > charge for the entire dmabuf rather than per-page. So the exporter
> > doesn't need to get involved (we wouldn't need a new dma_buf_op) and
> > we wouldn't have to worry about looping and locking for each page.
> >
> > > I'm just not sure if this is future prove and will work for all use cases, e.g. cloud gaming, native context for automotive etc...
> > >
> > > Essentially the problem boils down to two limitations:
> > > 1) a piece of memory can only be charged to one cgroup, the framework doesn't has a concept of charging shared memory to multiple groups
> >
> > Yup, memcg already has this problem with pagecache and shmem.
> >
> > > 2) when memory references in the form of file descriptors are passed between applications we have no way of changing the accounting to a different cgroup
> > >
> > > The passing of the memory reference already has a well defined uAPI and if we could solve those two limitations we not only solve the problem without introducing new uAPI (with potential new security risks) but also solve it for all other use cases which uses file descriptors as well as. E.g. memfd, accel and GPU drivers etc...
> > >
> > > On the other hand it is really nice to finally see this tackled for at least DMA-buf heaps.
> >
> > I have a question about this part. Albert I guess you are interested
> > only in accounting dmabuf-heap allocations, or do you expect to add
> > __GFP_ACCOUNT or mem_cgroup_charge_dmabuf calls to other
> > non-dmabuf-heap exporters?
>
> We're scoping this to dma-buf heaps for now. CMA heaps and the dmem
> controller are on the radar for follow-up/parallel work (there will be
> dragons and will surely need discussion). For DRM and V4L2 the
> long-term intent is migration to heaps, which would make direct
> accounting on those paths unnecessary.
Ah I see. GEM buffers exported to dmabufs are what I had in mind. I
guess this would only leave the odd non-DRM driver with the need to
add their own accounting calls, which I don't expect would be a big
problem.
> udmabufs are already
> memcg-charged, so adding a separate MEMCG_DMABUF would double count.
> Are there any other exporters you had in mind that would benefit from
> this approach?
>
> BR,
> Albert.
>
> >
> > > On the GPU side I have seen just another try of a driver doing some kind of special driver specific accounting to solve this just a few weeks ago. And to be honest such single driver island approach have the tendency to break more often that they are working correctly.
> > >
> > > Regards,
> > > Christian.
> > >
> > > >
> > > > Signed-off-by: Albert Esteve <aesteve@redhat.com>
> > > > ---
> > > > Documentation/admin-guide/cgroup-v2.rst | 5 ++--
> > > > drivers/dma-buf/dma-buf.c | 16 ++++---------
> > > > drivers/dma-buf/dma-heap.c | 42 ++++++++++++++++++++++++++++++---
> > > > drivers/dma-buf/heaps/system_heap.c | 2 --
> > > > include/uapi/linux/dma-heap.h | 6 +++++
> > > > 5 files changed, 53 insertions(+), 18 deletions(-)
> > > >
> > > > diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> > > > index 8bdbc2e866430..824d269531eb1 100644
> > > > --- a/Documentation/admin-guide/cgroup-v2.rst
> > > > +++ b/Documentation/admin-guide/cgroup-v2.rst
> > > > @@ -1636,8 +1636,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-buf.c b/drivers/dma-buf/dma-buf.c
> > > > index ce02377f48908..23fb758b78297 100644
> > > > --- a/drivers/dma-buf/dma-buf.c
> > > > +++ b/drivers/dma-buf/dma-buf.c
> > > > @@ -181,8 +181,11 @@ static void dma_buf_release(struct dentry *dentry)
> > > > */
> > > > BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active);
> > > >
> > > > - mem_cgroup_uncharge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > > > - mem_cgroup_put(dmabuf->memcg);
> > > > + if (dmabuf->memcg) {
> > > > + mem_cgroup_uncharge_dmabuf(dmabuf->memcg,
> > > > + PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > > > + mem_cgroup_put(dmabuf->memcg);
> > > > + }
> > > >
> > > > dmabuf->ops->release(dmabuf);
> > > >
> > > > @@ -764,13 +767,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> > > > dmabuf->resv = resv;
> > > > }
> > > >
> > > > - dmabuf->memcg = get_mem_cgroup_from_mm(current->mm);
> > > > - if (!mem_cgroup_charge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE,
> > > > - GFP_KERNEL)) {
> > > > - ret = -ENOMEM;
> > > > - goto err_memcg;
> > > > - }
> > > > -
> > > > file->private_data = dmabuf;
> > > > file->f_path.dentry->d_fsdata = dmabuf;
> > > > dmabuf->file = file;
> > > > @@ -781,8 +777,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> > > >
> > > > return dmabuf;
> > > >
> > > > -err_memcg:
> > > > - mem_cgroup_put(dmabuf->memcg);
> > > > err_file:
> > > > fput(file);
> > > > err_module:
> > > > diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c
> > > > index ac5f8685a6494..ff6e259afcdc0 100644
> > > > --- a/drivers/dma-buf/dma-heap.c
> > > > +++ b/drivers/dma-buf/dma-heap.c
> > > > @@ -7,13 +7,17 @@
> > > > */
> > > >
> > > > #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/nospec.h>
> > > > +#include <linux/pidfd.h>
> > > > #include <linux/syscalls.h>
> > > > #include <linux/uaccess.h>
> > > > #include <linux/xarray.h>
> > > > @@ -55,10 +59,12 @@ MODULE_PARM_DESC(mem_accounting,
> > > > "Enable cgroup-based memory accounting for dma-buf heap allocations (default=false).");
> > > >
> > > > static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > > > - u32 fd_flags,
> > > > - u64 heap_flags)
> > > > + u32 fd_flags, u64 heap_flags,
> > > > + struct mem_cgroup *charge_to)
> > > > {
> > > > struct dma_buf *dmabuf;
> > > > + unsigned int nr_pages;
> > > > + struct mem_cgroup *memcg = charge_to;
> > > > int fd;
> > > >
> > > > /*
> > > > @@ -73,6 +79,22 @@ static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > > > if (IS_ERR(dmabuf))
> > > > return PTR_ERR(dmabuf);
> > > >
> > > > + nr_pages = len / PAGE_SIZE;
> > > > +
> > > > + if (memcg)
> > > > + css_get(&memcg->css);
> > > > + else if (mem_accounting)
> > > > + memcg = get_mem_cgroup_from_mm(current->mm);
> > > > +
> > > > + if (memcg) {
> > > > + if (!mem_cgroup_charge_dmabuf(memcg, nr_pages, GFP_KERNEL)) {
> > > > + mem_cgroup_put(memcg);
> > > > + dma_buf_put(dmabuf);
> > > > + return -ENOMEM;
> > > > + }
> > > > + dmabuf->memcg = memcg;
> > > > + }
> > > > +
> > > > fd = dma_buf_fd(dmabuf, fd_flags);
> > > > if (fd < 0) {
> > > > dma_buf_put(dmabuf);
> > > > @@ -102,6 +124,9 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > > > {
> > > > struct dma_heap_allocation_data *heap_allocation = data;
> > > > struct dma_heap *heap = file->private_data;
> > > > + struct mem_cgroup *memcg = NULL;
> > > > + struct task_struct *task;
> > > > + unsigned int pidfd_flags;
> > > > int fd;
> > > >
> > > > if (heap_allocation->fd)
> > > > @@ -113,9 +138,20 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > > > if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS)
> > > > return -EINVAL;
> > > >
> > > > + if (heap_allocation->charge_pid_fd) {
> > > > + task = pidfd_get_task(heap_allocation->charge_pid_fd, &pidfd_flags);
> > > > + if (IS_ERR(task))
> > > > + return PTR_ERR(task);
> > > > +
> > > > + memcg = get_mem_cgroup_from_mm(task->mm);
> > > > + put_task_struct(task);
> > > > + }
> > > > +
> > > > fd = dma_heap_buffer_alloc(heap, heap_allocation->len,
> > > > heap_allocation->fd_flags,
> > > > - heap_allocation->heap_flags);
> > > > + heap_allocation->heap_flags,
> > > > + memcg);
> > > > + mem_cgroup_put(memcg);
> > > > if (fd < 0)
> > > > return fd;
> > > >
> > > > diff --git a/drivers/dma-buf/heaps/system_heap.c b/drivers/dma-buf/heaps/system_heap.c
> > > > index 03c2b87cb1112..95d7688167b93 100644
> > > > --- a/drivers/dma-buf/heaps/system_heap.c
> > > > +++ b/drivers/dma-buf/heaps/system_heap.c
> > > > @@ -385,8 +385,6 @@ static struct page *alloc_largest_available(unsigned long size,
> > > > if (max_order < orders[i])
> > > > continue;
> > > > flags = order_flags[i];
> > > > - if (mem_accounting)
> > > > - flags |= __GFP_ACCOUNT;
> > > > page = alloc_pages(flags, orders[i]);
> > > > if (!page)
> > > > continue;
> > > > diff --git a/include/uapi/linux/dma-heap.h b/include/uapi/linux/dma-heap.h
> > > > index a4cf716a49fa6..e02b0f8cbc6a1 100644
> > > > --- a/include/uapi/linux/dma-heap.h
> > > > +++ b/include/uapi/linux/dma-heap.h
> > > > @@ -29,6 +29,10 @@
> > > > * handle to the allocated dma-buf
> > > > * @fd_flags: file descriptor flags used when allocating
> > > > * @heap_flags: flags passed to heap
> > > > + * @charge_pid_fd: optional pidfd of the process whose cgroup should be
> > > > + * charged for this allocation; 0 means charge the calling
> > > > + * process's cgroup
> > > > + * @__padding: reserved, must be zero
> > > > *
> > > > * Provided by userspace as an argument to the ioctl
> > > > */
> > > > @@ -37,6 +41,8 @@ struct dma_heap_allocation_data {
> > > > __u32 fd;
> > > > __u32 fd_flags;
> > > > __u64 heap_flags;
> > > > + __u32 charge_pid_fd;
> > > > + __u32 __padding;
> > > > };
> > > >
> > > > #define DMA_HEAP_IOC_MAGIC 'H'
> > > >
> > >
> >
>
^ permalink raw reply
* Re: [PATCH] cpuset: Fix multi-source deadline task accounting and bandwidth bypass
From: Dietmar Eggemann @ 2026-05-13 16:22 UTC (permalink / raw)
To: Aaron Tomlin, longman, tj, hannes, mkoutny
Cc: chenridong, neelx, cgroups, linux-kernel
In-Reply-To: <20260512010341.101419-1-atomlin@atomlin.com>
On 12.05.26 03:03, Aaron Tomlin wrote:
> During a batch migration where threads in a taskset originate from
> multiple source cpusets (e.g., via cgroup.procs), cpuset_can_attach()
> and cpuset_attach() currently evaluate the source cpuset exactly once
> by caching the first task's oldcs.
>
> This creates two distinct critical flaws for SCHED_DEADLINE tasks:
>
> 1. oldcs->nr_deadline_tasks is decremented solely on the first
> source cpuset. If tasks originated from other cpusets, their
> counts are permanently leaked, and the first cpuset permanently
> underflows.
>
> 2. cpumask_intersects() is evaluated strictly against the first
> task's source cpuset. This allows tasks originating from
> entirely isolated root domains to silently bypass the
> dl_bw_alloc() admission control.
>
> This patch refactors the deadline accounting to evaluate task_cs(task)
> on a per-task basis during the cgroup_taskset_for_each() loops. To
> achieve accurate accounting before the core cgroup migration actually
> executes, the permanent nr_deadline_tasks increments/decrements are
> shifted into cpuset_can_attach(). If the migration aborts, the counts
> are gracefully reverted via an internal rollback loop or the
> cpuset_cancel_attach() callback.
Is there a testcase to provoke this issue in the current code?
I tried to move a process with 6 DL tasks from one cpuset to another by:
echo $PID > /sys/fs/cgroup/B/cgroup.procs
but in this case old_cs is the same for all these tasks.
[ 1991.852034] cgroup_migrate() (7) leader=[dl_batch_cgroup 823] threadgroup=1
[ 1991.852068] cgroup_migrate_execute() tset->nr_tasks=7
[ 1991.852238] cpuset_can_attach() (4) [dl_batch_cgroup 832] nr_migrate_dl_tasks=1 sum_migrate_dl_bw=104857 old_cs=ffff0000c4955200
[ 1991.852246] cpuset_can_attach() (4) [dl_batch_cgroup 833] nr_migrate_dl_tasks=2 sum_migrate_dl_bw=209714 old_cs=ffff0000c4955200
[ 1991.852248] cpuset_can_attach() (4) [dl_batch_cgroup 834] nr_migrate_dl_tasks=3 sum_migrate_dl_bw=314571 old_cs=ffff0000c4955200
[ 1991.852249] cpuset_can_attach() (4) [dl_batch_cgroup 835] nr_migrate_dl_tasks=4 sum_migrate_dl_bw=419428 old_cs=ffff0000c4955200
[ 1991.852249] cpuset_can_attach() (4) [dl_batch_cgroup 836] nr_migrate_dl_tasks=5 sum_migrate_dl_bw=524285 old_cs=ffff0000c4955200
[ 1991.852250] cpuset_can_attach() (4) [dl_batch_cgroup 837] nr_migrate_dl_tasks=6 sum_migrate_dl_bw=629142 old_cs=ffff0000c4955200
[ 1991.852328] cpuset_attach() (5) cs=ffff0000c1e9fc00 oldcs=ffff0000c4955200 cs->nr_deadline_tasks=6 oldcs->nr_deadline_tasks=6 cs->nr_migrate_dl_tasks=6
dl_batch_cgroup 823 823 19 - 0 TS
dl_batch_cgroup 823 832 140 0 - DLN
dl_batch_cgroup 823 833 140 0 - DLN
dl_batch_cgroup 823 834 140 0 - DLN
dl_batch_cgroup 823 835 140 0 - DLN
dl_batch_cgroup 823 836 140 0 - DLN
dl_batch_cgroup 823 837 140 0 - DLN
[...]
^ permalink raw reply
* Re: [PATCH 08/23] arm64: topology: Use RCU to protect access to HK_TYPE_TICK cpumask
From: Frederic Weisbecker @ 2026-05-13 16:19 UTC (permalink / raw)
To: Waiman Long
Cc: Tejun Heo, Johannes Weiner, Michal Koutný, Jonathan Corbet,
Shuah Khan, Catalin Marinas, Will Deacon, K. Y. Srinivasan,
Haiyang Zhang, Wei Liu, Dexuan Cui, Long Li, Guenter Roeck,
Paul E. McKenney, Neeraj Upadhyay, Joel Fernandes, Josh Triplett,
Boqun Feng, Uladzislau Rezki, Steven Rostedt, Mathieu Desnoyers,
Lai Jiangshan, Zqiang, Anna-Maria Behnsen, Ingo Molnar,
Thomas Gleixner, Chen Ridong, Peter Zijlstra, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, cgroups,
linux-doc, linux-kernel, linux-arm-kernel, linux-hyperv,
linux-hwmon, rcu, netdev, linux-kselftest, Costa Shulyupin,
Qiliang Yuan
In-Reply-To: <20260421030351.281436-9-longman@redhat.com>
Le Mon, Apr 20, 2026 at 11:03:36PM -0400, Waiman Long a écrit :
> As the HK_TYPE_TICK cpumask is going to be changeable at run time, we
> need to use RCU to protect access to the cpumask to prevent it from
> going away in the middle of the operation.
>
> Signed-off-by: Waiman Long <longman@redhat.com>
> ---
> arch/arm64/kernel/topology.c | 17 ++++++++++++++---
> 1 file changed, 14 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c
> index b32f13358fbb..48f150801689 100644
> --- a/arch/arm64/kernel/topology.c
> +++ b/arch/arm64/kernel/topology.c
> @@ -173,6 +173,7 @@ void arch_cpu_idle_enter(void)
> if (!amu_fie_cpu_supported(cpu))
> return;
>
> + guard(rcu)();
> /* Kick in AMU update but only if one has not happened already */
> if (housekeeping_cpu(cpu, HK_TYPE_TICK) &&
> time_is_before_jiffies(per_cpu(cpu_amu_samples.last_scale_update,
> cpu)))
This is called with IRQs disabled in the current CPU that is online so it's
already guaranteed to be stable.
> @@ -187,11 +188,16 @@ int arch_freq_get_on_cpu(int cpu)
> unsigned int start_cpu = cpu;
> unsigned long last_update;
> unsigned int freq = 0;
> + bool hk_cpu;
> u64 scale;
>
> if (!amu_fie_cpu_supported(cpu) || !arch_scale_freq_ref(cpu))
> return -EOPNOTSUPP;
>
> + scoped_guard(rcu) {
> + hk_cpu = housekeeping_cpu(cpu, HK_TYPE_TICK);
> + }
> +
> while (1) {
>
> amu_sample = per_cpu_ptr(&cpu_amu_samples, cpu);
> @@ -204,16 +210,21 @@ int arch_freq_get_on_cpu(int cpu)
> * (and thus freq scale), if available, for given policy: this boils
> * down to identifying an active cpu within the same freq domain, if any.
> */
> - if (!housekeeping_cpu(cpu, HK_TYPE_TICK) ||
> + if (!hk_cpu ||
> time_is_before_jiffies(last_update + msecs_to_jiffies(AMU_SAMPLE_EXP_MS))) {
> struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
> + bool hk_intersects;
> int ref_cpu;
>
> if (!policy)
> return -EINVAL;
>
> - if (!cpumask_intersects(policy->related_cpus,
> - housekeeping_cpumask(HK_TYPE_TICK))) {
> + scoped_guard(rcu) {
> + hk_intersects = cpumask_intersects(policy->related_cpus,
> + housekeeping_cpumask(HK_TYPE_TICK));
> + }
> +
> + if (!hk_intersects) {
> cpufreq_cpu_put(policy);
> return -EOPNOTSUPP;
> }
Ok so this is racy but it's fine because:
This function is only used by cpufreq with either cpufreq_policy_write or
cpufreq_policy_read held (that is, struct cpufreq_policy::rwsem).
And that rwsem is write held on cpufreq_online() -> cpufreq_policy_online() and
also offline to guarantee the policy->cpus and policy->cpu stability.
Therefore housekeeping_cpumask() should only deal with stable online CPUs here. So
even if the housekeeping mask can be changed concurrently, those CPUs can't
appear or disappear from it.
Would be worth adding a comment about that.
--
Frederic Weisbecker
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2] cgroup/cpuset: Return only actually allocated CPUs during partition invalidation
From: Waiman Long @ 2026-05-13 15:02 UTC (permalink / raw)
To: Sun Shaojie, Chen Ridong, Tejun Heo, Johannes Weiner,
Michal Koutný
Cc: cgroups, linux-kernel
In-Reply-To: <20260513103738.442779-1-sunshaojie@kylinos.cn>
On 5/13/26 6:37 AM, Sun Shaojie wrote:
> From: sunshaojie <sunshaojie@kylinos.cn>
>
> In update_parent_effective_cpumask() with partcmd_invalidate, the CPUs
> to return to the parent are computed as:
>
> adding = cpumask_and(tmp->addmask, xcpus, parent->effective_xcpus);
>
> where xcpus = user_xcpus(cs) which returns cs->exclusive_cpus (if set)
> or cs->cpus_allowed. When exclusive_cpus is not set, user_xcpus(cs) can
> contain CPUs that were never actually granted to the partition due to
> sibling exclusion in compute_excpus(). Consequently, the invalidation
> may return CPUs to the parent that remain in use by sibling partitions,
> causing overlapping effective_cpus and triggering the
> WARN_ON_ONCE(1) in generate_sched_domains().
>
> Use cs->effective_xcpus instead, which reflects the CPUs actually
> granted to this partition.
>
> Reproducer (on a 4-CPU machine):
>
> cd /sys/fs/cgroup
> mkdir a1 b1
>
> # a1 becomes partition root with CPUs 0-1
> echo "0-1" > a1/cpuset.cpus
> echo "root" > a1/cpuset.cpus.partition
>
> # b1 becomes partition root with CPUs 1-2, but sibling exclusion
> # reduces its effective_xcpus to CPU 2 only
> echo "1-2" > b1/cpuset.cpus
> echo "root" > b1/cpuset.cpus.partition
>
> # b1 changes cpus_allowed to 0-1 -> partition invalidation
> echo "0-1" > b1/cpuset.cpus
>
> # Expected: CPUs 2-3 (only CPU 2 returned from b1)
> # Actual: CPUs 1-3 (CPU 0-1 returned, overlapping with a1)
> cat cpuset.cpus.effective
>
> dmesg will also show a WARNING from generate_sched_domains() reporting
> overlapping partition root effective_cpus.
>
> Fixes: 2a3602030d80 ("cgroup/cpuset: Don't invalidate sibling partitions on cpuset.cpus conflict")
> Signed-off-by: sunshaojie <sunshaojie@kylinos.cn>
> Test-by: Chen Ridong <chenridong@huaweicloud.com>
> Reviewed-by: Chen Ridong <chenridong@huaweicloud.com>
>
> ---
> Changes in v2:
> - Updated Fixes tag per review by Chen Ridong
> ---
> kernel/cgroup/cpuset.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
> index 1335e437098e..2311470ef077 100644
> --- a/kernel/cgroup/cpuset.c
> +++ b/kernel/cgroup/cpuset.c
> @@ -1715,7 +1715,8 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
> */
> if (is_partition_valid(parent))
> adding = cpumask_and(tmp->addmask,
> - xcpus, parent->effective_xcpus);
> + cs->effective_xcpus,
> + parent->effective_xcpus);
> if (old_prs > 0)
> new_prs = -old_prs;
>
Thanks for catching this bug.
Reviewed-by: Waiman Long <longman@redhat.com>
^ permalink raw reply
* Re: [linus:master] [mm] 01b9da291c: stress-ng.switch.ops_per_sec 67.7% regression
From: Shakeel Butt @ 2026-05-13 14:27 UTC (permalink / raw)
To: Qi Zheng
Cc: kernel test robot, oe-lkp, lkp, linux-kernel, Andrew Morton,
David Carlier, Allen Pais, Axel Rasmussen, Baoquan He,
Chengming Zhou, Chen Ridong, David Hildenbrand, Hamza Mahfooz,
Harry Yoo, Hugh Dickins, Imran Khan, Johannes Weiner,
Kamalesh Babulal, Lance Yang, Liam Howlett, Lorenzo Stoakes,
Michal Hocko, Michal Koutný, Mike Rapoport, Muchun Song,
Muchun Song, Nhat Pham, Roman Gushchin, Suren Baghdasaryan,
Usama Arif, Vlastimil Babka, Wei Xu, Yosry Ahmed, Yuanchu Xie,
Zi Yan, Usama Arif, cgroups, linux-mm
In-Reply-To: <agSAT4ldp3dzKWPl@linux.dev>
On Wed, May 13, 2026 at 06:49:45AM -0700, Shakeel Butt wrote:
> On Wed, May 13, 2026 at 10:10:34AM +0800, Qi Zheng wrote:
> >
> >
> > On 5/13/26 12:03 AM, Shakeel Butt wrote:
> > > On Tue, May 12, 2026 at 08:56:52PM +0800, kernel test robot wrote:
> > > >
> > > >
> > > > Hello,
> > > >
> > > > kernel test robot noticed a 67.7% regression of stress-ng.switch.ops_per_sec on:
> > > >
> > > >
> > > > commit: 01b9da291c4969354807b52956f4aae1f41b4924 ("mm: memcontrol: convert objcg to be per-memcg per-node type")
> > > > https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git master
> > >
> > > This is most probably due to shuffling of struct mem_cgroup and struct
> > > mem_cgroup_per_node members.
> >
> > Another possibility is that after objcg was split into per-node, the
> > slab accounting fast path is still designed assuming only one current
> > objcg per CPU:
> >
> > struct obj_stock_pcp {
> > struct obj_cgroup *cached_objcg;
> > };
> >
> > So it's may cause the following thrashing:
> >
> > CPU stock cached = memcg/node0 objcg
> > free object tagged = memcg/node1 objcg
> > => __refill_obj_stock --> objcg mismatch
> > => drain_obj_stock()
> > => cache switches to node1 objcg
> >
> > next local allocation tagged = node0 objcg
> > => mismatch again
> > => drain_obj_stock()
>
> Actually I think this is the issue, we have ping pong threads running on
> different nodes where though theu are in same cgroup but their current->obcg is
> for local node and thus this ping pong is thrashing the per-cpu objcg stock.
>
> The easier fix would be to compare objcg->memcg instead of just objcg during
> draining and caching. In addition we can add support for multiple objcg per-cpu
> stock caching.
Something like the following:
From d756abe831a905d6fe32bad9a984fc619dafb7e0 Mon Sep 17 00:00:00 2001
From: Shakeel Butt <shakeel.butt@linux.dev>
Date: Wed, 13 May 2026 07:24:55 -0700
Subject: [PATCH] mm/memcontrol: skip obj_stock drain when refilled objcg
shares memcg
Signed-off-by: Shakeel Butt <shakeel.butt@linux.dev>
---
mm/memcontrol.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index d978e18b9b2d..01ed7a8e18ac 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -3318,6 +3318,7 @@ static void __refill_obj_stock(struct obj_cgroup *objcg,
unsigned int nr_bytes,
bool allow_uncharge)
{
+ struct obj_cgroup *cached;
unsigned int nr_pages = 0;
if (!stock) {
@@ -3327,7 +3328,18 @@ static void __refill_obj_stock(struct obj_cgroup *objcg,
goto out;
}
- if (READ_ONCE(stock->cached_objcg) != objcg) { /* reset if necessary */
+ cached = READ_ONCE(stock->cached_objcg);
+ if (cached != objcg &&
+ (!cached || obj_cgroup_memcg(cached) != obj_cgroup_memcg(objcg))) {
drain_obj_stock(stock);
obj_cgroup_get(objcg);
stock->nr_bytes = atomic_read(&objcg->nr_charged_bytes)
--
2.53.0-Meta
^ permalink raw reply related
* Re: [linus:master] [mm] 01b9da291c: stress-ng.switch.ops_per_sec 67.7% regression
From: Shakeel Butt @ 2026-05-13 13:49 UTC (permalink / raw)
To: Qi Zheng
Cc: kernel test robot, oe-lkp, lkp, linux-kernel, Andrew Morton,
David Carlier, Allen Pais, Axel Rasmussen, Baoquan He,
Chengming Zhou, Chen Ridong, David Hildenbrand, Hamza Mahfooz,
Harry Yoo, Hugh Dickins, Imran Khan, Johannes Weiner,
Kamalesh Babulal, Lance Yang, Liam Howlett, Lorenzo Stoakes,
Michal Hocko, Michal Koutný, Mike Rapoport, Muchun Song,
Muchun Song, Nhat Pham, Roman Gushchin, Suren Baghdasaryan,
Usama Arif, Vlastimil Babka, Wei Xu, Yosry Ahmed, Yuanchu Xie,
Zi Yan, Usama Arif, cgroups, linux-mm
In-Reply-To: <0e1b8994-944d-4dda-8966-3cd43661796d@linux.dev>
On Wed, May 13, 2026 at 10:10:34AM +0800, Qi Zheng wrote:
>
>
> On 5/13/26 12:03 AM, Shakeel Butt wrote:
> > On Tue, May 12, 2026 at 08:56:52PM +0800, kernel test robot wrote:
> > >
> > >
> > > Hello,
> > >
> > > kernel test robot noticed a 67.7% regression of stress-ng.switch.ops_per_sec on:
> > >
> > >
> > > commit: 01b9da291c4969354807b52956f4aae1f41b4924 ("mm: memcontrol: convert objcg to be per-memcg per-node type")
> > > https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git master
> >
> > This is most probably due to shuffling of struct mem_cgroup and struct
> > mem_cgroup_per_node members.
>
> Another possibility is that after objcg was split into per-node, the
> slab accounting fast path is still designed assuming only one current
> objcg per CPU:
>
> struct obj_stock_pcp {
> struct obj_cgroup *cached_objcg;
> };
>
> So it's may cause the following thrashing:
>
> CPU stock cached = memcg/node0 objcg
> free object tagged = memcg/node1 objcg
> => __refill_obj_stock --> objcg mismatch
> => drain_obj_stock()
> => cache switches to node1 objcg
>
> next local allocation tagged = node0 objcg
> => mismatch again
> => drain_obj_stock()
Actually I think this is the issue, we have ping pong threads running on
different nodes where though theu are in same cgroup but their current->obcg is
for local node and thus this ping pong is thrashing the per-cpu objcg stock.
The easier fix would be to compare objcg->memcg instead of just objcg during
draining and caching. In addition we can add support for multiple objcg per-cpu
stock caching.
^ permalink raw reply
* Re: [PATCH 04/23] tick/nohz: Allow runtime changes in full dynticks CPUs
From: Frederic Weisbecker @ 2026-05-13 13:04 UTC (permalink / raw)
To: Thomas Gleixner
Cc: Waiman Long, Tejun Heo, Johannes Weiner, Michal Koutný,
Jonathan Corbet, Shuah Khan, Catalin Marinas, Will Deacon,
K. Y. Srinivasan, Haiyang Zhang, Wei Liu, Dexuan Cui, Long Li,
Guenter Roeck, Paul E. McKenney, Neeraj Upadhyay, Joel Fernandes,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Anna-Maria Behnsen,
Ingo Molnar, Chen Ridong, Peter Zijlstra, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, cgroups,
linux-doc, linux-kernel, linux-arm-kernel, linux-hyperv,
linux-hwmon, rcu, netdev, linux-kselftest, Costa Shulyupin,
Qiliang Yuan
In-Reply-To: <87340od7ev.ffs@tglx>
Le Tue, Apr 21, 2026 at 10:50:00AM +0200, Thomas Gleixner a écrit :
> On Mon, Apr 20 2026 at 23:03, Waiman Long wrote:
> > + /*
> > + * To properly enable/disable nohz_full dynticks for the affected CPUs,
> > + * the new nohz_full CPUs have to be copied to tick_nohz_full_mask and
> > + * ct_cpu_track_user/ct_cpu_untrack_user() will have to be called
> > + * for those CPUs that have their states changed. Those CPUs should be
> > + * in an offline state.
> > + */
> > + for_each_cpu_andnot(cpu, cpumask, tick_nohz_full_mask) {
> > + WARN_ON_ONCE(cpu_online(cpu));
> > + ct_cpu_track_user(cpu);
> > + cpumask_set_cpu(cpu, tick_nohz_full_mask);
> > + }
> > +
> > + for_each_cpu_andnot(cpu, tick_nohz_full_mask, cpumask) {
> > + WARN_ON_ONCE(cpu_online(cpu));
> > + ct_cpu_untrack_user(cpu);
> > + cpumask_clear_cpu(cpu, tick_nohz_full_mask);
> > + }
> > +}
>
> So this writes to tick_nohz_full_mask while other CPUs can access
> it. That's just wrong and I'm not at all interested in the resulting
> KCSAN warnings.
>
> tick_nohz_full_mask needs to become a RCU protected pointer, which is
> updated once the new mask is established in a separately allocated one.
How about just dropping tick_nohz_full_mask that is just
~housekeeping_cpumask(HK_TYPE_KERNEL_NOISE) which itself is becoming RCU
protected in this patchset?
Thanks.
>
> Thanks,
>
> tglx
>
>
--
Frederic Weisbecker
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2 00/10] sched: Flatten the pick
From: Peter Zijlstra @ 2026-05-13 12:43 UTC (permalink / raw)
To: Vincent Guittot
Cc: mingo, longman, chenridong, juri.lelli, dietmar.eggemann, rostedt,
bsegall, mgorman, vschneid, tj, hannes, mkoutny, cgroups,
linux-kernel, jstultz, kprateek.nayak, qyousef
In-Reply-To: <20260513113510.GK1889694@noisy.programming.kicks-ass.net>
On Wed, May 13, 2026 at 01:35:10PM +0200, Peter Zijlstra wrote:
> On Tue, May 12, 2026 at 10:42:33AM +0200, Vincent Guittot wrote:
>
> > I haven't reviewed the patches yet but I ran some tests with it while
> > testing sched latency related changes for short slice wakeup
> > preemption. I have some large hackbench regressions with this series
> > on HMP system with and without EAS. those figures are unexpected
> > because the benchs run on root cfs
> >
> > One example with hackbench 8 groups thread pipe
> > tip/sched/core tip/sched/core +this patchset +this patchset
> > slice 2.8ms 16ms 2.8ms 16ms
> > dragonboard rb5 with EAS
> > 0,748(+/-4,6%) 0,621(+/-3.6%) +17% 1,915(+/-7.9%) -156%
> > 0,689(+/- 9.1%) +8%
> >
> > radxa orion6 HMP without EAS
> > 0,588(+/-5.8%) 0,677(+/-5.9%) -15% 1,505(+/-10%) -156%
> > 1,071(+/-5.9%) -82%
> >
> > Increasing the slice partly removes regressions but tis is surprising
> > because the bench runs at root cfs and I thought that results will not
> > change in such a case
>
> D'oh :/
>
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index e54da4c6c945..77d0e1937f2c 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -9071,7 +9071,7 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f
> enum preempt_wakeup_action preempt_action = PREEMPT_WAKEUP_PICK;
> struct task_struct *donor = rq->donor;
> struct sched_entity *nse, *se = &donor->se, *pse = &p->se;
> - struct cfs_rq *cfs_rq = task_cfs_rq(donor);
> + struct cfs_rq *cfs_rq = &rq->cfs;
> int cse_is_idle, pse_is_idle;
>
> /*
With that fixed, I now get:
vanilla slice(*)
FPS min 3.0 11.1
avg 44.7 57.3
max 88.1 96.2
FT min 9.1 8.0
avg 41.4 21.0
max 157.2 53.9
FPS (Frames Per Second)
FT (FrameTime)
Which I suppose shows we now preempt less. Its still significantly
better with reduced slice, but not as good as it was.
^ permalink raw reply
* Re: [PATCH RFC 2/5] dma-heap: charge dma-buf memory via explicit memcg
From: Albert Esteve @ 2026-05-13 12:41 UTC (permalink / raw)
To: Christian König
Cc: Tejun Heo, Johannes Weiner, Michal Koutný, Jonathan Corbet,
Shuah Khan, Sumit Semwal, Michal Hocko, Roman Gushchin,
Shakeel Butt, Muchun Song, Andrew Morton, Benjamin Gaignard,
Brian Starkey, John Stultz, T.J. Mercier, Christian Brauner,
Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Ondrej Mosnacek, Shuah Khan, cgroups, linux-doc, linux-kernel,
linux-media, dri-devel, linaro-mm-sig, linux-mm,
linux-security-module, selinux, linux-kselftest, mripard,
echanude
In-Reply-To: <8ef38815-6ae9-4359-86d4-042554357639@amd.com>
On Tue, May 12, 2026 at 12:14 PM Christian König
<christian.koenig@amd.com> wrote:
>
> On 5/12/26 11:10, Albert Esteve wrote:
> > 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_pid_fd to struct dma_heap_allocation_data. When set to
> > a valid pidfd, DMA_HEAP_IOCTL_ALLOC resolves the target task's
> > memcg and charges the buffer there via mem_cgroup_charge_dmabuf()
> > inside dma_heap_buffer_alloc(). Without charge_pid_fd, and with
> > the mem_accounting module parameter enabled, the buffer is charged
> > to the allocator's own cgroup.
> >
> > Additionally, commit 3c227be90659 ("dma-buf: system_heap: account for
> > system heap allocation in memcg") adds __GFP_ACCOUNT to system-heap
> > page allocations. Keeping __GFP_ACCOUNT would charge the same pages
> > twice (once to kmem, once to MEMCG_DMABUF), thus remove it and route
> > all accounting through a single MEMCG_DMABUF path.
> >
> > 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_pid_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_pid_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.
>
> Well that doesn't looks soo bad, it at least seems to tackle the problem at hand for Android and some of other embedded use cases.
>
> I'm just not sure if this is future prove and will work for all use cases, e.g. cloud gaming, native context for automotive etc...
>
> Essentially the problem boils down to two limitations:
> 1) a piece of memory can only be charged to one cgroup, the framework doesn't has a concept of charging shared memory to multiple groups
> 2) when memory references in the form of file descriptors are passed between applications we have no way of changing the accounting to a different cgroup
>
> The passing of the memory reference already has a well defined uAPI and if we could solve those two limitations we not only solve the problem without introducing new uAPI (with potential new security risks) but also solve it for all other use cases which uses file descriptors as well as. E.g. memfd, accel and GPU drivers etc...
Honestly, adding a hook to fd-passing uAPI to manage charge transfers
sounds like a promising solution requiring no uAPI changes. However,
it still does not cover all paths, e.g., dup() or fork(). And shared
memory sounds like a hard one to tackle, where deciding the best
policy is more a per-usecase thing and would probably require
userspace configuration. All in all, charge_pid_fd covers a
well-defined and immediately practical subset. The UAPI cost is small
and the mechanism is explicit about what it does and doesn't solve. A
general solution, if it ever converges, would likely supersede
charge_pid_fd for most cases, which is a fine outcome if it solves the
problem more completely.
Either way, if you have a specific approach in mind for solving any of
the above limitations, I'd be happy to look into it further.
BR,
Albert.
>
> On the other hand it is really nice to finally see this tackled for at least DMA-buf heaps. On the GPU side I have seen just another try of a driver doing some kind of special driver specific accounting to solve this just a few weeks ago. And to be honest such single driver island approach have the tendency to break more often that they are working correctly.
>
> Regards,
> Christian.
>
> >
> > Signed-off-by: Albert Esteve <aesteve@redhat.com>
> > ---
> > Documentation/admin-guide/cgroup-v2.rst | 5 ++--
> > drivers/dma-buf/dma-buf.c | 16 ++++---------
> > drivers/dma-buf/dma-heap.c | 42 ++++++++++++++++++++++++++++++---
> > drivers/dma-buf/heaps/system_heap.c | 2 --
> > include/uapi/linux/dma-heap.h | 6 +++++
> > 5 files changed, 53 insertions(+), 18 deletions(-)
> >
> > diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> > index 8bdbc2e866430..824d269531eb1 100644
> > --- a/Documentation/admin-guide/cgroup-v2.rst
> > +++ b/Documentation/admin-guide/cgroup-v2.rst
> > @@ -1636,8 +1636,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-buf.c b/drivers/dma-buf/dma-buf.c
> > index ce02377f48908..23fb758b78297 100644
> > --- a/drivers/dma-buf/dma-buf.c
> > +++ b/drivers/dma-buf/dma-buf.c
> > @@ -181,8 +181,11 @@ static void dma_buf_release(struct dentry *dentry)
> > */
> > BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active);
> >
> > - mem_cgroup_uncharge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > - mem_cgroup_put(dmabuf->memcg);
> > + if (dmabuf->memcg) {
> > + mem_cgroup_uncharge_dmabuf(dmabuf->memcg,
> > + PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > + mem_cgroup_put(dmabuf->memcg);
> > + }
> >
> > dmabuf->ops->release(dmabuf);
> >
> > @@ -764,13 +767,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> > dmabuf->resv = resv;
> > }
> >
> > - dmabuf->memcg = get_mem_cgroup_from_mm(current->mm);
> > - if (!mem_cgroup_charge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE,
> > - GFP_KERNEL)) {
> > - ret = -ENOMEM;
> > - goto err_memcg;
> > - }
> > -
> > file->private_data = dmabuf;
> > file->f_path.dentry->d_fsdata = dmabuf;
> > dmabuf->file = file;
> > @@ -781,8 +777,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> >
> > return dmabuf;
> >
> > -err_memcg:
> > - mem_cgroup_put(dmabuf->memcg);
> > err_file:
> > fput(file);
> > err_module:
> > diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c
> > index ac5f8685a6494..ff6e259afcdc0 100644
> > --- a/drivers/dma-buf/dma-heap.c
> > +++ b/drivers/dma-buf/dma-heap.c
> > @@ -7,13 +7,17 @@
> > */
> >
> > #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/nospec.h>
> > +#include <linux/pidfd.h>
> > #include <linux/syscalls.h>
> > #include <linux/uaccess.h>
> > #include <linux/xarray.h>
> > @@ -55,10 +59,12 @@ MODULE_PARM_DESC(mem_accounting,
> > "Enable cgroup-based memory accounting for dma-buf heap allocations (default=false).");
> >
> > static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > - u32 fd_flags,
> > - u64 heap_flags)
> > + u32 fd_flags, u64 heap_flags,
> > + struct mem_cgroup *charge_to)
> > {
> > struct dma_buf *dmabuf;
> > + unsigned int nr_pages;
> > + struct mem_cgroup *memcg = charge_to;
> > int fd;
> >
> > /*
> > @@ -73,6 +79,22 @@ static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > if (IS_ERR(dmabuf))
> > return PTR_ERR(dmabuf);
> >
> > + nr_pages = len / PAGE_SIZE;
> > +
> > + if (memcg)
> > + css_get(&memcg->css);
> > + else if (mem_accounting)
> > + memcg = get_mem_cgroup_from_mm(current->mm);
> > +
> > + if (memcg) {
> > + if (!mem_cgroup_charge_dmabuf(memcg, nr_pages, GFP_KERNEL)) {
> > + mem_cgroup_put(memcg);
> > + dma_buf_put(dmabuf);
> > + return -ENOMEM;
> > + }
> > + dmabuf->memcg = memcg;
> > + }
> > +
> > fd = dma_buf_fd(dmabuf, fd_flags);
> > if (fd < 0) {
> > dma_buf_put(dmabuf);
> > @@ -102,6 +124,9 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > {
> > struct dma_heap_allocation_data *heap_allocation = data;
> > struct dma_heap *heap = file->private_data;
> > + struct mem_cgroup *memcg = NULL;
> > + struct task_struct *task;
> > + unsigned int pidfd_flags;
> > int fd;
> >
> > if (heap_allocation->fd)
> > @@ -113,9 +138,20 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS)
> > return -EINVAL;
> >
> > + if (heap_allocation->charge_pid_fd) {
> > + task = pidfd_get_task(heap_allocation->charge_pid_fd, &pidfd_flags);
> > + if (IS_ERR(task))
> > + return PTR_ERR(task);
> > +
> > + memcg = get_mem_cgroup_from_mm(task->mm);
> > + put_task_struct(task);
> > + }
> > +
> > fd = dma_heap_buffer_alloc(heap, heap_allocation->len,
> > heap_allocation->fd_flags,
> > - heap_allocation->heap_flags);
> > + heap_allocation->heap_flags,
> > + memcg);
> > + mem_cgroup_put(memcg);
> > if (fd < 0)
> > return fd;
> >
> > diff --git a/drivers/dma-buf/heaps/system_heap.c b/drivers/dma-buf/heaps/system_heap.c
> > index 03c2b87cb1112..95d7688167b93 100644
> > --- a/drivers/dma-buf/heaps/system_heap.c
> > +++ b/drivers/dma-buf/heaps/system_heap.c
> > @@ -385,8 +385,6 @@ static struct page *alloc_largest_available(unsigned long size,
> > if (max_order < orders[i])
> > continue;
> > flags = order_flags[i];
> > - if (mem_accounting)
> > - flags |= __GFP_ACCOUNT;
> > page = alloc_pages(flags, orders[i]);
> > if (!page)
> > continue;
> > diff --git a/include/uapi/linux/dma-heap.h b/include/uapi/linux/dma-heap.h
> > index a4cf716a49fa6..e02b0f8cbc6a1 100644
> > --- a/include/uapi/linux/dma-heap.h
> > +++ b/include/uapi/linux/dma-heap.h
> > @@ -29,6 +29,10 @@
> > * handle to the allocated dma-buf
> > * @fd_flags: file descriptor flags used when allocating
> > * @heap_flags: flags passed to heap
> > + * @charge_pid_fd: optional pidfd of the process whose cgroup should be
> > + * charged for this allocation; 0 means charge the calling
> > + * process's cgroup
> > + * @__padding: reserved, must be zero
> > *
> > * Provided by userspace as an argument to the ioctl
> > */
> > @@ -37,6 +41,8 @@ struct dma_heap_allocation_data {
> > __u32 fd;
> > __u32 fd_flags;
> > __u64 heap_flags;
> > + __u32 charge_pid_fd;
> > + __u32 __padding;
> > };
> >
> > #define DMA_HEAP_IOC_MAGIC 'H'
> >
>
^ permalink raw reply
* Re: [RFC PATCH v5 20/29] sched/deadline: Allow deeper hierarchies of RT cgroups
From: Yuri Andriaccio @ 2026-05-13 12:08 UTC (permalink / raw)
To: Tejun Heo
Cc: luca abeni, Peter Zijlstra, Yuri Andriaccio, Ingo Molnar,
Juri Lelli, Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
Ben Segall, Mel Gorman, Valentin Schneider, linux-kernel, hannes,
mkoutny, cgroups
In-Reply-To: <agNvghphiv9sCJrq@slm.duckdns.org>
Hello,
> How is a delegated subtree prevented from setting cpu.rt.min = 'root' and
> escaping its ancestors' cpu.rt.max budget?
Is it strictly required that a child cgroup must have 'less runtime'
than its parent? To be more precise I mean scheduling tasks on the root
runqueue instead of using dl-servers. Small note: given that HCBS
cgroups use dl-servers, and thus run at higher priority than FIFO/RR
scheduled on the root runqueue, if a cgroup rt.min is 'root' would yes
escape its ancestor budget but it may also possibly get starved because
of the priority levels.
If we require that child cgroups cannot escape their parent's bandwidth,
even when using 'root', then the cpu.rt.max file must be disallowed in
the root cgroup (removing the possibility to reserve bandwidth for HCBS,
and so doing the admission test similarly to when SCHED_DEADLINE tasks
are executed), and cpu.rt.max would use either 'root' if the whole
subtree must be scheduled onto the root runqueue or a <runtime> <period>
combination to reserve bandwidth for the whole subtree. The cpu.rt.min
would then only be used to reserve internal bandwidth for the cgroup
itself. This also means that a whole subtree either uses HCBS everywhere
or the root runqueue everywhere.
> If the users on the system already started using rt, how do you
enable the
> controller from the top down with budgets already being used down in the
> hierarchy?
In my original idea rt tasks would only interfere with their own cgroup
configuration, but not with the subtree or their parents. When
cpu.rt.min = 'root', you are free to change cpu.rt.max values to
whatever you like in any place of the hierarchy, and tasks inside the
rt.min = 'root' cgroup would not be affected as they are run in the root
runqueue.
If you want to switch a cgroup from/to 'root' and HCBS, you'd have to
either move all the RT tasks out of the cgroup, set rt.min, and then
move them back in, or change temporarily their scheduling policy to
non-rt (SCHED_OTHER, SCHED_DEADLINE, whatever) and then back.
Hopefully I've answered your questions. Which solution do you think
makes the most sense?
Yuri
^ permalink raw reply
* Re: [PATCH v3 10/12] mm/memcg, swap: store cgroup id in cluster table directly
From: Kairui Song @ 2026-05-13 11:49 UTC (permalink / raw)
To: Chris Li
Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Barry Song, Hugh Dickins, Kemeng Shi, Nhat Pham, Baoquan He,
Johannes Weiner, Youngjun Park, Chengming Zhou, Roman Gushchin,
Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <CACePvbXeUv9g+pKW55hrEbwrFZaZ+XdBip9oSwT7pfztrG_7GA@mail.gmail.com>
On Sat, May 9, 2026 at 6:46 AM Chris Li <chrisl@kernel.org> wrote:
>
> On Tue, Apr 21, 2026 at 2:16 AM Kairui Song via B4 Relay
> <devnull+kasong.tencent.com@kernel.org> wrote:
> >
> > From: Kairui Song <kasong@tencent.com>
> >
> > Drop the usage of the swap_cgroup_ctrl, and use the dynamic cluster
> > table instead.
>
> Nice! It takes so many steps to finally drop the static allocated swap
> cgroup ctrl array. Thank you for making it happen.
>
> >
> > The per-cluster memcg table is 1024 / 512 bytes on most archs, and does
> > not need RCU protection: the cgroup data is only read and written under
> > the cluster lock. That keeps things simple, lets the allocation use
> > plain kmalloc with immediate kfree (no deferred free), and keeps
> > fragmentation acceptable.
> >
> > Signed-off-by: Kairui Song <kasong@tencent.com>
>
> Overall looks good, with some nitpick and question follows.
>
> Acked-by: Chris Li <chrisl@kernel.org>
>
> > ---
> > include/linux/memcontrol.h | 6 ++++--
> > include/linux/swap.h | 8 +++----
> > mm/memcontrol-v1.c | 42 +++++++++++++++++++++++-------------
> > mm/memcontrol.c | 14 +++++++-----
> > mm/swap.h | 4 ++++
> > mm/swap_state.c | 6 ++----
> > mm/swap_table.h | 54 ++++++++++++++++++++++++++++++++++++++++++++++
> > mm/swapfile.c | 35 +++++++++++++++++++-----------
> > mm/vmscan.c | 2 +-
> > 9 files changed, 128 insertions(+), 43 deletions(-)
> >
> > diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> > index a013f37f24aa..bf1a6e131eca 100644
> > --- a/include/linux/memcontrol.h
> > +++ b/include/linux/memcontrol.h
> > @@ -29,6 +29,7 @@ struct obj_cgroup;
> > struct page;
> > struct mm_struct;
> > struct kmem_cache;
> > +struct swap_cluster_info;
> >
> > /* Cgroup-specific page state, on top of universal node page state */
> > enum memcg_stat_item {
> > @@ -1899,7 +1900,7 @@ static inline void mem_cgroup_exit_user_fault(void)
> > current->in_user_fault = 0;
> > }
> >
> > -void __memcg1_swapout(struct folio *folio);
> > +void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci);
> > void memcg1_swapin(struct folio *folio);
> >
> > #else /* CONFIG_MEMCG_V1 */
> > @@ -1929,7 +1930,8 @@ static inline void mem_cgroup_exit_user_fault(void)
> > {
> > }
> >
> > -static inline void __memcg1_swapout(struct folio *folio)
> > +static inline void __memcg1_swapout(struct folio *folio,
> > + struct swap_cluster_info *ci)
> > {
> > }
> >
> > diff --git a/include/linux/swap.h b/include/linux/swap.h
> > index f2949f5844a6..57af4647d432 100644
> > --- a/include/linux/swap.h
> > +++ b/include/linux/swap.h
> > @@ -582,12 +582,12 @@ static inline int mem_cgroup_try_charge_swap(struct folio *folio)
> > return __mem_cgroup_try_charge_swap(folio);
> > }
> >
> > -extern void __mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages);
> > -static inline void mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages)
> > +extern void __mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_pages);
> > +static inline void mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_pages)
> > {
> > if (mem_cgroup_disabled())
> > return;
> > - __mem_cgroup_uncharge_swap(entry, nr_pages);
> > + __mem_cgroup_uncharge_swap(id, nr_pages);
> > }
> >
> > extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg);
> > @@ -598,7 +598,7 @@ static inline int mem_cgroup_try_charge_swap(struct folio *folio)
> > return 0;
> > }
> >
> > -static inline void mem_cgroup_uncharge_swap(swp_entry_t entry,
> > +static inline void mem_cgroup_uncharge_swap(unsigned short id,
> > unsigned int nr_pages)
> > {
> > }
> > diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
> > index 36c507d81dc5..494e7b9adc60 100644
> > --- a/mm/memcontrol-v1.c
> > +++ b/mm/memcontrol-v1.c
> > @@ -14,6 +14,7 @@
> >
> > #include "internal.h"
> > #include "swap.h"
> > +#include "swap_table.h"
> > #include "memcontrol-v1.h"
> >
> > /*
> > @@ -606,14 +607,15 @@ void memcg1_commit_charge(struct folio *folio, struct mem_cgroup *memcg)
> > /**
> > * __memcg1_swapout - transfer a memsw charge to swap
> > * @folio: folio whose memsw charge to transfer
> > + * @ci: the locked swap cluster holding the swap entries
> > *
> > * Transfer the memsw charge of @folio to the swap entry stored in
> > * folio->swap.
> > *
> > - * Context: folio must be isolated, unmapped, locked and is just about
> > - * to be freed, and caller must disable IRQs.
> > + * Context: folio must be isolated, unmapped, locked and is just about to
> > + * be freed, and caller must disable IRQs and hold the swap cluster lock.
> > */
> > -void __memcg1_swapout(struct folio *folio)
> > +void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
> > {
> > struct mem_cgroup *memcg, *swap_memcg;
> > struct obj_cgroup *objcg;
> > @@ -646,7 +648,8 @@ void __memcg1_swapout(struct folio *folio)
> > swap_memcg = mem_cgroup_private_id_get_online(memcg, nr_entries);
> > mod_memcg_state(swap_memcg, MEMCG_SWAP, nr_entries);
> >
> > - swap_cgroup_record(folio, mem_cgroup_private_id(swap_memcg), folio->swap);
> > + __swap_cgroup_set(ci, swp_cluster_offset(folio->swap), nr_entries,
> > + mem_cgroup_private_id(swap_memcg));
> >
> > folio_unqueue_deferred_split(folio);
> > folio->memcg_data = 0;
> > @@ -661,8 +664,7 @@ void __memcg1_swapout(struct folio *folio)
> > }
> >
> > /*
> > - * Interrupts should be disabled here because the caller holds the
> > - * i_pages lock which is taken with interrupts-off. It is
> > + * The caller must hold the swap cluster lock with IRQ off. It is
> > * important here to have the interrupts disabled because it is the
> > * only synchronisation we have for updating the per-CPU variables.
> > */
> > @@ -677,7 +679,7 @@ void __memcg1_swapout(struct folio *folio)
> > }
> >
> > /**
> > - * memcg1_swapin - uncharge swap slot
> > + * memcg1_swapin - uncharge swap slot on swapin
> > * @folio: folio being swapped in
> > *
> > * Call this function after successfully adding the charged
> > @@ -687,6 +689,10 @@ void __memcg1_swapout(struct folio *folio)
> > */
> > void memcg1_swapin(struct folio *folio)
> > {
> > + struct swap_cluster_info *ci;
> > + unsigned long nr_pages;
> > + unsigned short id;
> > +
> > VM_WARN_ON_ONCE_FOLIO(!folio_test_swapcache(folio), folio);
> > VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
> >
> > @@ -702,14 +708,20 @@ void memcg1_swapin(struct folio *folio)
> > * correspond 1:1 to page and swap slot lifetimes: we charge the
> > * page to memory here, and uncharge swap when the slot is freed.
> > */
> > - if (do_memsw_account()) {
> > - /*
> > - * The swap entry might not get freed for a long time,
> > - * let's not wait for it. The page already received a
> > - * memory+swap charge, drop the swap entry duplicate.
> > - */
> > - mem_cgroup_uncharge_swap(folio->swap, folio_nr_pages(folio));
> > - }
> > + if (!do_memsw_account())
> > + return;
> > +
> > + /*
> > + * The swap entry might not get freed for a long time,
> > + * let's not wait for it. The page already received a
> > + * memory+swap charge, drop the swap entry duplicate.
> > + */
> > + nr_pages = folio_nr_pages(folio);
> > + ci = swap_cluster_get_and_lock(folio);
> > + id = __swap_cgroup_clear(ci, swp_cluster_offset(folio->swap),
> > + nr_pages);
> > + swap_cluster_unlock(ci);
> > + mem_cgroup_uncharge_swap(id, nr_pages);
> > }
> >
> > void memcg1_uncharge_batch(struct mem_cgroup *memcg, unsigned long pgpgout,
> > diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> > index 641706fa47bf..193c8eb73be7 100644
> > --- a/mm/memcontrol.c
> > +++ b/mm/memcontrol.c
> > @@ -64,6 +64,8 @@
> > #include <linux/sched/isolation.h>
> > #include <linux/kmemleak.h>
> > #include "internal.h"
> > +#include "swap.h"
> > +#include "swap_table.h"
> > #include <net/sock.h>
> > #include <net/ip.h>
> > #include "slab.h"
> > @@ -5462,6 +5464,7 @@ int __init mem_cgroup_init(void)
> > int __mem_cgroup_try_charge_swap(struct folio *folio)
> > {
> > unsigned int nr_pages = folio_nr_pages(folio);
> > + struct swap_cluster_info *ci;
> > struct page_counter *counter;
> > struct mem_cgroup *memcg;
> > struct obj_cgroup *objcg;
> > @@ -5495,22 +5498,23 @@ int __mem_cgroup_try_charge_swap(struct folio *folio)
> > }
> > mod_memcg_state(memcg, MEMCG_SWAP, nr_pages);
> >
> > - swap_cgroup_record(folio, mem_cgroup_private_id(memcg), folio->swap);
> > + ci = swap_cluster_get_and_lock(folio);
> > + __swap_cgroup_set(ci, swp_cluster_offset(folio->swap), nr_pages,
> > + mem_cgroup_private_id(memcg));
> > + swap_cluster_unlock(ci);
> >
> > return 0;
> > }
> >
> > /**
> > * __mem_cgroup_uncharge_swap - uncharge swap space
> > - * @entry: swap entry to uncharge
> > + * @id: cgroup id to uncharge
> > * @nr_pages: the amount of swap space to uncharge
> > */
> > -void __mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages)
> > +void __mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_pages)
> > {
> > struct mem_cgroup *memcg;
> > - unsigned short id;
> >
> > - id = swap_cgroup_clear(entry, nr_pages);
> > rcu_read_lock();
> > memcg = mem_cgroup_from_private_id(id);
> > if (memcg) {
> > diff --git a/mm/swap.h b/mm/swap.h
> > index 80c2f1bf7a57..e4ac7dbc1080 100644
> > --- a/mm/swap.h
> > +++ b/mm/swap.h
> > @@ -5,6 +5,7 @@
> > #include <linux/atomic.h> /* for atomic_long_t */
> > struct mempolicy;
> > struct swap_iocb;
> > +struct swap_memcg_table;
> >
> > extern int page_cluster;
> >
> > @@ -38,6 +39,9 @@ struct swap_cluster_info {
> > u8 order;
> > atomic_long_t __rcu *table; /* Swap table entries, see mm/swap_table.h */
> > unsigned int *extend_table; /* For large swap count, protected by ci->lock */
> > +#ifdef CONFIG_MEMCG
> > + struct swap_memcg_table *memcg_table; /* Swap table entries' cgroup record */
> > +#endif
> > struct list_head list;
> > };
> >
> > diff --git a/mm/swap_state.c b/mm/swap_state.c
> > index 86d517a33a55..71a3f128fcf0 100644
> > --- a/mm/swap_state.c
> > +++ b/mm/swap_state.c
> > @@ -176,21 +176,19 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci,
> > if (shadowp && swp_tb_is_shadow(old_tb))
> > *shadowp = swp_tb_to_shadow(old_tb);
> > if (memcg_id)
> > - *memcg_id = lookup_swap_cgroup_id(targ_entry);
> > + *memcg_id = __swap_cgroup_get(ci, ci_off);
> >
> > if (nr == 1)
> > return 0;
> >
> > - targ_entry.val = round_down(targ_entry.val, nr);
> > ci_off = round_down(ci_off, nr);
> > ci_end = ci_off + nr;
> > do {
> > old_tb = __swap_table_get(ci, ci_off);
> > if (unlikely(swp_tb_is_folio(old_tb) ||
> > !__swp_tb_get_count(old_tb) ||
> > - (memcg_id && *memcg_id != lookup_swap_cgroup_id(targ_entry))))
> > + (memcg_id && *memcg_id != __swap_cgroup_get(ci, ci_off))))
> > return -EBUSY;
> > - targ_entry.val++;
> > } while (++ci_off < ci_end);
> >
> > return 0;
> > diff --git a/mm/swap_table.h b/mm/swap_table.h
> > index 8415ffbe2b9c..b2b02ee161b1 100644
> > --- a/mm/swap_table.h
> > +++ b/mm/swap_table.h
> > @@ -11,6 +11,11 @@ struct swap_table {
> > atomic_long_t entries[SWAPFILE_CLUSTER];
> > };
> >
> > +/* For storing memcg private id */
> > +struct swap_memcg_table {
> > + unsigned short id[SWAPFILE_CLUSTER];
> > +};
> > +
> > #define SWP_TABLE_USE_PAGE (sizeof(struct swap_table) == PAGE_SIZE)
> >
> > /*
> > @@ -247,4 +252,53 @@ static inline unsigned long swap_table_get(struct swap_cluster_info *ci,
> >
> > return swp_tb;
> > }
> > +
> > +#ifdef CONFIG_MEMCG
> > +static inline void __swap_cgroup_set(struct swap_cluster_info *ci,
> > + unsigned int ci_off, unsigned long nr, unsigned short id)
> > +{
> > + lockdep_assert_held(&ci->lock);
> > + VM_WARN_ON_ONCE(ci_off >= SWAPFILE_CLUSTER);
> > + do {
> > + ci->memcg_table->id[ci_off++] = id;
>
> Do you need to check the memcg_table is not NULL here? Because this
> function is no longer static. Another caller might invoke this when
> the cluster hasn't allocated the memcg_table. They shouldn't. We might
> want some check and complain here.
Good idea, a NULL check seems good to be defensive. I also noticed
that I should skip memcg table allocation as well if
mem_cgroup_disabled() is true to save some memory. I will update this.
^ permalink raw reply
* Re: [PATCH RFC 2/5] dma-heap: charge dma-buf memory via explicit memcg
From: Albert Esteve @ 2026-05-13 11:39 UTC (permalink / raw)
To: T.J. Mercier
Cc: Christian König, Tejun Heo, Johannes Weiner,
Michal Koutný, Jonathan Corbet, Shuah Khan, Sumit Semwal,
Michal Hocko, Roman Gushchin, Shakeel Butt, Muchun Song,
Andrew Morton, Benjamin Gaignard, Brian Starkey, John Stultz,
Christian Brauner, Paul Moore, James Morris, Serge E. Hallyn,
Stephen Smalley, Ondrej Mosnacek, Shuah Khan, cgroups, linux-doc,
linux-kernel, linux-media, dri-devel, linaro-mm-sig, linux-mm,
linux-security-module, selinux, linux-kselftest, mripard,
echanude
In-Reply-To: <CABdmKX2uwZ12kYJYPJGfWxuMBOJS=64b1GRj72tfB5D=NKM22w@mail.gmail.com>
On Tue, May 12, 2026 at 8:53 PM T.J. Mercier <tjmercier@google.com> wrote:
>
> On Tue, May 12, 2026 at 3:14 AM Christian König
> <christian.koenig@amd.com> wrote:
> >
> > On 5/12/26 11:10, Albert Esteve wrote:
> > > 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_pid_fd to struct dma_heap_allocation_data. When set to
> > > a valid pidfd, DMA_HEAP_IOCTL_ALLOC resolves the target task's
> > > memcg and charges the buffer there via mem_cgroup_charge_dmabuf()
> > > inside dma_heap_buffer_alloc(). Without charge_pid_fd, and with
> > > the mem_accounting module parameter enabled, the buffer is charged
> > > to the allocator's own cgroup.
> > >
> > > Additionally, commit 3c227be90659 ("dma-buf: system_heap: account for
> > > system heap allocation in memcg") adds __GFP_ACCOUNT to system-heap
> > > page allocations. Keeping __GFP_ACCOUNT would charge the same pages
> > > twice (once to kmem, once to MEMCG_DMABUF), thus remove it and route
> > > all accounting through a single MEMCG_DMABUF path.
> > >
> > > 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_pid_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_pid_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.
> >
> > Well that doesn't looks soo bad, it at least seems to tackle the problem at hand for Android and some of other embedded use cases.
>
> Yeah I think this might work. I know of 3 cases, and it trivially
> solves the first two. The third requires some work on our end to
> extend our userspace interfaces to include the pidfd but it seems
> doable. I'm checking with our graphics folks.
>
> 1) Direct allocation from user (e.g. app -> allocation ioctl on
> /dev/dma_heap/foo)
> No changes required to userspace. mem_accounting=1 charges the app.
>
> 2) Single hop remote allocation (e.g. app -> AHardwareBuffer_allocate
> -> gralloc)
> gralloc has the caller's pid as described in the commit message. Open
> a pidfd and pass it in the dma_heap_allocation_data.
>
> 3) Double hop remote allocation (e.g. app -> dequeueBuffer ->
> SurfaceFlinger -> gralloc)
> In this case gralloc knows SurfaceFlinger's pid, but not the app's. So
> we need to add the app's pidfd to the SurfaceFlinger -> gralloc
> interface, or transfer the memcg charge from SurfaceFlinger to the app
> after the allocation.
> It'd be nice to avoid the charge transfer option entirely, but if we
> need it that doesn't seem so bad in this case because it's a bulk
> charge for the entire dmabuf rather than per-page. So the exporter
> doesn't need to get involved (we wouldn't need a new dma_buf_op) and
> we wouldn't have to worry about looping and locking for each page.
>
> > I'm just not sure if this is future prove and will work for all use cases, e.g. cloud gaming, native context for automotive etc...
> >
> > Essentially the problem boils down to two limitations:
> > 1) a piece of memory can only be charged to one cgroup, the framework doesn't has a concept of charging shared memory to multiple groups
>
> Yup, memcg already has this problem with pagecache and shmem.
>
> > 2) when memory references in the form of file descriptors are passed between applications we have no way of changing the accounting to a different cgroup
> >
> > The passing of the memory reference already has a well defined uAPI and if we could solve those two limitations we not only solve the problem without introducing new uAPI (with potential new security risks) but also solve it for all other use cases which uses file descriptors as well as. E.g. memfd, accel and GPU drivers etc...
> >
> > On the other hand it is really nice to finally see this tackled for at least DMA-buf heaps.
>
> I have a question about this part. Albert I guess you are interested
> only in accounting dmabuf-heap allocations, or do you expect to add
> __GFP_ACCOUNT or mem_cgroup_charge_dmabuf calls to other
> non-dmabuf-heap exporters?
We're scoping this to dma-buf heaps for now. CMA heaps and the dmem
controller are on the radar for follow-up/parallel work (there will be
dragons and will surely need discussion). For DRM and V4L2 the
long-term intent is migration to heaps, which would make direct
accounting on those paths unnecessary. udmabufs are already
memcg-charged, so adding a separate MEMCG_DMABUF would double count.
Are there any other exporters you had in mind that would benefit from
this approach?
BR,
Albert.
>
> > On the GPU side I have seen just another try of a driver doing some kind of special driver specific accounting to solve this just a few weeks ago. And to be honest such single driver island approach have the tendency to break more often that they are working correctly.
> >
> > Regards,
> > Christian.
> >
> > >
> > > Signed-off-by: Albert Esteve <aesteve@redhat.com>
> > > ---
> > > Documentation/admin-guide/cgroup-v2.rst | 5 ++--
> > > drivers/dma-buf/dma-buf.c | 16 ++++---------
> > > drivers/dma-buf/dma-heap.c | 42 ++++++++++++++++++++++++++++++---
> > > drivers/dma-buf/heaps/system_heap.c | 2 --
> > > include/uapi/linux/dma-heap.h | 6 +++++
> > > 5 files changed, 53 insertions(+), 18 deletions(-)
> > >
> > > diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> > > index 8bdbc2e866430..824d269531eb1 100644
> > > --- a/Documentation/admin-guide/cgroup-v2.rst
> > > +++ b/Documentation/admin-guide/cgroup-v2.rst
> > > @@ -1636,8 +1636,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-buf.c b/drivers/dma-buf/dma-buf.c
> > > index ce02377f48908..23fb758b78297 100644
> > > --- a/drivers/dma-buf/dma-buf.c
> > > +++ b/drivers/dma-buf/dma-buf.c
> > > @@ -181,8 +181,11 @@ static void dma_buf_release(struct dentry *dentry)
> > > */
> > > BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active);
> > >
> > > - mem_cgroup_uncharge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > > - mem_cgroup_put(dmabuf->memcg);
> > > + if (dmabuf->memcg) {
> > > + mem_cgroup_uncharge_dmabuf(dmabuf->memcg,
> > > + PAGE_ALIGN(dmabuf->size) / PAGE_SIZE);
> > > + mem_cgroup_put(dmabuf->memcg);
> > > + }
> > >
> > > dmabuf->ops->release(dmabuf);
> > >
> > > @@ -764,13 +767,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> > > dmabuf->resv = resv;
> > > }
> > >
> > > - dmabuf->memcg = get_mem_cgroup_from_mm(current->mm);
> > > - if (!mem_cgroup_charge_dmabuf(dmabuf->memcg, PAGE_ALIGN(dmabuf->size) / PAGE_SIZE,
> > > - GFP_KERNEL)) {
> > > - ret = -ENOMEM;
> > > - goto err_memcg;
> > > - }
> > > -
> > > file->private_data = dmabuf;
> > > file->f_path.dentry->d_fsdata = dmabuf;
> > > dmabuf->file = file;
> > > @@ -781,8 +777,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
> > >
> > > return dmabuf;
> > >
> > > -err_memcg:
> > > - mem_cgroup_put(dmabuf->memcg);
> > > err_file:
> > > fput(file);
> > > err_module:
> > > diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c
> > > index ac5f8685a6494..ff6e259afcdc0 100644
> > > --- a/drivers/dma-buf/dma-heap.c
> > > +++ b/drivers/dma-buf/dma-heap.c
> > > @@ -7,13 +7,17 @@
> > > */
> > >
> > > #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/nospec.h>
> > > +#include <linux/pidfd.h>
> > > #include <linux/syscalls.h>
> > > #include <linux/uaccess.h>
> > > #include <linux/xarray.h>
> > > @@ -55,10 +59,12 @@ MODULE_PARM_DESC(mem_accounting,
> > > "Enable cgroup-based memory accounting for dma-buf heap allocations (default=false).");
> > >
> > > static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > > - u32 fd_flags,
> > > - u64 heap_flags)
> > > + u32 fd_flags, u64 heap_flags,
> > > + struct mem_cgroup *charge_to)
> > > {
> > > struct dma_buf *dmabuf;
> > > + unsigned int nr_pages;
> > > + struct mem_cgroup *memcg = charge_to;
> > > int fd;
> > >
> > > /*
> > > @@ -73,6 +79,22 @@ static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
> > > if (IS_ERR(dmabuf))
> > > return PTR_ERR(dmabuf);
> > >
> > > + nr_pages = len / PAGE_SIZE;
> > > +
> > > + if (memcg)
> > > + css_get(&memcg->css);
> > > + else if (mem_accounting)
> > > + memcg = get_mem_cgroup_from_mm(current->mm);
> > > +
> > > + if (memcg) {
> > > + if (!mem_cgroup_charge_dmabuf(memcg, nr_pages, GFP_KERNEL)) {
> > > + mem_cgroup_put(memcg);
> > > + dma_buf_put(dmabuf);
> > > + return -ENOMEM;
> > > + }
> > > + dmabuf->memcg = memcg;
> > > + }
> > > +
> > > fd = dma_buf_fd(dmabuf, fd_flags);
> > > if (fd < 0) {
> > > dma_buf_put(dmabuf);
> > > @@ -102,6 +124,9 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > > {
> > > struct dma_heap_allocation_data *heap_allocation = data;
> > > struct dma_heap *heap = file->private_data;
> > > + struct mem_cgroup *memcg = NULL;
> > > + struct task_struct *task;
> > > + unsigned int pidfd_flags;
> > > int fd;
> > >
> > > if (heap_allocation->fd)
> > > @@ -113,9 +138,20 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data)
> > > if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS)
> > > return -EINVAL;
> > >
> > > + if (heap_allocation->charge_pid_fd) {
> > > + task = pidfd_get_task(heap_allocation->charge_pid_fd, &pidfd_flags);
> > > + if (IS_ERR(task))
> > > + return PTR_ERR(task);
> > > +
> > > + memcg = get_mem_cgroup_from_mm(task->mm);
> > > + put_task_struct(task);
> > > + }
> > > +
> > > fd = dma_heap_buffer_alloc(heap, heap_allocation->len,
> > > heap_allocation->fd_flags,
> > > - heap_allocation->heap_flags);
> > > + heap_allocation->heap_flags,
> > > + memcg);
> > > + mem_cgroup_put(memcg);
> > > if (fd < 0)
> > > return fd;
> > >
> > > diff --git a/drivers/dma-buf/heaps/system_heap.c b/drivers/dma-buf/heaps/system_heap.c
> > > index 03c2b87cb1112..95d7688167b93 100644
> > > --- a/drivers/dma-buf/heaps/system_heap.c
> > > +++ b/drivers/dma-buf/heaps/system_heap.c
> > > @@ -385,8 +385,6 @@ static struct page *alloc_largest_available(unsigned long size,
> > > if (max_order < orders[i])
> > > continue;
> > > flags = order_flags[i];
> > > - if (mem_accounting)
> > > - flags |= __GFP_ACCOUNT;
> > > page = alloc_pages(flags, orders[i]);
> > > if (!page)
> > > continue;
> > > diff --git a/include/uapi/linux/dma-heap.h b/include/uapi/linux/dma-heap.h
> > > index a4cf716a49fa6..e02b0f8cbc6a1 100644
> > > --- a/include/uapi/linux/dma-heap.h
> > > +++ b/include/uapi/linux/dma-heap.h
> > > @@ -29,6 +29,10 @@
> > > * handle to the allocated dma-buf
> > > * @fd_flags: file descriptor flags used when allocating
> > > * @heap_flags: flags passed to heap
> > > + * @charge_pid_fd: optional pidfd of the process whose cgroup should be
> > > + * charged for this allocation; 0 means charge the calling
> > > + * process's cgroup
> > > + * @__padding: reserved, must be zero
> > > *
> > > * Provided by userspace as an argument to the ioctl
> > > */
> > > @@ -37,6 +41,8 @@ struct dma_heap_allocation_data {
> > > __u32 fd;
> > > __u32 fd_flags;
> > > __u64 heap_flags;
> > > + __u32 charge_pid_fd;
> > > + __u32 __padding;
> > > };
> > >
> > > #define DMA_HEAP_IOC_MAGIC 'H'
> > >
> >
>
^ permalink raw reply
* Re: [PATCH v2 00/10] sched: Flatten the pick
From: Peter Zijlstra @ 2026-05-13 11:35 UTC (permalink / raw)
To: Vincent Guittot
Cc: mingo, longman, chenridong, juri.lelli, dietmar.eggemann, rostedt,
bsegall, mgorman, vschneid, tj, hannes, mkoutny, cgroups,
linux-kernel, jstultz, kprateek.nayak, qyousef
In-Reply-To: <CAKfTPtA2aBtuBffVV02VgsRRi5mRK0G5ununzuvJ7h7buygNxg@mail.gmail.com>
On Tue, May 12, 2026 at 10:42:33AM +0200, Vincent Guittot wrote:
> I haven't reviewed the patches yet but I ran some tests with it while
> testing sched latency related changes for short slice wakeup
> preemption. I have some large hackbench regressions with this series
> on HMP system with and without EAS. those figures are unexpected
> because the benchs run on root cfs
>
> One example with hackbench 8 groups thread pipe
> tip/sched/core tip/sched/core +this patchset +this patchset
> slice 2.8ms 16ms 2.8ms 16ms
> dragonboard rb5 with EAS
> 0,748(+/-4,6%) 0,621(+/-3.6%) +17% 1,915(+/-7.9%) -156%
> 0,689(+/- 9.1%) +8%
>
> radxa orion6 HMP without EAS
> 0,588(+/-5.8%) 0,677(+/-5.9%) -15% 1,505(+/-10%) -156%
> 1,071(+/-5.9%) -82%
>
> Increasing the slice partly removes regressions but tis is surprising
> because the bench runs at root cfs and I thought that results will not
> change in such a case
D'oh :/
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index e54da4c6c945..77d0e1937f2c 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -9071,7 +9071,7 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f
enum preempt_wakeup_action preempt_action = PREEMPT_WAKEUP_PICK;
struct task_struct *donor = rq->donor;
struct sched_entity *nse, *se = &donor->se, *pse = &p->se;
- struct cfs_rq *cfs_rq = task_cfs_rq(donor);
+ struct cfs_rq *cfs_rq = &rq->cfs;
int cse_is_idle, pse_is_idle;
/*
^ permalink raw reply related
* [PATCH v2 4/4] cgroup/rdma: document rdma.peak, rdma.events and rdma.events.local
From: Tao Cui @ 2026-05-13 10:49 UTC (permalink / raw)
To: tj, hannes, mkoutny, cgroups; +Cc: Tao Cui
In-Reply-To: <20260513104956.373216-1-cuitao@kylinos.cn>
Add interface file documentation for the new rdma cgroup files to
Documentation/admin-guide/cgroup-v2.rst.
Signed-off-by: Tao Cui <cuitao@kylinos.cn>
---
Documentation/admin-guide/cgroup-v2.rst | 54 +++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index 6efd0095ed99..c8763300e827 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -2785,6 +2785,60 @@ RDMA Interface Files
mlx4_0 hca_handle=1 hca_object=20
ocrdma1 hca_handle=1 hca_object=23
+ rdma.peak
+ A read-only nested-keyed file that exists for all the cgroups
+ except root. It shows the historical high watermark of
+ resource usage per device since the cgroup was created.
+
+ An example for mlx4 and ocrdma device follows::
+
+ mlx4_0 hca_handle=1 hca_object=20
+ ocrdma1 hca_handle=0 hca_object=23
+
+ rdma.events
+ A read-only nested-keyed file which exists on non-root
+ cgroups. The following nested keys are defined.
+
+ max
+ The number of times a process in this cgroup or its
+ descendants attempted an RDMA resource allocation that
+ was rejected because a rdma.max limit in the subtree
+ was reached. This is a hierarchical counter: the event
+ is propagated upward to all ancestor cgroups that
+ already have a resource pool for the device. A value
+ change in this file generates a file modified event.
+
+ alloc_fail
+ The number of RDMA resource allocation attempts that
+ originated in this cgroup or its descendants and failed
+ due to a rdma.max limit being reached. This is a
+ hierarchical counter propagated upward.
+
+ An example for mlx4 device follows::
+
+ mlx4_0 hca_handle.max=5 hca_handle.alloc_fail=3 hca_object.max=0 hca_object.alloc_fail=0
+
+ rdma.events.local
+ Similar to rdma.events but the fields in the file are local
+ to the cgroup i.e. not hierarchical. The file modified event
+ generated on this file reflects only the local events.
+
+ The following nested keys are defined.
+
+ max
+ The number of times a process in this cgroup or its
+ descendants attempted an RDMA resource allocation that
+ was rejected because this cgroup's own rdma.max limit
+ was reached.
+ alloc_fail
+ The number of RDMA resource allocation attempts
+ originating from this cgroup that failed due to this
+ cgroup's or an ancestor's rdma.max limit.
+
+ An example for mlx4 device follows::
+
+ mlx4_0 hca_handle.max=5 hca_handle.alloc_fail=0 hca_object.max=0 hca_object.alloc_fail=0
+
DMEM
----
--
2.43.0
^ permalink raw reply related
* [PATCH v2 3/4] cgroup/rdma: add rdma.events.local for per-cgroup allocation failure attribution
From: Tao Cui @ 2026-05-13 10:49 UTC (permalink / raw)
To: tj, hannes, mkoutny, cgroups; +Cc: Tao Cui
In-Reply-To: <20260513104956.373216-1-cuitao@kylinos.cn>
Add per-cgroup local event counters to track RDMA resource limit
exhaustion from the perspective of individual cgroups. The
rdma.events.local file reports two per-resource counters:
- max: number of times this cgroup's limit was the one that blocked
an allocation in the subtree
- alloc_fail: number of allocation attempts originating from this
cgroup that failed due to an ancestor's limit
This mirrors the design of pids.events.local, where events are
attributed to the cgroup that imposed the limit, not necessarily the
cgroup where the allocation was attempted.
Signed-off-by: Tao Cui <cuitao@kylinos.cn>
---
include/linux/cgroup_rdma.h | 3 +-
kernel/cgroup/rdma.c | 94 ++++++++++++++++++++++++++++++++-----
2 files changed, 85 insertions(+), 12 deletions(-)
diff --git a/include/linux/cgroup_rdma.h b/include/linux/cgroup_rdma.h
index ac691fe7d3f5..404e746552ca 100644
--- a/include/linux/cgroup_rdma.h
+++ b/include/linux/cgroup_rdma.h
@@ -25,8 +25,9 @@ struct rdma_cgroup {
*/
struct list_head rpools;
- /* Handle for rdma.events */
+ /* Handles for rdma.events[.local] */
struct cgroup_file events_file;
+ struct cgroup_file events_local_file;
};
struct rdmacg_device {
diff --git a/kernel/cgroup/rdma.c b/kernel/cgroup/rdma.c
index 2b729976b9e9..5c94cf080655 100644
--- a/kernel/cgroup/rdma.c
+++ b/kernel/cgroup/rdma.c
@@ -82,8 +82,11 @@ struct rdmacg_resource_pool {
/* total number counts which are set to max */
int num_max_cnt;
- /* per-resource hierarchical max event counters */
+ /* per-resource event counters */
u64 events_max[RDMACG_RESOURCE_MAX];
+ u64 events_alloc_fail[RDMACG_RESOURCE_MAX];
+ u64 events_local_max[RDMACG_RESOURCE_MAX];
+ u64 events_local_alloc_fail[RDMACG_RESOURCE_MAX];
};
static struct rdma_cgroup *css_rdmacg(struct cgroup_subsys_state *css)
@@ -218,7 +221,10 @@ uncharge_cg_locked(struct rdma_cgroup *cg,
*/
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
if (rpool->resources[i].peak ||
- READ_ONCE(rpool->events_max[i]))
+ READ_ONCE(rpool->events_max[i]) ||
+ READ_ONCE(rpool->events_local_max[i]) ||
+ READ_ONCE(rpool->events_alloc_fail[i]) ||
+ READ_ONCE(rpool->events_local_alloc_fail[i]))
return;
}
/*
@@ -230,16 +236,19 @@ uncharge_cg_locked(struct rdma_cgroup *cg,
}
/**
- * rdmacg_event_locked - fire hierarchical max event when resource limit is hit
+ * rdmacg_event_locked - fire event when resource allocation exceeds limit
+ * @cg: requesting cgroup
* @over_cg: cgroup whose limit was exceeded
* @device: rdma device
* @index: resource type index
*
- * Must be called under rdmacg_mutex. Propagates max event counts
- * from @over_cg (including itself) upward to all ancestors with
- * an rpool and notifies userspace.
+ * Must be called under rdmacg_mutex. Updates event counters in the
+ * resource pools of @cg and @over_cg, propagates hierarchical max
+ * events from @over_cg (including itself) upward, and notifies
+ * userspace via cgroup_file_notify().
*/
-static void rdmacg_event_locked(struct rdma_cgroup *over_cg,
+static void rdmacg_event_locked(struct rdma_cgroup *cg,
+ struct rdma_cgroup *over_cg,
struct rdmacg_device *device,
enum rdmacg_resource_type index)
{
@@ -248,6 +257,21 @@ static void rdmacg_event_locked(struct rdma_cgroup *over_cg,
lockdep_assert_held(&rdmacg_mutex);
+ /* Increment local alloc_fail in requesting cgroup */
+ rpool = find_cg_rpool_locked(cg, device);
+ if (rpool) {
+ rpool->events_local_alloc_fail[index]++;
+ cgroup_file_notify(&cg->events_local_file);
+ }
+
+ /* Increment local max in the over-limit cgroup */
+ rpool = find_cg_rpool_locked(over_cg, device);
+ if (rpool) {
+ rpool->events_local_max[index]++;
+ cgroup_file_notify(&over_cg->events_local_file);
+ }
+
+ /* Propagate hierarchical max events upward */
for (p = over_cg; parent_rdmacg(p); p = parent_rdmacg(p)) {
rpool = find_cg_rpool_locked(p, device);
if (rpool) {
@@ -255,6 +279,14 @@ static void rdmacg_event_locked(struct rdma_cgroup *over_cg,
cgroup_file_notify(&p->events_file);
}
}
+ /* Propagate hierarchical alloc_fail from requesting cgroup upward */
+ for (p = cg; parent_rdmacg(p); p = parent_rdmacg(p)) {
+ rpool = find_cg_rpool_locked(p, device);
+ if (rpool) {
+ rpool->events_alloc_fail[index]++;
+ cgroup_file_notify(&p->events_file);
+ }
+ }
}
/**
@@ -368,7 +400,7 @@ int rdmacg_try_charge(struct rdma_cgroup **rdmacg,
err:
if (ret == -EAGAIN)
- rdmacg_event_locked(p, device, index);
+ rdmacg_event_locked(cg, p, device, index);
mutex_unlock(&rdmacg_mutex);
rdmacg_uncharge_hierarchy(cg, device, p, index);
return ret;
@@ -529,7 +561,10 @@ static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
if (rpool->resources[i].peak ||
- READ_ONCE(rpool->events_max[i]))
+ READ_ONCE(rpool->events_max[i]) ||
+ READ_ONCE(rpool->events_local_max[i]) ||
+ READ_ONCE(rpool->events_alloc_fail[i]) ||
+ READ_ONCE(rpool->events_local_alloc_fail[i]))
goto dev_err;
}
/*
@@ -618,9 +653,40 @@ static int rdmacg_events_show(struct seq_file *sf, void *v)
seq_printf(sf, "%s ", device->name);
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
- seq_printf(sf, "%s.max=%lld",
+ seq_printf(sf, "%s.max=%lld %s.alloc_fail=%lld",
rdmacg_resource_names[i],
- rpool ? (s64)READ_ONCE(rpool->events_max[i]) : 0);
+ rpool ? (s64)READ_ONCE(rpool->events_max[i]) : 0,
+ rdmacg_resource_names[i],
+ rpool ? (s64)READ_ONCE(rpool->events_alloc_fail[i]) : 0);
+ if (i < RDMACG_RESOURCE_MAX - 1)
+ seq_putc(sf, ' ');
+ }
+ seq_putc(sf, '\n');
+ }
+
+ mutex_unlock(&rdmacg_mutex);
+ return 0;
+}
+
+static int rdmacg_events_local_show(struct seq_file *sf, void *v)
+{
+ struct rdma_cgroup *cg = css_rdmacg(seq_css(sf));
+ struct rdmacg_resource_pool *rpool;
+ struct rdmacg_device *device;
+ int i;
+
+ mutex_lock(&rdmacg_mutex);
+
+ list_for_each_entry(device, &rdmacg_devices, dev_node) {
+ rpool = find_cg_rpool_locked(cg, device);
+
+ seq_printf(sf, "%s ", device->name);
+ for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
+ seq_printf(sf, "%s.max=%lld %s.alloc_fail=%lld",
+ rdmacg_resource_names[i],
+ rpool ? (s64)READ_ONCE(rpool->events_local_max[i]) : 0,
+ rdmacg_resource_names[i],
+ rpool ? (s64)READ_ONCE(rpool->events_local_alloc_fail[i]) : 0);
if (i < RDMACG_RESOURCE_MAX - 1)
seq_putc(sf, ' ');
}
@@ -657,6 +723,12 @@ static struct cftype rdmacg_files[] = {
.file_offset = offsetof(struct rdma_cgroup, events_file),
.flags = CFTYPE_NOT_ON_ROOT,
},
+ {
+ .name = "events.local",
+ .seq_show = rdmacg_events_local_show,
+ .file_offset = offsetof(struct rdma_cgroup, events_local_file),
+ .flags = CFTYPE_NOT_ON_ROOT,
+ },
{ } /* terminate */
};
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/4] cgroup/rdma: add rdma.events to track resource limit exhaustion
From: Tao Cui @ 2026-05-13 10:49 UTC (permalink / raw)
To: tj, hannes, mkoutny, cgroups; +Cc: Tao Cui
In-Reply-To: <20260513104956.373216-1-cuitao@kylinos.cn>
Add per-device hierarchical event counters to track when RDMA resource
limits are exceeded. The rdma.events file reports max event counts
propagated upward from the cgroup whose limit was hit to all ancestors.
This mirrors the design of pids.events, where events are attributed to
the cgroup that imposed the limit, not necessarily the cgroup where the
allocation was attempted. Userspace can monitor this file via
poll/epoll for real-time notification of resource exhaustion.
Signed-off-by: Tao Cui <cuitao@kylinos.cn>
---
include/linux/cgroup_rdma.h | 3 ++
kernel/cgroup/rdma.c | 72 +++++++++++++++++++++++++++++++++++--
2 files changed, 73 insertions(+), 2 deletions(-)
diff --git a/include/linux/cgroup_rdma.h b/include/linux/cgroup_rdma.h
index 80edae03c313..ac691fe7d3f5 100644
--- a/include/linux/cgroup_rdma.h
+++ b/include/linux/cgroup_rdma.h
@@ -24,6 +24,9 @@ struct rdma_cgroup {
* that belongs to this cgroup.
*/
struct list_head rpools;
+
+ /* Handle for rdma.events */
+ struct cgroup_file events_file;
};
struct rdmacg_device {
diff --git a/kernel/cgroup/rdma.c b/kernel/cgroup/rdma.c
index 4e3bf0bade18..2b729976b9e9 100644
--- a/kernel/cgroup/rdma.c
+++ b/kernel/cgroup/rdma.c
@@ -81,6 +81,9 @@ struct rdmacg_resource_pool {
u64 usage_sum;
/* total number counts which are set to max */
int num_max_cnt;
+
+ /* per-resource hierarchical max event counters */
+ u64 events_max[RDMACG_RESOURCE_MAX];
};
static struct rdma_cgroup *css_rdmacg(struct cgroup_subsys_state *css)
@@ -214,7 +217,8 @@ uncharge_cg_locked(struct rdma_cgroup *cg,
* watermark even after all resources are freed.
*/
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
- if (rpool->resources[i].peak)
+ if (rpool->resources[i].peak ||
+ READ_ONCE(rpool->events_max[i]))
return;
}
/*
@@ -225,6 +229,34 @@ uncharge_cg_locked(struct rdma_cgroup *cg,
}
}
+/**
+ * rdmacg_event_locked - fire hierarchical max event when resource limit is hit
+ * @over_cg: cgroup whose limit was exceeded
+ * @device: rdma device
+ * @index: resource type index
+ *
+ * Must be called under rdmacg_mutex. Propagates max event counts
+ * from @over_cg (including itself) upward to all ancestors with
+ * an rpool and notifies userspace.
+ */
+static void rdmacg_event_locked(struct rdma_cgroup *over_cg,
+ struct rdmacg_device *device,
+ enum rdmacg_resource_type index)
+{
+ struct rdmacg_resource_pool *rpool;
+ struct rdma_cgroup *p;
+
+ lockdep_assert_held(&rdmacg_mutex);
+
+ for (p = over_cg; parent_rdmacg(p); p = parent_rdmacg(p)) {
+ rpool = find_cg_rpool_locked(p, device);
+ if (rpool) {
+ rpool->events_max[index]++;
+ cgroup_file_notify(&p->events_file);
+ }
+ }
+}
+
/**
* rdmacg_uncharge_hierarchy - hierarchically uncharge rdma resource count
* @cg: pointer to cg to uncharge and all parents in hierarchy
@@ -335,6 +367,8 @@ int rdmacg_try_charge(struct rdma_cgroup **rdmacg,
return 0;
err:
+ if (ret == -EAGAIN)
+ rdmacg_event_locked(p, device, index);
mutex_unlock(&rdmacg_mutex);
rdmacg_uncharge_hierarchy(cg, device, p, index);
return ret;
@@ -494,7 +528,8 @@ static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
int i;
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
- if (rpool->resources[i].peak)
+ if (rpool->resources[i].peak ||
+ READ_ONCE(rpool->events_max[i]))
goto dev_err;
}
/*
@@ -569,6 +604,33 @@ static int rdmacg_resource_read(struct seq_file *sf, void *v)
return 0;
}
+static int rdmacg_events_show(struct seq_file *sf, void *v)
+{
+ struct rdma_cgroup *cg = css_rdmacg(seq_css(sf));
+ struct rdmacg_resource_pool *rpool;
+ struct rdmacg_device *device;
+ int i;
+
+ mutex_lock(&rdmacg_mutex);
+
+ list_for_each_entry(device, &rdmacg_devices, dev_node) {
+ rpool = find_cg_rpool_locked(cg, device);
+
+ seq_printf(sf, "%s ", device->name);
+ for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
+ seq_printf(sf, "%s.max=%lld",
+ rdmacg_resource_names[i],
+ rpool ? (s64)READ_ONCE(rpool->events_max[i]) : 0);
+ if (i < RDMACG_RESOURCE_MAX - 1)
+ seq_putc(sf, ' ');
+ }
+ seq_putc(sf, '\n');
+ }
+
+ mutex_unlock(&rdmacg_mutex);
+ return 0;
+}
+
static struct cftype rdmacg_files[] = {
{
.name = "max",
@@ -589,6 +651,12 @@ static struct cftype rdmacg_files[] = {
.private = RDMACG_RESOURCE_TYPE_PEAK,
.flags = CFTYPE_NOT_ON_ROOT,
},
+ {
+ .name = "events",
+ .seq_show = rdmacg_events_show,
+ .file_offset = offsetof(struct rdma_cgroup, events_file),
+ .flags = CFTYPE_NOT_ON_ROOT,
+ },
{ } /* terminate */
};
--
2.43.0
^ permalink raw reply related
* [PATCH v2 1/4] cgroup/rdma: add rdma.peak for per-device peak usage tracking
From: Tao Cui @ 2026-05-13 10:49 UTC (permalink / raw)
To: tj, hannes, mkoutny, cgroups; +Cc: Tao Cui
In-Reply-To: <20260513104956.373216-1-cuitao@kylinos.cn>
rdma.peak tracks the high watermark of resource usage per device,
giving a better baseline on which to set rdma.max. Polling
rdma.current isn't feasible since it would miss short-lived spikes.
This interface is analogous to memory.peak.
Signed-off-by: Tao Cui <cuitao@kylinos.cn>
---
kernel/cgroup/rdma.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/kernel/cgroup/rdma.c b/kernel/cgroup/rdma.c
index 3df7c38ce481..4e3bf0bade18 100644
--- a/kernel/cgroup/rdma.c
+++ b/kernel/cgroup/rdma.c
@@ -44,6 +44,7 @@ static LIST_HEAD(rdmacg_devices);
enum rdmacg_file_type {
RDMACG_RESOURCE_TYPE_MAX,
RDMACG_RESOURCE_TYPE_STAT,
+ RDMACG_RESOURCE_TYPE_PEAK,
};
/*
@@ -60,6 +61,7 @@ static char const *rdmacg_resource_names[] = {
struct rdmacg_resource {
int max;
int usage;
+ int peak;
};
/*
@@ -204,6 +206,17 @@ uncharge_cg_locked(struct rdma_cgroup *cg,
rpool->usage_sum--;
if (rpool->usage_sum == 0 &&
rpool->num_max_cnt == RDMACG_RESOURCE_MAX) {
+ int i;
+
+ /*
+ * Keep the rpool alive if any peak value is non-zero,
+ * so that rdma.peak persists as a historical high-
+ * watermark even after all resources are freed.
+ */
+ for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
+ if (rpool->resources[i].peak)
+ return;
+ }
/*
* No user of the rpool and all entries are set to max, so
* safe to delete this rpool.
@@ -310,6 +323,12 @@ int rdmacg_try_charge(struct rdma_cgroup **rdmacg,
}
}
}
+ /* Update peak only after all charges succeed */
+ for (p = cg; p; p = parent_rdmacg(p)) {
+ rpool = find_cg_rpool_locked(p, device);
+ if (rpool && rpool->resources[index].usage > rpool->resources[index].peak)
+ rpool->resources[index].peak = rpool->resources[index].usage;
+ }
mutex_unlock(&rdmacg_mutex);
*rdmacg = cg;
@@ -472,6 +491,12 @@ static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
if (rpool->usage_sum == 0 &&
rpool->num_max_cnt == RDMACG_RESOURCE_MAX) {
+ int i;
+
+ for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
+ if (rpool->resources[i].peak)
+ goto dev_err;
+ }
/*
* No user of the rpool and all entries are set to max, so
* safe to delete this rpool.
@@ -506,6 +531,8 @@ static void print_rpool_values(struct seq_file *sf,
value = rpool->resources[i].max;
else
value = S32_MAX;
+ } else if (sf_type == RDMACG_RESOURCE_TYPE_PEAK) {
+ value = rpool ? rpool->resources[i].peak : 0;
} else {
if (rpool)
value = rpool->resources[i].usage;
@@ -556,6 +583,12 @@ static struct cftype rdmacg_files[] = {
.private = RDMACG_RESOURCE_TYPE_STAT,
.flags = CFTYPE_NOT_ON_ROOT,
},
+ {
+ .name = "peak",
+ .seq_show = rdmacg_resource_read,
+ .private = RDMACG_RESOURCE_TYPE_PEAK,
+ .flags = CFTYPE_NOT_ON_ROOT,
+ },
{ } /* terminate */
};
@@ -575,6 +608,13 @@ rdmacg_css_alloc(struct cgroup_subsys_state *parent)
static void rdmacg_css_free(struct cgroup_subsys_state *css)
{
struct rdma_cgroup *cg = css_rdmacg(css);
+ struct rdmacg_resource_pool *rpool, *tmp;
+
+ /* Clean up rpools kept alive by non-zero peak values */
+ mutex_lock(&rdmacg_mutex);
+ list_for_each_entry_safe(rpool, tmp, &cg->rpools, cg_node)
+ free_cg_rpool_locked(rpool);
+ mutex_unlock(&rdmacg_mutex);
kfree(cg);
}
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/4] cgroup/rdma: add rdma.peak and rdma.events[.local]
From: Tao Cui @ 2026-05-13 10:49 UTC (permalink / raw)
To: tj, hannes, mkoutny, cgroups; +Cc: Tao Cui
Hi,
This is v2 of the RDMA cgroup observability series. Thanks to the
reviewers for the detailed feedback on v1.
This series adds new cgroup interface files to the RDMA controller
to improve observability of resource usage and limit enforcement:
- rdma.peak: per-device high watermark of resource usage
- rdma.events: hierarchical max and alloc_fail event counters
- rdma.events.local: per-cgroup local max and alloc_fail counters
rdma.peak tracks the historical high watermark so administrators can
determine a sensible rdma.max based on actual peak demand rather than
guesswork. This is directly analogous to memory.peak.
rdma.events and rdma.events.local provide per-device counters that
track how often resource limits block allocations, and can be monitored
via poll/epoll for real-time alerting. Both files expose the same
keys (max and alloc_fail); rdma.events aggregates hierarchically while
rdma.events.local shows per-cgroup values. This follows the
pids.events / pids.events.local design.
Patch overview:
Patch 1 introduces rdma.peak, adding a per-resource peak field to track
the high watermark of usage, updated only after a full hierarchical
charge succeeds, and extends rpool lifetime to preserve non-zero
peak values.
Patch 2 adds rdma.events, which introduces rdmacg_event_locked() to
propagate hierarchical max counters upward from the over-limit
cgroup, with poll/epoll notification via cgroup_file_notify().
Patch 3 adds rdma.events.local and hierarchical alloc_fail, extending
the event framework with per-cgroup local counters (local_max for
the over-limit cgroup, local_alloc_fail for the requesting cgroup)
and a hierarchical alloc_fail counter propagated from the requestor
upward.
Patch 4 documents all three new interface files in cgroup-v2.rst.
Tao Cui (4):
cgroup/rdma: add rdma.peak for per-device peak usage tracking
cgroup/rdma: add rdma.events to track resource limit exhaustion
cgroup/rdma: add rdma.events.local for per-cgroup allocation failure
attribution
cgroup/rdma: document rdma.peak, rdma.events and rdma.events.local
Documentation/admin-guide/cgroup-v2.rst | 54 +++++++
include/linux/cgroup_rdma.h | 4 +
kernel/cgroup/rdma.c | 180 ++++++++++++++++++++++++
3 files changed, 238 insertions(+)
---
Changes in v2:
- Fix peak updated before full hierarchical charge succeeds.
- Use find_cg_rpool_locked() to avoid creating spurious rpools.
- Replace atomic64_t with u64 + READ_ONCE (all under rdmacg_mutex).
- Use key=value output format, remove trailing spaces.
- Always list all devices, show zero for devices without an rpool.
- Extend rpool-free condition to preserve non-zero event counters.
- Rename "failcnt" to "alloc_fail" (cgroup v2 naming convention).
- Fix alloc_fail semantics: local to the requesting cgroup only.
- Add hierarchical alloc_fail to rdma.events for key consistency.
- Add documentation in Documentation/admin-guide/cgroup-v2.rst.
v1:
https://lore.kernel.org/all/20260512031719.273507-1-cuitao@kylinos.cn/
--
2.43.0
^ permalink raw reply
* [tj-cgroup:for-next] BUILD SUCCESS d3b0a7f21119f5a66cb76aa28fb8cc13206aaf7d
From: kernel test robot @ 2026-05-13 10:42 UTC (permalink / raw)
To: Tejun Heo; +Cc: cgroups
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-next
branch HEAD: d3b0a7f21119f5a66cb76aa28fb8cc13206aaf7d Merge branch 'for-7.2' into for-next
elapsed time: 1010m
configs tested: 191
configs skipped: 10
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc allyesconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc randconfig-001-20260513 gcc-14.3.0
arc randconfig-002-20260513 gcc-14.3.0
arm allnoconfig clang-23
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm allyesconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm randconfig-001-20260513 gcc-14.3.0
arm randconfig-002-20260513 gcc-14.3.0
arm randconfig-003-20260513 gcc-14.3.0
arm randconfig-004-20260513 gcc-14.3.0
arm64 allmodconfig clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260513 gcc-12.5.0
arm64 randconfig-002-20260513 gcc-12.5.0
arm64 randconfig-003-20260513 gcc-12.5.0
arm64 randconfig-004-20260513 gcc-12.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260513 gcc-12.5.0
csky randconfig-002-20260513 gcc-12.5.0
hexagon allmodconfig clang-17
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig clang-23
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260513 gcc-8.5.0
hexagon randconfig-002-20260513 gcc-8.5.0
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig gcc-14
i386 buildonly-randconfig-001-20260513 clang-20
i386 buildonly-randconfig-002-20260513 clang-20
i386 buildonly-randconfig-003-20260513 clang-20
i386 buildonly-randconfig-004-20260513 clang-20
i386 buildonly-randconfig-005-20260513 clang-20
i386 buildonly-randconfig-006-20260513 clang-20
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260513 clang-20
i386 randconfig-002-20260513 clang-20
i386 randconfig-003-20260513 clang-20
i386 randconfig-004-20260513 clang-20
i386 randconfig-005-20260513 clang-20
i386 randconfig-006-20260513 clang-20
i386 randconfig-007-20260513 clang-20
i386 randconfig-011-20260513 clang-20
i386 randconfig-012-20260513 clang-20
i386 randconfig-012-20260513 gcc-14
i386 randconfig-013-20260513 clang-20
i386 randconfig-014-20260513 clang-20
i386 randconfig-015-20260513 clang-20
i386 randconfig-016-20260513 clang-20
i386 randconfig-017-20260513 clang-20
loongarch allmodconfig clang-19
loongarch allmodconfig clang-23
loongarch allnoconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260513 gcc-8.5.0
loongarch randconfig-002-20260513 gcc-8.5.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k allyesconfig gcc-15.2.0
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 randconfig-001-20260513 gcc-8.5.0
nios2 randconfig-002-20260513 gcc-8.5.0
openrisc allmodconfig clang-23
openrisc allmodconfig gcc-15.2.0
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc allyesconfig gcc-15.2.0
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260513 gcc-8.5.0
parisc randconfig-002-20260513 gcc-8.5.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc mpc832x_rdb_defconfig gcc-15.2.0
powerpc mpc834x_itx_defconfig clang-16
powerpc rainier_defconfig gcc-15.2.0
powerpc randconfig-001-20260513 gcc-8.5.0
powerpc randconfig-002-20260513 gcc-8.5.0
powerpc64 randconfig-001-20260513 gcc-8.5.0
powerpc64 randconfig-002-20260513 gcc-8.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260513 gcc-15.2.0
riscv randconfig-002-20260513 gcc-15.2.0
s390 allmodconfig clang-18
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260513 gcc-15.2.0
s390 randconfig-002-20260513 gcc-15.2.0
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh allyesconfig gcc-15.2.0
sh defconfig gcc-14
sh randconfig-001-20260513 gcc-15.2.0
sh randconfig-002-20260513 gcc-15.2.0
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260513 gcc-11.5.0
sparc randconfig-002-20260513 gcc-11.5.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260513 gcc-11.5.0
sparc64 randconfig-002-20260513 gcc-11.5.0
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-14
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260513 gcc-11.5.0
um randconfig-002-20260513 gcc-11.5.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260513 gcc-12
x86_64 buildonly-randconfig-002-20260513 gcc-12
x86_64 buildonly-randconfig-003-20260513 gcc-12
x86_64 buildonly-randconfig-004-20260513 gcc-12
x86_64 buildonly-randconfig-005-20260513 gcc-12
x86_64 buildonly-randconfig-006-20260513 gcc-12
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-011-20260513 gcc-14
x86_64 randconfig-012-20260513 gcc-14
x86_64 randconfig-013-20260513 gcc-14
x86_64 randconfig-014-20260513 gcc-14
x86_64 randconfig-015-20260513 gcc-14
x86_64 randconfig-016-20260513 gcc-14
x86_64 randconfig-071-20260513 gcc-14
x86_64 randconfig-072-20260513 gcc-14
x86_64 randconfig-073-20260513 gcc-14
x86_64 randconfig-074-20260513 gcc-14
x86_64 randconfig-075-20260513 gcc-14
x86_64 randconfig-076-20260513 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
xtensa randconfig-001-20260513 gcc-11.5.0
xtensa randconfig-002-20260513 gcc-11.5.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox