Commit 59ef78d4 authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

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: default avatarAlexei Starovoitov <ast@kernel.org>
parents c390adfd 221b5e76
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
# 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                   (?)
+58 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
// 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 −0
Original line number Diff line number Diff line
// 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;
}
+32 −0
Original line number Diff line number Diff line
@@ -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();
Loading