Re: [PATCH v3 3/3] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier

Jesse Taube <[email protected]> Wed, 5 Aug 2026 15:20:14 -0400
Newsgroups org.infradead.lists.linux-riscv,org.kernel.vger.linux-kselftest
Message-ID <CADRr4be7dwuMk2sxgXRgx89ph5uYNqveX0=H17j35+VBCJ10mw@mail.gmail.com>
On Fri, Jul 24, 2026 at 8:17 PM Andy Chiu <[email protected]> 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 <[email protected]>

Tested-by: Jesse Taube <[email protected]>

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
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-riscv
>


_______________________________________________
linux-riscv mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-riscv