[PATCH v3 3/3] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier
Andy Chiu
tchiu at tenstorrent.com
Fri Jul 24 17:15:52 PDT 2026
Add a selftest for the RISCV_HWPROBE_KEY_EXT_ENABLED modifier key, which
reports the extensions that are not only present in hardware but also
enabled for the calling process.
The modifier is positional within a single hwprobe request: keys placed
before it report what is present in hardware, keys placed after it report
what is present and enabled. A single query therefore returns both views:
[ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ]
present modifier enabled
The test uses this to check that Vector is masked out of the enabled view
once V is disabled for the process, while remaining in the present view.
The disabled-V check depends on user space being allowed to run vector
instructions by default (/proc/sys/abi/riscv_v_default_allow == 1). The
checks that require the modifier or V in hardware are skipped when either
is unavailable.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Andy Chiu <tchiu at tenstorrent.com>
---
.../selftests/riscv/hwprobe/.gitignore | 2 +
.../testing/selftests/riscv/hwprobe/Makefile | 14 ++-
.../selftests/riscv/hwprobe/ext-enabled.c | 111 ++++++++++++++++++
.../riscv/hwprobe/ext-enabled_nolibc.c | 58 +++++++++
4 files changed, 181 insertions(+), 4 deletions(-)
create mode 100644 tools/testing/selftests/riscv/hwprobe/ext-enabled.c
create mode 100644 tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c
diff --git a/tools/testing/selftests/riscv/hwprobe/.gitignore b/tools/testing/selftests/riscv/hwprobe/.gitignore
index 6e384e80ea1a..ceb4bf86340c 100644
--- a/tools/testing/selftests/riscv/hwprobe/.gitignore
+++ b/tools/testing/selftests/riscv/hwprobe/.gitignore
@@ -1,3 +1,5 @@
hwprobe
cbo
which-cpus
+ext-enabled
+ext-enabled_nolibc
diff --git a/tools/testing/selftests/riscv/hwprobe/Makefile b/tools/testing/selftests/riscv/hwprobe/Makefile
index 71e3f26c541b..55be82758830 100644
--- a/tools/testing/selftests/riscv/hwprobe/Makefile
+++ b/tools/testing/selftests/riscv/hwprobe/Makefile
@@ -2,9 +2,8 @@
# Copyright (C) 2021 ARM Limited
# Originally tools/testing/arm64/abi/Makefile
-CFLAGS += -I$(top_srcdir)/tools/include
-
-TEST_GEN_PROGS := hwprobe cbo which-cpus
+TEST_GEN_PROGS := hwprobe cbo which-cpus ext-enabled
+TEST_GEN_PROGS_EXTENDED := ext-enabled_nolibc
include ../../lib.mk
@@ -12,7 +11,14 @@ $(OUTPUT)/hwprobe: hwprobe.c sys_hwprobe.S
$(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
$(OUTPUT)/cbo: cbo.c sys_hwprobe.S
- $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
+ $(CC) -static -o $@ $(CFLAGS) -I$(top_srcdir)/tools/include $(LDFLAGS) $^
$(OUTPUT)/which-cpus: which-cpus.c sys_hwprobe.S
$(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
+
+$(OUTPUT)/ext-enabled: ext-enabled.c sys_hwprobe.S
+ $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
+
+$(OUTPUT)/ext-enabled_nolibc: ext-enabled_nolibc.c
+ $(CC) -nostdlib -static -include ../../../../include/nolibc/nolibc.h \
+ -Wall $(CFLAGS) $(LDFLAGS) $^ -o $@ -lgcc
diff --git a/tools/testing/selftests/riscv/hwprobe/ext-enabled.c b/tools/testing/selftests/riscv/hwprobe/ext-enabled.c
new file mode 100644
index 000000000000..887820911629
--- /dev/null
+++ b/tools/testing/selftests/riscv/hwprobe/ext-enabled.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test the RISCV_HWPROBE_KEY_EXT_ENABLED positional modifier.
+ *
+ * The modifier is a one-way switch within a single hwprobe request: keys
+ * before it report what is present in hardware, keys after it report what is
+ * also enabled for the calling process. A single query returns both views:
+ *
+ * [ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ]
+ * present modifier enabled
+ *
+ * When V is disabled for the process it must drop out of the enabled view but
+ * stay in the present view. V cannot be turned off for a thread that already
+ * has it on (prctl returns -EPERM); the NEXT control only takes effect across
+ * execve(). So the disabled-V case runs in a forked child that sets NEXT=off
+ * and execs a nolibc worker (built without libc so no vector-optimized libc
+ * routine can SIGILL while V is off). The worker reports back via exit code.
+ */
+#include <unistd.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include "hwprobe.h"
+#include "kselftest.h"
+
+#ifndef PR_RISCV_V_SET_CONTROL
+#define PR_RISCV_V_SET_CONTROL 69
+#define PR_RISCV_V_VSTATE_CTRL_OFF 1
+#endif
+#define VSTATE_CTRL_NEXT_SHIFT 2
+
+#define WORKER "./ext-enabled_nolibc"
+
+/* Verdicts returned by the worker. */
+#define VOFF_OK 0 /* present keeps V, enabled masks it */
+#define VOFF_BUG 1 /* enabled view still reported V */
+#define VOFF_SETUP 2 /* could not disable V / probe failed */
+
+/* Run the canonical present/enabled query; returns the raw syscall result. */
+static long query(__u64 *present, __u64 *enabled, __s64 *mod_key)
+{
+ struct riscv_hwprobe pairs[3] = {
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, },
+ { .key = RISCV_HWPROBE_KEY_EXT_ENABLED, },
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, },
+ };
+ long ret = riscv_hwprobe(pairs, 3, 0, NULL, 0);
+
+ *present = pairs[0].value;
+ *mod_key = pairs[1].key;
+ *enabled = pairs[2].value;
+ return ret;
+}
+
+/* Fork a child that disables V across execve, and return the worker verdict. */
+static int probe_with_v_disabled(void)
+{
+ char * const argv[] = { WORKER, NULL };
+ char * const envp[] = { NULL };
+ int status;
+ pid_t pid = fork();
+
+ if (pid < 0)
+ return VOFF_SETUP;
+ if (pid == 0) {
+ /* Disable V for the next execve of this child. */
+ prctl(PR_RISCV_V_SET_CONTROL,
+ PR_RISCV_V_VSTATE_CTRL_OFF << VSTATE_CTRL_NEXT_SHIFT);
+ execve(WORKER, argv, envp);
+ _exit(VOFF_SETUP); /* execve failed */
+ }
+ if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status))
+ return VOFF_SETUP;
+ return WEXITSTATUS(status);
+}
+
+/*
+ * This test assumes the user space allow executing vector instruction as a
+ * default behavior. Namely, /proc/sys/abi/riscv_v_default_allow is 1
+ */
+int main(void)
+{
+ __u64 present = 0, enabled = 0;
+ __s64 mod_key = 0;
+ int verdict;
+
+ ksft_print_header();
+ ksft_set_plan(2);
+
+ if (query(&present, &enabled, &mod_key))
+ ksft_exit_fail_msg("hwprobe() failed\n");
+
+ ksft_test_result(mod_key == RISCV_HWPROBE_KEY_EXT_ENABLED,
+ "RISCV_HWPROBE_KEY_EXT_ENABLED is recognized\n");
+
+ /* The remaining checks need both the modifier and V in hardware. */
+ if (mod_key != RISCV_HWPROBE_KEY_EXT_ENABLED ||
+ !(enabled & RISCV_HWPROBE_IMA_V)) {
+ ksft_test_result_skip("V off: present keeps V, enabled masks it\n");
+ ksft_finished();
+ }
+
+ /* V off (only reachable across execve): enabled view masks it. */
+ verdict = probe_with_v_disabled();
+ if (verdict == VOFF_SETUP)
+ ksft_test_result_skip("V off: present keeps V, but could not disable V\n");
+ else
+ ksft_test_result(verdict == VOFF_OK,
+ "V off: present keeps V, enabled masks it\n");
+
+ ksft_finished();
+}
diff --git a/tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c b/tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c
new file mode 100644
index 000000000000..d7f0a3c8314d
--- /dev/null
+++ b/tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nolibc worker for the RISCV_HWPROBE_KEY_EXT_ENABLED test.
+ *
+ * ext-enabled execs this program with V disabled for the process. It is built
+ * with nolibc on purpose: there is no libc here, so no vector-optimized libc
+ * routine can run and take a SIGILL while V is off. It probes the canonical
+ * [present, modifier, enabled] request and reports a verdict via exit code:
+ *
+ * 0 present keeps V, enabled masks it (correct)
+ * 1 enabled view still reports V (bug)
+ * 2 setup problem (V not off, or hwprobe failed)
+ */
+
+#ifndef __NR_riscv_hwprobe
+#define __NR_riscv_hwprobe 258
+#endif
+
+#ifndef PR_RISCV_V_GET_CONTROL
+#define PR_RISCV_V_GET_CONTROL 70
+#define PR_RISCV_V_VSTATE_CTRL_OFF 1
+#define PR_RISCV_V_VSTATE_CTRL_CUR_MASK 0x3
+#endif
+
+#define RISCV_HWPROBE_KEY_IMA_EXT_0 4
+#define RISCV_HWPROBE_KEY_EXT_ENABLED 17
+#define RISCV_HWPROBE_IMA_V (1 << 2)
+
+struct riscv_hwprobe {
+ long long key;
+ unsigned long long value;
+};
+
+int main(void)
+{
+ struct riscv_hwprobe pairs[3] = {
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0 },
+ { .key = RISCV_HWPROBE_KEY_EXT_ENABLED },
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0 },
+ };
+ long ctrl;
+
+ /* We must really be running with V turned off. */
+ ctrl = prctl(PR_RISCV_V_GET_CONTROL, 0, 0, 0, 0);
+ if (ctrl < 0 ||
+ (ctrl & PR_RISCV_V_VSTATE_CTRL_CUR_MASK) != PR_RISCV_V_VSTATE_CTRL_OFF)
+ return 2;
+
+ if (syscall(__NR_riscv_hwprobe, pairs, 3, 0, 0, 0))
+ return 2;
+
+ /* Present view must still carry V (it is in hardware). */
+ if (!(pairs[0].value & RISCV_HWPROBE_IMA_V))
+ return 2;
+
+ /* Enabled view must have masked V out. */
+ return (pairs[2].value & RISCV_HWPROBE_IMA_V) ? 1 : 0;
+}
--
2.43.0
More information about the linux-riscv
mailing list