mirror of
https://github.com/torvalds/linux.git
synced 2026-05-28 00:53:34 +02:00
Merge branch 'selftests-bpf-introduce-execution-context-detection-helpers'
Changwoo Min says: ==================== selftests/bpf: Introduce execution context detection helpers This series introduces four new BPF-native inline helpers -- bpf_in_nmi(), bpf_in_hardirq(), bpf_in_serving_softirq(), and bpf_in_task() -- to allow BPF programs to query the current execution context. Following the feedback on v1, these are implemented in bpf_experimental.h as inline helpers wrapping get_preempt_count(). This approach allows the logic to be JIT-inlined for better performance compared to a kfunc call, while providing the granular context detection (e.g., hardirq vs. softirq) required by subsystems like sched_ext. The series includes a new selftest suite, exe_ctx, which uses bpf_testmod to verify context detection across Task, HardIRQ, and SoftIRQ boundaries via irq_work and tasklets. NMI context testing is omitted as NMIs cannot be triggered deterministically within software-only BPF CI environments. ChangeLog v2 -> v3: - Added exe_ctx to DENYLIST.s390x since new helpers are supported only on x86 and arm64 (patch 2). - Added comments to helpers describing supported architectures (patch 1). ChangeLog v1 -> v2: - Dropped the core kernel kfunc implementations, and implemented context detection as inline BPF helpers in bpf_experimental.h. - Renamed the selftest suite from ctx_kfunc to exe_ctx to reflect the change from kfuncs to helpers. - Updated BPF programs to use the new inline helpers. - Swapped clean-up order between tasklet and irqwork in bpf_testmod to avoid re-scheduling the already-killed tasklet (reported by bot+bpf-ci). ==================== Link: https://patch.msgid.link/20260125115413.117502-1-changwoo@igalia.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
59ef78d403
|
|
@ -1,4 +1,5 @@
|
|||
# TEMPORARY
|
||||
# Alphabetical order
|
||||
exe_ctx # execution context check (e.g., hardirq, softirq, etc)
|
||||
get_stack_raw_tp # user_stack corrupted user stack (no backchain userspace)
|
||||
stacktrace_build_id # compare_map_keys stackid_hmap vs. stackmap err -2 errno 2 (?)
|
||||
|
|
|
|||
|
|
@ -610,6 +610,8 @@ extern int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__str,
|
|||
#define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT)
|
||||
#define NMI_MASK (__IRQ_MASK(NMI_BITS) << NMI_SHIFT)
|
||||
|
||||
#define SOFTIRQ_OFFSET (1UL << SOFTIRQ_SHIFT)
|
||||
|
||||
extern bool CONFIG_PREEMPT_RT __kconfig __weak;
|
||||
#ifdef bpf_target_x86
|
||||
extern const int __preempt_count __ksym;
|
||||
|
|
@ -648,4 +650,60 @@ static inline int bpf_in_interrupt(void)
|
|||
(tsk->softirq_disable_cnt & SOFTIRQ_MASK);
|
||||
}
|
||||
|
||||
/* Description
|
||||
* Report whether it is in NMI context. Only works on the following archs:
|
||||
* * x86
|
||||
* * arm64
|
||||
*/
|
||||
static inline int bpf_in_nmi(void)
|
||||
{
|
||||
return get_preempt_count() & NMI_MASK;
|
||||
}
|
||||
|
||||
/* Description
|
||||
* Report whether it is in hard IRQ context. Only works on the following archs:
|
||||
* * x86
|
||||
* * arm64
|
||||
*/
|
||||
static inline int bpf_in_hardirq(void)
|
||||
{
|
||||
return get_preempt_count() & HARDIRQ_MASK;
|
||||
}
|
||||
|
||||
/* Description
|
||||
* Report whether it is in softirq context. Only works on the following archs:
|
||||
* * x86
|
||||
* * arm64
|
||||
*/
|
||||
static inline int bpf_in_serving_softirq(void)
|
||||
{
|
||||
struct task_struct___preempt_rt *tsk;
|
||||
int pcnt;
|
||||
|
||||
pcnt = get_preempt_count();
|
||||
if (!CONFIG_PREEMPT_RT)
|
||||
return (pcnt & SOFTIRQ_MASK) & SOFTIRQ_OFFSET;
|
||||
|
||||
tsk = (void *) bpf_get_current_task_btf();
|
||||
return (tsk->softirq_disable_cnt & SOFTIRQ_MASK) & SOFTIRQ_OFFSET;
|
||||
}
|
||||
|
||||
/* Description
|
||||
* Report whether it is in task context. Only works on the following archs:
|
||||
* * x86
|
||||
* * arm64
|
||||
*/
|
||||
static inline int bpf_in_task(void)
|
||||
{
|
||||
struct task_struct___preempt_rt *tsk;
|
||||
int pcnt;
|
||||
|
||||
pcnt = get_preempt_count();
|
||||
if (!CONFIG_PREEMPT_RT)
|
||||
return !(pcnt & (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET));
|
||||
|
||||
tsk = (void *) bpf_get_current_task_btf();
|
||||
return !((pcnt & (NMI_MASK | HARDIRQ_MASK)) |
|
||||
((tsk->softirq_disable_cnt & SOFTIRQ_MASK) & SOFTIRQ_OFFSET));
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
59
tools/testing/selftests/bpf/prog_tests/exe_ctx.c
Normal file
59
tools/testing/selftests/bpf/prog_tests/exe_ctx.c
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2026 Valve Corporation.
|
||||
* Author: Changwoo Min <changwoo@igalia.com>
|
||||
*/
|
||||
|
||||
#include <test_progs.h>
|
||||
#include <sys/syscall.h>
|
||||
#include "test_ctx.skel.h"
|
||||
|
||||
void test_exe_ctx(void)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_test_run_opts, opts);
|
||||
cpu_set_t old_cpuset, target_cpuset;
|
||||
struct test_ctx *skel;
|
||||
int err, prog_fd;
|
||||
|
||||
/* 1. Pin the current process to CPU 0. */
|
||||
if (sched_getaffinity(0, sizeof(old_cpuset), &old_cpuset) == 0) {
|
||||
CPU_ZERO(&target_cpuset);
|
||||
CPU_SET(0, &target_cpuset);
|
||||
ASSERT_OK(sched_setaffinity(0, sizeof(target_cpuset),
|
||||
&target_cpuset), "setaffinity");
|
||||
}
|
||||
|
||||
skel = test_ctx__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_load"))
|
||||
goto restore_affinity;
|
||||
|
||||
err = test_ctx__attach(skel);
|
||||
if (!ASSERT_OK(err, "skel_attach"))
|
||||
goto cleanup;
|
||||
|
||||
/* 2. When we run this, the kernel will execute the BPF prog on CPU 0. */
|
||||
prog_fd = bpf_program__fd(skel->progs.trigger_all_contexts);
|
||||
err = bpf_prog_test_run_opts(prog_fd, &opts);
|
||||
ASSERT_OK(err, "test_run_trigger");
|
||||
|
||||
/* 3. Wait for the local CPU's softirq/tasklet to finish. */
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
if (skel->bss->count_task > 0 &&
|
||||
skel->bss->count_hardirq > 0 &&
|
||||
skel->bss->count_softirq > 0)
|
||||
break;
|
||||
usleep(1000); /* Wait 1ms per iteration, up to 1 sec total */
|
||||
}
|
||||
|
||||
/* On CPU 0, these should now all be non-zero. */
|
||||
ASSERT_GT(skel->bss->count_task, 0, "task_ok");
|
||||
ASSERT_GT(skel->bss->count_hardirq, 0, "hardirq_ok");
|
||||
ASSERT_GT(skel->bss->count_softirq, 0, "softirq_ok");
|
||||
|
||||
cleanup:
|
||||
test_ctx__destroy(skel);
|
||||
|
||||
restore_affinity:
|
||||
ASSERT_OK(sched_setaffinity(0, sizeof(old_cpuset), &old_cpuset),
|
||||
"restore_affinity");
|
||||
}
|
||||
48
tools/testing/selftests/bpf/progs/test_ctx.c
Normal file
48
tools/testing/selftests/bpf/progs/test_ctx.c
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2026 Valve Corporation.
|
||||
* Author: Changwoo Min <changwoo@igalia.com>
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "bpf_experimental.h"
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
extern void bpf_kfunc_trigger_ctx_check(void) __ksym;
|
||||
|
||||
int count_hardirq;
|
||||
int count_softirq;
|
||||
int count_task;
|
||||
|
||||
/* Triggered via bpf_prog_test_run from user-space */
|
||||
SEC("syscall")
|
||||
int trigger_all_contexts(void *ctx)
|
||||
{
|
||||
if (bpf_in_task())
|
||||
__sync_fetch_and_add(&count_task, 1);
|
||||
|
||||
/* Trigger the firing of a hardirq and softirq for test. */
|
||||
bpf_kfunc_trigger_ctx_check();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Observer for HardIRQ */
|
||||
SEC("fentry/bpf_testmod_test_hardirq_fn")
|
||||
int BPF_PROG(on_hardirq)
|
||||
{
|
||||
if (bpf_in_hardirq())
|
||||
__sync_fetch_and_add(&count_hardirq, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Observer for SoftIRQ */
|
||||
SEC("fentry/bpf_testmod_test_softirq_fn")
|
||||
int BPF_PROG(on_softirq)
|
||||
{
|
||||
if (bpf_in_serving_softirq())
|
||||
__sync_fetch_and_add(&count_softirq, 1);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1168,6 +1168,33 @@ __bpf_kfunc int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux);
|
|||
__bpf_kfunc int bpf_kfunc_implicit_arg_legacy(int a, int b, struct bpf_prog_aux *aux);
|
||||
__bpf_kfunc int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux);
|
||||
|
||||
/* hook targets */
|
||||
noinline void bpf_testmod_test_hardirq_fn(void) { barrier(); }
|
||||
noinline void bpf_testmod_test_softirq_fn(void) { barrier(); }
|
||||
|
||||
/* Tasklet for SoftIRQ context */
|
||||
static void ctx_check_tasklet_fn(struct tasklet_struct *t)
|
||||
{
|
||||
bpf_testmod_test_softirq_fn();
|
||||
}
|
||||
|
||||
DECLARE_TASKLET(ctx_check_tasklet, ctx_check_tasklet_fn);
|
||||
|
||||
/* IRQ Work for HardIRQ context */
|
||||
static void ctx_check_irq_fn(struct irq_work *work)
|
||||
{
|
||||
bpf_testmod_test_hardirq_fn();
|
||||
tasklet_schedule(&ctx_check_tasklet);
|
||||
}
|
||||
|
||||
static struct irq_work ctx_check_irq = IRQ_WORK_INIT_HARD(ctx_check_irq_fn);
|
||||
|
||||
/* The kfunc trigger */
|
||||
__bpf_kfunc void bpf_kfunc_trigger_ctx_check(void)
|
||||
{
|
||||
irq_work_queue(&ctx_check_irq);
|
||||
}
|
||||
|
||||
BTF_KFUNCS_START(bpf_testmod_check_kfunc_ids)
|
||||
BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_test1)
|
||||
|
|
@ -1213,6 +1240,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1_assoc, KF_IMPLICIT_ARGS)
|
|||
BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg, KF_IMPLICIT_ARGS)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy, KF_IMPLICIT_ARGS)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy_impl)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_trigger_ctx_check)
|
||||
BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids)
|
||||
|
||||
static int bpf_testmod_ops_init(struct btf *btf)
|
||||
|
|
@ -1844,6 +1872,10 @@ static void bpf_testmod_exit(void)
|
|||
while (refcount_read(&prog_test_struct.cnt) > 1)
|
||||
msleep(20);
|
||||
|
||||
/* Clean up irqwork and tasklet */
|
||||
irq_work_sync(&ctx_check_irq);
|
||||
tasklet_kill(&ctx_check_tasklet);
|
||||
|
||||
bpf_kfunc_close_sock();
|
||||
sysfs_remove_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
|
||||
unregister_bpf_testmod_uprobe();
|
||||
|
|
|
|||
|
|
@ -169,4 +169,8 @@ extern int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args) __weak
|
|||
struct prog_test_member *bpf_kfunc_get_default_trusted_ptr_test(void) __ksym;
|
||||
void bpf_kfunc_put_default_trusted_ptr_test(struct prog_test_member *trusted_ptr) __ksym;
|
||||
|
||||
void bpf_testmod_test_hardirq_fn(void);
|
||||
void bpf_testmod_test_softirq_fn(void);
|
||||
void bpf_kfunc_trigger_ctx_check(void) __ksym;
|
||||
|
||||
#endif /* _BPF_TESTMOD_KFUNC_H */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user