[PATCH bpf-next v12 2/7] bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_array maps

Leon Hwang posted 7 patches 5 days, 7 hours ago
[PATCH bpf-next v12 2/7] bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_array maps
Posted by Leon Hwang 5 days, 7 hours ago
Introduce support for the BPF_F_ALL_CPUS flag in percpu_array maps to
allow updating values for all CPUs with a single value for both
update_elem and update_batch APIs.

Introduce support for the BPF_F_CPU flag in percpu_array maps to allow:

* update value for specified CPU for both update_elem and update_batch
APIs.
* lookup value for specified CPU for both lookup_elem and lookup_batch
APIs.

The BPF_F_CPU flag is passed via:

* map_flags of lookup_elem and update_elem APIs along with embedded cpu
info.
* elem_flags of lookup_batch and update_batch APIs along with embedded
cpu info.

Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
 include/linux/bpf.h   |  9 +++++++--
 kernel/bpf/arraymap.c | 29 +++++++++++++++++++++++------
 kernel/bpf/syscall.c  |  2 +-
 3 files changed, 31 insertions(+), 9 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index d84af3719b59..01a99e3a3e51 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -2762,7 +2762,7 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env,
 				   struct bpf_func_state *callee);
 
 int bpf_percpu_hash_copy(struct bpf_map *map, void *key, void *value);
-int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value);
+int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value, u64 flags);
 int bpf_percpu_hash_update(struct bpf_map *map, void *key, void *value,
 			   u64 flags);
 int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
@@ -3831,7 +3831,12 @@ bpf_prog_update_insn_ptrs(struct bpf_prog *prog, u32 *offsets, void *image)
 
 static inline bool bpf_map_supports_cpu_flags(enum bpf_map_type map_type)
 {
-	return false;
+	switch (map_type) {
+	case BPF_MAP_TYPE_PERCPU_ARRAY:
+		return true;
+	default:
+		return false;
+	}
 }
 
 static inline int bpf_map_check_op_flags(struct bpf_map *map, u64 flags, u64 allowed_flags)
diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index 1eeb31c5b317..67e9e811de3a 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -307,7 +307,7 @@ static void *percpu_array_map_lookup_percpu_elem(struct bpf_map *map, void *key,
 	return per_cpu_ptr(array->pptrs[index & array->index_mask], cpu);
 }
 
-int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value)
+int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value, u64 map_flags)
 {
 	struct bpf_array *array = container_of(map, struct bpf_array, map);
 	u32 index = *(u32 *)key;
@@ -325,11 +325,18 @@ int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value)
 	size = array->elem_size;
 	rcu_read_lock();
 	pptr = array->pptrs[index & array->index_mask];
+	if (map_flags & BPF_F_CPU) {
+		cpu = map_flags >> 32;
+		copy_map_value(map, value, per_cpu_ptr(pptr, cpu));
+		check_and_init_map_value(map, value);
+		goto unlock;
+	}
 	for_each_possible_cpu(cpu) {
 		copy_map_value_long(map, value + off, per_cpu_ptr(pptr, cpu));
 		check_and_init_map_value(map, value + off);
 		off += size;
 	}
+unlock:
 	rcu_read_unlock();
 	return 0;
 }
@@ -398,10 +405,11 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
 	struct bpf_array *array = container_of(map, struct bpf_array, map);
 	u32 index = *(u32 *)key;
 	void __percpu *pptr;
-	int cpu, off = 0;
+	void *ptr, *val;
 	u32 size;
+	int cpu;
 
-	if (unlikely(map_flags > BPF_EXIST))
+	if (unlikely((map_flags & BPF_F_LOCK) || (u32)map_flags > BPF_F_ALL_CPUS))
 		/* unknown flags */
 		return -EINVAL;
 
@@ -422,11 +430,20 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
 	size = array->elem_size;
 	rcu_read_lock();
 	pptr = array->pptrs[index & array->index_mask];
+	if (map_flags & BPF_F_CPU) {
+		cpu = map_flags >> 32;
+		ptr = per_cpu_ptr(pptr, cpu);
+		copy_map_value(map, ptr, value);
+		bpf_obj_free_fields(array->map.record, ptr);
+		goto unlock;
+	}
 	for_each_possible_cpu(cpu) {
-		copy_map_value_long(map, per_cpu_ptr(pptr, cpu), value + off);
-		bpf_obj_free_fields(array->map.record, per_cpu_ptr(pptr, cpu));
-		off += size;
+		ptr = per_cpu_ptr(pptr, cpu);
+		val = (map_flags & BPF_F_ALL_CPUS) ? value : value + size * cpu;
+		copy_map_value(map, ptr, val);
+		bpf_obj_free_fields(array->map.record, ptr);
 	}
+unlock:
 	rcu_read_unlock();
 	return 0;
 }
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 7a1d0ce8fa57..6aa98257fa3f 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -318,7 +318,7 @@ static int bpf_map_copy_value(struct bpf_map *map, void *key, void *value,
 	    map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH) {
 		err = bpf_percpu_hash_copy(map, key, value);
 	} else if (map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY) {
-		err = bpf_percpu_array_copy(map, key, value);
+		err = bpf_percpu_array_copy(map, key, value, flags);
 	} else if (map->map_type == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE) {
 		err = bpf_percpu_cgroup_storage_copy(map, key, value);
 	} else if (map->map_type == BPF_MAP_TYPE_STACK_TRACE) {
-- 
2.51.2
Re: [PATCH bpf-next v12 2/7] bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_array maps
Posted by bot+bpf-ci@kernel.org 5 days, 7 hours ago
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index d84af3719..01a99e3a3 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h

[ ... ]

> diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
> index 1eeb31c5b..67e9e811d 100644
> --- a/kernel/bpf/arraymap.c
> +++ b/kernel/bpf/arraymap.c

[ ... ]

> @@ -398,10 +405,11 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
>  	struct bpf_array *array = container_of(map, struct bpf_array, map);
>  	u32 index = *(u32 *)key;
>  	void __percpu *pptr;
> -	int cpu, off = 0;
> +	void *ptr, *val;
>  	u32 size;
> +	int cpu;
>
> -	if (unlikely(map_flags > BPF_EXIST))
> +	if (unlikely((map_flags & BPF_F_LOCK) || (u32)map_flags > BPF_F_ALL_CPUS))
>  		/* unknown flags */
>  		return -EINVAL;
>
> @@ -409,7 +417,7 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
>  		/* all elements were pre-allocated, cannot insert a new one */
>  		return -E2BIG;
>
> -	if (unlikely(map_flags & BPF_NOEXIST))
> +	if (unlikely(map_flags == BPF_NOEXIST))
                              ^^

Does changing this from bitwise-AND to equality break flag combination
handling? The old code checked (map_flags & BPF_NOEXIST), which would
trigger for any flag combination including BPF_NOEXIST. The new code only
checks (map_flags == BPF_NOEXIST), so if userspace passes
(BPF_F_CPU | BPF_NOEXIST), the BPF_NOEXIST flag gets silently ignored
instead of returning -EEXIST as before.

While all elements in percpu_array maps are pre-allocated and BPF_NOEXIST
should always fail anyway, silently ignoring a flag the user explicitly set
seems inconsistent with the principle of rejecting invalid flag combinations
at validation time.

>  		/* all elements already exist */
>  		return -EEXIST;
>

[ ... ]


---
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/19708012130
Re: [PATCH bpf-next v12 2/7] bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_array maps
Posted by Leon Hwang 5 days, 7 hours ago

On 2025/11/26 23:11, bot+bpf-ci@kernel.org wrote:
>> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
>> index d84af3719..01a99e3a3 100644
>> --- a/include/linux/bpf.h
>> +++ b/include/linux/bpf.h
> 
> [ ... ]
> 
>> diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
>> index 1eeb31c5b..67e9e811d 100644
>> --- a/kernel/bpf/arraymap.c
>> +++ b/kernel/bpf/arraymap.c
> 
> [ ... ]
> 
>> @@ -398,10 +405,11 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
>>  	struct bpf_array *array = container_of(map, struct bpf_array, map);
>>  	u32 index = *(u32 *)key;
>>  	void __percpu *pptr;
>> -	int cpu, off = 0;
>> +	void *ptr, *val;
>>  	u32 size;
>> +	int cpu;
>>
>> -	if (unlikely(map_flags > BPF_EXIST))
>> +	if (unlikely((map_flags & BPF_F_LOCK) || (u32)map_flags > BPF_F_ALL_CPUS))
>>  		/* unknown flags */
>>  		return -EINVAL;
>>
>> @@ -409,7 +417,7 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
>>  		/* all elements were pre-allocated, cannot insert a new one */
>>  		return -E2BIG;
>>
>> -	if (unlikely(map_flags & BPF_NOEXIST))
>> +	if (unlikely(map_flags == BPF_NOEXIST))
>                               ^^
> 

?

No such change in this version.

It seems that this change was inferred from v11 to v12 by AI itself.

Thanks,
Leon

> Does changing this from bitwise-AND to equality break flag combination
> handling? The old code checked (map_flags & BPF_NOEXIST), which would
> trigger for any flag combination including BPF_NOEXIST. The new code only
> checks (map_flags == BPF_NOEXIST), so if userspace passes
> (BPF_F_CPU | BPF_NOEXIST), the BPF_NOEXIST flag gets silently ignored
> instead of returning -EEXIST as before.
> 
> While all elements in percpu_array maps are pre-allocated and BPF_NOEXIST
> should always fail anyway, silently ignoring a flag the user explicitly set
> seems inconsistent with the principle of rejecting invalid flag combinations
> at validation time.
> 
>>  		/* all elements already exist */
>>  		return -EEXIST;
>>
> 
> [ ... ]
> 
> 
> ---
> 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/19708012130
Re: [PATCH bpf-next v12 2/7] bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_array maps
Posted by Chris Mason 5 days, 6 hours ago

On 11/26/25 10:24 AM, Leon Hwang wrote:
> 
> 
> On 2025/11/26 23:11, bot+bpf-ci@kernel.org wrote:
>>> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
>>> index d84af3719..01a99e3a3 100644
>>> --- a/include/linux/bpf.h
>>> +++ b/include/linux/bpf.h
>>
>> [ ... ]
>>
>>> diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
>>> index 1eeb31c5b..67e9e811d 100644
>>> --- a/kernel/bpf/arraymap.c
>>> +++ b/kernel/bpf/arraymap.c
>>
>> [ ... ]
>>
>>> @@ -398,10 +405,11 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
>>>  	struct bpf_array *array = container_of(map, struct bpf_array, map);
>>>  	u32 index = *(u32 *)key;
>>>  	void __percpu *pptr;
>>> -	int cpu, off = 0;
>>> +	void *ptr, *val;
>>>  	u32 size;
>>> +	int cpu;
>>>
>>> -	if (unlikely(map_flags > BPF_EXIST))
>>> +	if (unlikely((map_flags & BPF_F_LOCK) || (u32)map_flags > BPF_F_ALL_CPUS))
>>>  		/* unknown flags */
>>>  		return -EINVAL;
>>>
>>> @@ -409,7 +417,7 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
>>>  		/* all elements were pre-allocated, cannot insert a new one */
>>>  		return -E2BIG;
>>>
>>> -	if (unlikely(map_flags & BPF_NOEXIST))
>>> +	if (unlikely(map_flags == BPF_NOEXIST))
>>                               ^^
>>
> 
> ?
> 
> No such change in this version.
> 
> It seems that this change was inferred from v11 to v12 by AI itself

Thanks for flagging this, I'll try to find this section of the logs to
see how the false positive checks failed to catch it.

-chris
Re: [PATCH bpf-next v12 2/7] bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_array maps
Posted by Ihor Solodrai 5 days ago
On 11/26/25 7:56 AM, Chris Mason wrote:
> 
> 
> On 11/26/25 10:24 AM, Leon Hwang wrote:
>>
>>
>> On 2025/11/26 23:11, bot+bpf-ci@kernel.org wrote:
>>>> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
>>>> index d84af3719..01a99e3a3 100644
>>>> --- a/include/linux/bpf.h
>>>> +++ b/include/linux/bpf.h
>>>
>>> [ ... ]
>>>
>>>> diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
>>>> index 1eeb31c5b..67e9e811d 100644
>>>> --- a/kernel/bpf/arraymap.c
>>>> +++ b/kernel/bpf/arraymap.c
>>>
>>> [ ... ]
>>>
>>>> @@ -398,10 +405,11 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
>>>>  	struct bpf_array *array = container_of(map, struct bpf_array, map);
>>>>  	u32 index = *(u32 *)key;
>>>>  	void __percpu *pptr;
>>>> -	int cpu, off = 0;
>>>> +	void *ptr, *val;
>>>>  	u32 size;
>>>> +	int cpu;
>>>>
>>>> -	if (unlikely(map_flags > BPF_EXIST))
>>>> +	if (unlikely((map_flags & BPF_F_LOCK) || (u32)map_flags > BPF_F_ALL_CPUS))
>>>>  		/* unknown flags */
>>>>  		return -EINVAL;
>>>>
>>>> @@ -409,7 +417,7 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
>>>>  		/* all elements were pre-allocated, cannot insert a new one */
>>>>  		return -E2BIG;
>>>>
>>>> -	if (unlikely(map_flags & BPF_NOEXIST))
>>>> +	if (unlikely(map_flags == BPF_NOEXIST))
>>>                               ^^
>>>
>>
>> ?
>>
>> No such change in this version.
>>
>> It seems that this change was inferred from v11 to v12 by AI itself
> 
> Thanks for flagging this, I'll try to find this section of the logs to
> see how the false positive checks failed to catch it.
> 
> -chris

AI got confused here, this was not in the diff.

But it appears it got triggered, because there are these two code
fragments nearby [1]:


	if (unlikely(map_flags & BPF_NOEXIST))
		/* all elements already exist */
		return -EEXIST;

and

	if (unlikely(map_flags == BPF_NOEXIST))
		/* all elements already exist */
		return -EEXIST;

Which is a good thing to notice even if this is intentional.

Anyone knows if it is?

https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/kernel/bpf/arraymap.c#n356

>