[PATCH bpf-next v2 5/6] selftests/bpf: Add __cpu_feature annotation for CPU-feature-gated tests
Leon Hwang
leon.hwang at linux.dev
Thu Feb 19 06:29:27 PST 2026
Add a new __cpu_feature("...") test annotation and parse it in
selftests/bpf test_loader.
Behavior:
- Annotation value is matched against CPU feature tokens from
/proc/cpuinfo (case-insensitive).
- Multiple __cpu_feature annotations can be specified for one test; all
required features must be present.
- If any required feature is missing, the test is skipped.
Limitation:
- __cpu_feature is evaluated per test function and is not scoped per
__arch_* block. A single test that combines multiple architectures
cannot express different per-arch feature requirements.
This lets JIT/disassembly-sensitive tests declare explicit CPU feature
requirements and avoid false failures on unsupported systems.
Signed-off-by: Leon Hwang <leon.hwang at linux.dev>
---
tools/testing/selftests/bpf/progs/bpf_misc.h | 7 +
tools/testing/selftests/bpf/test_loader.c | 150 +++++++++++++++++++
2 files changed, 157 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h
index c9bfbe1bafc1..75e66373a64d 100644
--- a/tools/testing/selftests/bpf/progs/bpf_misc.h
+++ b/tools/testing/selftests/bpf/progs/bpf_misc.h
@@ -126,6 +126,12 @@
* Several __arch_* annotations could be specified at once.
* When test case is not run on current arch it is marked as skipped.
* __caps_unpriv Specify the capabilities that should be set when running the test.
+ * __cpu_feature Specify required CPU feature for test execution.
+ * Multiple __cpu_feature annotations could be specified.
+ * Value must match a CPU feature token exposed by
+ * /proc/cpuinfo (case-insensitive).
+ * Can't be used together with multiple __arch_* tags.
+ * If any required feature is not present, test case is skipped.
*
* __linear_size Specify the size of the linear area of non-linear skbs, or
* 0 for linear skbs.
@@ -156,6 +162,7 @@
#define __arch_riscv64 __arch("RISCV64")
#define __arch_s390x __arch("s390x")
#define __caps_unpriv(caps) __attribute__((btf_decl_tag("comment:test_caps_unpriv=" EXPAND_QUOTE(caps))))
+#define __cpu_feature(feat) __attribute__((btf_decl_tag("comment:test_cpu_feature=" feat)))
#define __load_if_JITed() __attribute__((btf_decl_tag("comment:load_mode=jited")))
#define __load_if_no_JITed() __attribute__((btf_decl_tag("comment:load_mode=no_jited")))
#define __stderr(msg) __attribute__((btf_decl_tag("comment:test_expect_stderr=" XSTR(__COUNTER__) "=" msg)))
diff --git a/tools/testing/selftests/bpf/test_loader.c b/tools/testing/selftests/bpf/test_loader.c
index 338c035c3688..3729d1572589 100644
--- a/tools/testing/selftests/bpf/test_loader.c
+++ b/tools/testing/selftests/bpf/test_loader.c
@@ -4,6 +4,7 @@
#include <stdlib.h>
#include <test_progs.h>
#include <bpf/btf.h>
+#include <ctype.h>
#include "autoconf_helper.h"
#include "disasm_helpers.h"
@@ -44,6 +45,7 @@
#define TEST_TAG_EXPECT_STDOUT_PFX "comment:test_expect_stdout="
#define TEST_TAG_EXPECT_STDOUT_PFX_UNPRIV "comment:test_expect_stdout_unpriv="
#define TEST_TAG_LINEAR_SIZE "comment:test_linear_size="
+#define TEST_TAG_CPU_FEATURE_PFX "comment:test_cpu_feature="
/* Warning: duplicated in bpf_misc.h */
#define POINTER_VALUE 0xbadcafe
@@ -67,6 +69,11 @@ enum load_mode {
NO_JITED = 1 << 1,
};
+struct cpu_feature_set {
+ char **names;
+ size_t cnt;
+};
+
struct test_subspec {
char *name;
bool expect_failure;
@@ -93,6 +100,7 @@ struct test_spec {
int linear_sz;
bool auxiliary;
bool valid;
+ struct cpu_feature_set cpu_features;
};
static int tester_init(struct test_loader *tester)
@@ -145,6 +153,16 @@ static void free_test_spec(struct test_spec *spec)
free(spec->unpriv.name);
spec->priv.name = NULL;
spec->unpriv.name = NULL;
+
+ if (spec->cpu_features.names) {
+ size_t i;
+
+ for (i = 0; i < spec->cpu_features.cnt; i++)
+ free(spec->cpu_features.names[i]);
+ free(spec->cpu_features.names);
+ spec->cpu_features.names = NULL;
+ spec->cpu_features.cnt = 0;
+ }
}
/* Compiles regular expression matching pattern.
@@ -394,6 +412,122 @@ static int get_current_arch(void)
return ARCH_UNKNOWN;
}
+static int cpu_feature_set_add(struct cpu_feature_set *set, const char *name)
+{
+ char **tmp, *norm;
+ size_t i, len;
+
+ if (!name || !name[0]) {
+ PRINT_FAIL("bad cpu feature spec: empty string");
+ return -EINVAL;
+ }
+
+ len = strlen(name);
+ norm = malloc(len + 1);
+ if (!norm)
+ return -ENOMEM;
+
+ for (i = 0; i < len; i++) {
+ if (isspace(name[i])) {
+ free(norm);
+ PRINT_FAIL("bad cpu feature spec: whitespace is not allowed in '%s'", name);
+ return -EINVAL;
+ }
+ norm[i] = tolower((unsigned char)name[i]);
+ }
+ norm[len] = '\0';
+
+ for (i = 0; i < set->cnt; i++) {
+ if (strcmp(set->names[i], norm) == 0) {
+ free(norm);
+ return 0;
+ }
+ }
+
+ tmp = realloc(set->names, (set->cnt + 1) * sizeof(*set->names));
+ if (!tmp) {
+ free(norm);
+ return -ENOMEM;
+ }
+ set->names = tmp;
+ set->names[set->cnt++] = norm;
+ return 0;
+}
+
+static bool cpu_feature_set_has(const struct cpu_feature_set *set, const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < set->cnt; i++) {
+ if (strcmp(set->names[i], name) == 0)
+ return true;
+ }
+ return false;
+}
+
+static bool cpu_feature_set_includes(const struct cpu_feature_set *have,
+ const struct cpu_feature_set *need)
+{
+ size_t i;
+
+ for (i = 0; i < need->cnt; i++) {
+ if (!cpu_feature_set_has(have, need->names[i]))
+ return false;
+ }
+ return true;
+}
+
+static const struct cpu_feature_set *get_current_cpu_features(void)
+{
+ static struct cpu_feature_set set;
+ static bool initialized;
+ char *line = NULL;
+ size_t len = 0;
+ FILE *fp;
+ int err;
+
+ if (initialized)
+ return &set;
+
+ initialized = true;
+ fp = fopen("/proc/cpuinfo", "r");
+ if (!fp)
+ return &set;
+
+ while (getline(&line, &len, fp) != -1) {
+ char *p = line, *colon, *tok;
+
+ while (*p && isspace(*p))
+ p++;
+ if (!str_has_pfx(p, "flags") &&
+ !str_has_pfx(p, "Features") &&
+ !str_has_pfx(p, "features"))
+ continue;
+
+ colon = strchr(p, ':');
+ if (!colon)
+ continue;
+
+ for (tok = strtok(colon + 1, " \t\n"); tok; tok = strtok(NULL, " \t\n")) {
+ err = cpu_feature_set_add(&set, tok);
+ if (err) {
+ PRINT_FAIL("failed to parse cpu feature from '/proc/cpuinfo': '%s'",
+ tok);
+ break;
+ }
+ }
+ }
+
+ free(line);
+ fclose(fp);
+ return &set;
+}
+
+static int parse_cpu_feature(const char *name, struct cpu_feature_set *set)
+{
+ return cpu_feature_set_add(set, name);
+}
+
/* Uses btf_decl_tag attributes to describe the expected test
* behavior, see bpf_misc.h for detailed description of each attribute
* and attribute combinations.
@@ -650,9 +784,20 @@ static int parse_test_spec(struct test_loader *tester,
err = -EINVAL;
goto cleanup;
}
+ } else if (str_has_pfx(s, TEST_TAG_CPU_FEATURE_PFX)) {
+ val = s + sizeof(TEST_TAG_CPU_FEATURE_PFX) - 1;
+ err = parse_cpu_feature(val, &spec->cpu_features);
+ if (err)
+ goto cleanup;
}
}
+ if (spec->cpu_features.cnt && __builtin_popcount(arch_mask) != 1) {
+ PRINT_FAIL("__cpu_feature requires exactly one __arch_* tag");
+ err = -EINVAL;
+ goto cleanup;
+ }
+
spec->arch_mask = arch_mask ?: -1;
spec->load_mask = load_mask ?: (JITED | NO_JITED);
@@ -1161,6 +1306,11 @@ void run_subtest(struct test_loader *tester,
return;
}
+ if (!cpu_feature_set_includes(get_current_cpu_features(), &spec->cpu_features)) {
+ test__skip();
+ return;
+ }
+
if (unpriv) {
if (!can_execute_unpriv(tester, spec)) {
test__skip();
--
2.52.0
More information about the linux-arm-kernel
mailing list