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


Reply via email to