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