[PATCH v3 3/3] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier
Jesse Taube
jtaubepe at redhat.com
Wed Aug 5 12:20:14 PDT 2026
On Fri, Jul 24, 2026 at 8:17 PM Andy Chiu <tchiu at tenstorrent.com> wrote:
>
> 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>
Tested-by: Jesse Taube <jtaubepe at redhat.com>
Tested with:
https://github.com/Mr-Bossman/dbtr-buildroot/tree/user_V_hwprobe
Makefile doesn't apply cleanly on 7.1, just modify it and run
`touch buildroot/output/build/linux-7.1/.stamp_patched`
and continue build.
Inside qemu run:
```sh
cd /usr/lib/kselftests/riscv
./ext-enabled
```
> ---
> .../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);
Can we print the error code here.
We should also say that you need to `cd /usr/lib/kselftests/riscv/` to
run this code
or use `argv[0]` to get the path of `WORKER`. That tripped me up when
testing and
is also an issue in `vstate_prctl`. Sidenote `vstate_prctl` seems to
panic in qemu.
> + _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");
IMO I think this should be an error. Or atleast the cases where
`VOFF_SETUP` is returned not by the sub-program.
> + 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)
> + */
Can we use the same macros for both
+/* 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 */
Thanks,
Jesse
> +
> +#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
>
>
> _______________________________________________
> linux-riscv mailing list
> linux-riscv at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-riscv
>
More information about the linux-riscv
mailing list