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 <[email protected]> --- 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

