On Tue, 1 Sept 2026 at 21:04, <[email protected]> wrote:
>
> > diff --git 
> > a/tools/testing/selftests/bpf/prog_tests/map_lookup_percpu_elem.c 
> > b/tools/testing/selftests/bpf/prog_tests/map_lookup_percpu_elem.c
> > index bfb1bf3fd427b..849f33259c003 100644
> > --- a/tools/testing/selftests/bpf/prog_tests/map_lookup_percpu_elem.c
> > +++ b/tools/testing/selftests/bpf/prog_tests/map_lookup_percpu_elem.c
> > @@ -2,29 +2,45 @@
> >  /* Copyright (c) 2022 Bytedance */
> >
> >  #include <test_progs.h>
> > +#include "bpf/libbpf_internal.h"
> >  #include "test_map_lookup_percpu_elem.skel.h"
> >
> >  void test_map_lookup_percpu_elem(void)
> >  {
> >       struct test_map_lookup_percpu_elem *skel;
> > -     __u64 key = 0, sum;
> > -     int ret, i, nr_cpus = libbpf_num_possible_cpus();
> > +     bool *possible = NULL;
> > +     __u64 key = 0, sum = 0;
> > +     int cpu, nr_cpu_ids, nr_cpus, ret, slot = 0;
> >       __u64 *buf;
> >
> > -     buf = malloc(nr_cpus*sizeof(__u64));
> > -     if (!ASSERT_OK_PTR(buf, "malloc"))
> > +     ret = parse_cpu_mask_file("/sys/devices/system/cpu/possible", 
> > &possible,
> > +                               &nr_cpu_ids);
> > +     if (!ASSERT_OK(ret, "parse possible CPU mask"))
> >               return;
> >
> > -     for (i = 0; i < nr_cpus; i++)
> > -             buf[i] = i;
> > -     sum = (nr_cpus - 1) * nr_cpus / 2;
> > +     nr_cpus = libbpf_num_possible_cpus();
> > +     if (!ASSERT_GT(nr_cpus, 0, "libbpf_num_possible_cpus"))
> > +             goto free_mask;
> > +
> > +     buf = malloc(nr_cpus * sizeof(*buf));
> > +     if (!ASSERT_OK_PTR(buf, "malloc"))
> > +             goto free_mask;
> > +
> > +     for (cpu = 0; cpu < nr_cpu_ids; cpu++) {
> > +             if (!possible[cpu])
> > +                     continue;
> > +             buf[slot++] = cpu;
> > +             sum += cpu;
> > +     }
>
> Does this buffer layout match how the kernel expects it?
>
> The userspace side fills buf[] with one entry per possible CPU, storing
> the logical CPU ID at packed index 'slot' (0, 1, 2, ...). For a mask
> like 0,2-3, this creates buf = {0, 2, 3} with 3 entries total.
>
> Looking at the kernel's update path, both percpu array and hash maps
> index the user buffer by logical CPU ID:
>
> kernel/bpf/arraymap.c:469 bpf_percpu_array_update()
>     for_each_possible_cpu(cpu) {
>             ptr = per_cpu_ptr(pptr, cpu);
>             val = (map_flags & BPF_F_ALL_CPUS) ? value : value + size * cpu;
>
> kernel/bpf/hashtab.c:1038 pcpu_copy_value()
>     for_each_possible_cpu(cpu) {
>             ...
>             copy_map_value_long(&htab->map, per_cpu_ptr(pptrs, cpu),
>                     value + off + size * cpu);
>
> With the mask 0,2-3 (num_possible_cpus() = 3, nr_cpu_ids = 4), the
> update walks possible CPUs {0, 2, 3} and indexes the buffer as:
>
>   cpu 0:  *(value + 0)  = buf[0] = 0   (intended)
>   cpu 2:  *(value + 16) = buf[2] = 3   (expects 2, gets 3)
>   cpu 3:  *(value + 24)             (past the 24-byte buffer end)
>
> The userspace layout is packed (3 entries), but the kernel accesses it
> as sparse (indexed by CPU ID). The test sized the buffer for
> num_possible_cpus() bytes, but the kernel's 'size * cpu' indexing can
> exceed that when the possible mask has holes.
>
> Should the userspace buffer either be laid out by CPU ID (sized for
> nr_cpu_ids, with holes at non-possible indices), or should the kernel's
> update path use packed indexing like the lookup direction does?
>
> > +     if (!ASSERT_EQ(slot, nr_cpus, "possible CPU mask weight"))
> > +             goto exit;
> >
> >       skel = test_map_lookup_percpu_elem__open();
> >       if (!ASSERT_OK_PTR(skel, "test_map_lookup_percpu_elem__open"))
> >               goto exit;
> >
> >       skel->rodata->my_pid = getpid();
> > -     skel->rodata->nr_cpus = nr_cpus;
> > +     skel->rodata->nr_cpu_ids = nr_cpu_ids;
> >
> >       ret = test_map_lookup_percpu_elem__load(skel);
>
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33521264909

For context on the review, there's a commit that's directly relevant
here but isn't in the mainline or bpf-next tree yet, so flagging it
explicitly:
Commit: 75b0a6db4300e4c2c9e97a0848deaa7acfb42fb7 ("bpf: Fix percpu map
update indexing with sparse CPU IDs")
That commit changes the array, hash, and cgroup-storage update paths
from value + size * cpu to a dense running offset, matching the lookup
side. With it, a possible mask of 0,2-3 maps the packed buffer as:
    slot 0 -> CPU 0
    slot 1 -> CPU 2
    slot 2 -> CPU 3
  I developed this selftest change on top of that commit but failed to
state the dependency.

Reply via email to