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:
Alexei Starovoitov 2026-01-25 08:20:50 -08:00
commit 59ef78d403
6 changed files with 202 additions and 0 deletions

View File

@ -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 (?)

View File

@ -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

View 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");
}

View 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;
}

View File

@ -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();

View File

@ -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 */