selftests/bpf: Test per-cpu initialization of a BPF_F_CPU created element

The existing cpu_flag subtests always prime a key with BPF_F_ALL_CPUS
before any BPF_F_CPU write, so the create path is never covered.

Add a subtest that creates the element with BPF_F_CPU on a map with
max_entries 1, so the key can only reuse the element the previous key
released, and check that the CPUs the update did not name read back
zero.  Run it for PERCPU_HASH preallocated and BPF_F_NO_PREALLOC,
whose per-cpu areas come from different allocators, and for
LRU_PERCPU_HASH.

Under BPF_F_NO_PREALLOC the reuse is only guaranteed on the cpu that
ran the delete, so pin the thread across the pair, and name a cpu other
than that one in map_flags.

Signed-off-by: Donggeun Yoo <donggeunyoo.kernel@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20260924102321.2120434-3-donggeunyoo.kernel@gmail.com
This commit is contained in:
Donggeun Yoo 2026-09-24 19:23:21 +09:00 committed by Alexei Starovoitov
parent c3a66e5f5b
commit 3422808f4e
No known key found for this signature in database

View File

@ -1,4 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <sched.h>
#include <test_progs.h>
#include "cgroup_helpers.h"
#include "percpu_alloc_array.skel.h"
@ -350,6 +352,103 @@ static void test_lru_percpu_hash_cpu_flag(void)
test_percpu_map_cpu_flag(BPF_MAP_TYPE_LRU_PERCPU_HASH);
}
/*
* A BPF_F_CPU update that creates an element must zero the value on the other
* cpus, rather than leave them holding whatever the recycled element last
* contained. max_entries is 1 so the second key can only reuse the element
* the first one released.
*/
static void test_percpu_map_cpu_flag_create(enum bpf_map_type map_type, __u32 map_flags)
{
LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = map_flags);
const u32 stale = 0xDEADC0DE, fresh = 0xC0FFEE;
int nr_cpus, cpu, map_fd, err, key;
int pinned_cpu, value_cpu;
cpu_set_t old_mask, new_mask;
bool restore_mask = false;
u32 value;
u64 flags;
nr_cpus = libbpf_num_possible_cpus();
if (!ASSERT_GT(nr_cpus, 0, "libbpf_num_possible_cpus"))
return;
if (nr_cpus < 2) {
test__skip();
return;
}
map_fd = bpf_map_create(map_type, "cpu_flag_create", sizeof(key), sizeof(value), 1, &opts);
if (!ASSERT_GE(map_fd, 0, "bpf_map_create"))
return;
/* NO_PREALLOC recycles per cpu, so keep the delete and the create on one cpu. */
err = sched_getaffinity(0, sizeof(old_mask), &old_mask);
if (!ASSERT_OK(err, "sched_getaffinity"))
goto out;
pinned_cpu = sched_getcpu();
if (!ASSERT_GE(pinned_cpu, 0, "sched_getcpu"))
goto out;
CPU_ZERO(&new_mask);
CPU_SET(pinned_cpu, &new_mask);
err = sched_setaffinity(0, sizeof(new_mask), &new_mask);
if (!ASSERT_OK(err, "sched_setaffinity"))
goto out;
restore_mask = true;
value_cpu = pinned_cpu ? 0 : 1;
key = 1;
value = stale;
err = bpf_map_update_elem(map_fd, &key, &value, BPF_F_ALL_CPUS);
if (!ASSERT_OK(err, "bpf_map_update_elem all_cpus"))
goto out;
err = bpf_map_delete_elem(map_fd, &key);
if (!ASSERT_OK(err, "bpf_map_delete_elem"))
goto out;
key = 2;
value = fresh;
flags = (u64)value_cpu << 32 | BPF_F_CPU;
err = bpf_map_update_elem(map_fd, &key, &value, flags);
if (!ASSERT_OK(err, "bpf_map_update_elem specified cpu"))
goto out;
for (cpu = 0; cpu < nr_cpus; cpu++) {
value = 0;
flags = (u64)cpu << 32 | BPF_F_CPU;
err = bpf_map_lookup_elem_flags(map_fd, &key, &value, flags);
if (!ASSERT_OK(err, "bpf_map_lookup_elem_flags specified cpu"))
goto out;
if (!ASSERT_EQ(value, cpu == value_cpu ? fresh : 0, "value on specified cpu"))
goto out;
}
out:
if (restore_mask)
sched_setaffinity(0, sizeof(old_mask), &old_mask);
close(map_fd);
}
static void test_percpu_hash_cpu_flag_create(void)
{
test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, 0);
}
static void test_percpu_hash_cpu_flag_create_malloc(void)
{
test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, BPF_F_NO_PREALLOC);
}
static void test_lru_percpu_hash_cpu_flag_create(void)
{
/* lru without prealloc is -ENOTSUPP, so there is no malloc variant */
test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_LRU_PERCPU_HASH, 0);
}
static void test_percpu_cgroup_storage_cpu_flag(void)
{
struct percpu_alloc_array *skel = NULL;
@ -454,6 +553,12 @@ void test_percpu_alloc(void)
test_percpu_hash_cpu_flag();
if (test__start_subtest("cpu_flag_lru_percpu_hash"))
test_lru_percpu_hash_cpu_flag();
if (test__start_subtest("cpu_flag_create_percpu_hash"))
test_percpu_hash_cpu_flag_create();
if (test__start_subtest("cpu_flag_create_percpu_hash_malloc"))
test_percpu_hash_cpu_flag_create_malloc();
if (test__start_subtest("cpu_flag_create_lru_percpu_hash"))
test_lru_percpu_hash_cpu_flag_create();
if (test__start_subtest("cpu_flag_percpu_cgroup_storage"))
test_percpu_cgroup_storage_cpu_flag();
if (test__start_subtest("cpu_flag_array"))