* [PATCH v3 0/3] riscv: hwprobe: report per-process extension availability
@ 2026-07-25 0:15 ` Andy Chiu
0 siblings, 0 replies; 14+ messages in thread
From: Andy Chiu @ 2026-07-25 0:15 UTC (permalink / raw)
To: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Samuel Holland, linux-riscv
Cc: bergner, kito.cheng, Andy Chiu, dfustini, greentime.hu,
linux-kernel, olof
hwprobe reports whether an extension is present in hardware, not whether it
is usable by the calling process. Vector can be disabled per-process via
prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF), yet
RISCV_HWPROBE_KEY_IMA_EXT_0 still reports it. An IFUNC resolver that
dispatches to a vector routine on that basis then takes a SIGILL.
This series adds RISCV_HWPROBE_KEY_EXT_ENABLED, a positional modifier:
within one request, keys before it report present-in-hardware, keys after
it report present-and-enabled for the calling process. Both views come back
in a single call:
[ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ]
present modifier enabled
When V is disabled for the process, V and its V-dependent sub-extensions are
cleared from a following RISCV_HWPROBE_KEY_IMA_EXT_0, and XTHEADVECTOR is
cleared from a following RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0. The enabled
view is per-process, so requests carrying the modifier are deferred from the
vDSO to the syscall. Old kernels report the key as -1, so support is
detectable and existing users are unaffected.
The earlier documentation patch [1] clarified that userspace should discover
non-standard Vector extensions through hwprobe(2) and then use
prctl(PR_RISCV_V_GET_CONTROL) to check whether the process is actually
allowed to execute them. Requiring that separate prctl step is awkward for
the IFUNC-resolver environment, where a resolver wants to decide from the
single hwprobe query it already issues. This series folds the availability
check into hwprobe itself, so a resolver obtains present-and-enabled state
in one call.
Tested under QEMU (rv64, V enabled); the selftest passes. The disabled-V
case runs in a forked child that sets the NEXT control to off and execs a
nolibc worker, since V cannot be disabled in-place (-EPERM) and a
vector-using libc cannot run with V off. That check assumes
/proc/sys/abi/riscv_v_default_allow == 1 and skips otherwise.
[1] https://lore.kernel.org/all/20260107000609.63892-1-andybnac@gmail.com/
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220795
Patch summary:
- Modify patch: none
- Unchanged patch: 2, 3
- New patch: 1
Changelog v3:
- add a patch to fix hwprobe value leaking for vendor extensions on
vdso (Sashiko)
- Link to v2: https://lore.kernel.org/all/20260724183646.2363114-1-tchiu@tenstorrent.com/
Changelog v2:
- Rebase on top of the latest riscv for-next branch (patchwork ci)
- Hook test_avail into sifive's vendor vector extensions (Sashiko)
- Link to v1: https://lore.kernel.org/all/20260723222109.2229089-3-tchiu@tenstorrent.com/
Andy Chiu (3):
riscv: hwprobe: initialize pair->value in hwprobe_one_pair()
riscv: hwprobe: export the availability of vector to user
selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED
modifier
Documentation/arch/riscv/hwprobe.rst | 27 +++++
arch/riscv/include/asm/hwprobe.h | 2 +-
.../asm/vendor_extensions/sifive_hwprobe.h | 6 +-
.../asm/vendor_extensions/thead_hwprobe.h | 6 +-
arch/riscv/include/uapi/asm/hwprobe.h | 2 +
arch/riscv/kernel/sys_hwprobe.c | 41 ++++---
arch/riscv/kernel/vdso/hwprobe.c | 25 +++-
.../kernel/vendor_extensions/sifive_hwprobe.c | 16 ++-
.../kernel/vendor_extensions/thead_hwprobe.c | 9 +-
.../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 +++++++++
13 files changed, 283 insertions(+), 36 deletions(-)
create mode 100644 tools/testing/selftests/riscv/hwprobe/ext-enabled.c
create mode 100644 tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c
base-commit: 76e885f2b89d9e88291c1864e5f89a6b180768da
prerequisite-patch-id: ecac88d39cfd235fd3bd3be495124f8156f45c5f
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v3 0/3] riscv: hwprobe: report per-process extension availability @ 2026-07-25 0:15 ` Andy Chiu 0 siblings, 0 replies; 14+ messages in thread From: Andy Chiu @ 2026-07-25 0:15 UTC (permalink / raw) To: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Samuel Holland, linux-riscv Cc: bergner, kito.cheng, Andy Chiu, dfustini, greentime.hu, linux-kernel, olof hwprobe reports whether an extension is present in hardware, not whether it is usable by the calling process. Vector can be disabled per-process via prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF), yet RISCV_HWPROBE_KEY_IMA_EXT_0 still reports it. An IFUNC resolver that dispatches to a vector routine on that basis then takes a SIGILL. This series adds RISCV_HWPROBE_KEY_EXT_ENABLED, a positional modifier: within one request, keys before it report present-in-hardware, keys after it report present-and-enabled for the calling process. Both views come back in a single call: [ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ] present modifier enabled When V is disabled for the process, V and its V-dependent sub-extensions are cleared from a following RISCV_HWPROBE_KEY_IMA_EXT_0, and XTHEADVECTOR is cleared from a following RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0. The enabled view is per-process, so requests carrying the modifier are deferred from the vDSO to the syscall. Old kernels report the key as -1, so support is detectable and existing users are unaffected. The earlier documentation patch [1] clarified that userspace should discover non-standard Vector extensions through hwprobe(2) and then use prctl(PR_RISCV_V_GET_CONTROL) to check whether the process is actually allowed to execute them. Requiring that separate prctl step is awkward for the IFUNC-resolver environment, where a resolver wants to decide from the single hwprobe query it already issues. This series folds the availability check into hwprobe itself, so a resolver obtains present-and-enabled state in one call. Tested under QEMU (rv64, V enabled); the selftest passes. The disabled-V case runs in a forked child that sets the NEXT control to off and execs a nolibc worker, since V cannot be disabled in-place (-EPERM) and a vector-using libc cannot run with V off. That check assumes /proc/sys/abi/riscv_v_default_allow == 1 and skips otherwise. [1] https://lore.kernel.org/all/20260107000609.63892-1-andybnac@gmail.com/ Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220795 Patch summary: - Modify patch: none - Unchanged patch: 2, 3 - New patch: 1 Changelog v3: - add a patch to fix hwprobe value leaking for vendor extensions on vdso (Sashiko) - Link to v2: https://lore.kernel.org/all/20260724183646.2363114-1-tchiu@tenstorrent.com/ Changelog v2: - Rebase on top of the latest riscv for-next branch (patchwork ci) - Hook test_avail into sifive's vendor vector extensions (Sashiko) - Link to v1: https://lore.kernel.org/all/20260723222109.2229089-3-tchiu@tenstorrent.com/ Andy Chiu (3): riscv: hwprobe: initialize pair->value in hwprobe_one_pair() riscv: hwprobe: export the availability of vector to user selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier Documentation/arch/riscv/hwprobe.rst | 27 +++++ arch/riscv/include/asm/hwprobe.h | 2 +- .../asm/vendor_extensions/sifive_hwprobe.h | 6 +- .../asm/vendor_extensions/thead_hwprobe.h | 6 +- arch/riscv/include/uapi/asm/hwprobe.h | 2 + arch/riscv/kernel/sys_hwprobe.c | 41 ++++--- arch/riscv/kernel/vdso/hwprobe.c | 25 +++- .../kernel/vendor_extensions/sifive_hwprobe.c | 16 ++- .../kernel/vendor_extensions/thead_hwprobe.c | 9 +- .../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 +++++++++ 13 files changed, 283 insertions(+), 36 deletions(-) create mode 100644 tools/testing/selftests/riscv/hwprobe/ext-enabled.c create mode 100644 tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c base-commit: 76e885f2b89d9e88291c1864e5f89a6b180768da prerequisite-patch-id: ecac88d39cfd235fd3bd3be495124f8156f45c5f -- 2.43.0 ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 1/3] riscv: hwprobe: initialize pair->value in hwprobe_one_pair() 2026-07-25 0:15 ` Andy Chiu (?) @ 2026-07-25 0:15 ` Andy Chiu 2026-08-05 16:56 ` Jesse Taube -1 siblings, 1 reply; 14+ messages in thread From: Andy Chiu @ 2026-07-25 0:15 UTC (permalink / raw) To: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Evan Green, Charlie Jenkins, linux-riscv Cc: bergner, kito.cheng, Andy Chiu, dfustini, greentime.hu, Andrew Jones, Deepak Gupta, Xu Lu, Yao Zihong, Jingwei Wang, Clément Léger The vendor-extension handlers reached from hwprobe_one_pair() (hwprobe_isa_vendor_ext_thead_0() and friends) only OR the present bits into pair->value via VENDOR_EXTENSION_SUPPORTED() and clear their own missing bits; they assume the caller has already zeroed pair->value. That holds for hwprobe_get_values() (it zeroes each pair) and hwprobe_get_cpus() (it re-initializes its scratch pair per key), but not for complete_hwprobe_vdso_data(), which reuses a single pair across all keys without re-zeroing. A vendor key therefore inherits stale bits from the previously probed key, and the wrong value is cached in the vDSO all_cpu_hwprobe_values[] and handed to userspace on the fast patih. Zero pair->value once at the top of hwprobe_one_pair() so every handler starts from a clean value regardless of the caller, and drop the now redundant zeroing in the *_BLOCK_SIZE cases. hwprobe_isa_ext0() keeps its own zeroing because hwprobe_ext0_has() calls it directly, bypassing hwprobe_one_pair(). Fixes: a5ea53da65c5 ("riscv: hwprobe: Add thead vendor extension probing") Signed-off-by: Andy Chiu <tchiu@tenstorrent.com> --- Changelog v3: - new patch since v3 --- arch/riscv/kernel/sys_hwprobe.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c index caf6762427c8..d2bb70fed8b9 100644 --- a/arch/riscv/kernel/sys_hwprobe.c +++ b/arch/riscv/kernel/sys_hwprobe.c @@ -295,6 +295,8 @@ static u64 hwprobe_vec_misaligned(const struct cpumask *cpus) static void hwprobe_one_pair(struct riscv_hwprobe *pair, const struct cpumask *cpus) { + pair->value = 0; + switch (pair->key) { case RISCV_HWPROBE_KEY_MVENDORID: case RISCV_HWPROBE_KEY_MARCHID: @@ -329,17 +331,14 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, break; case RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE: - pair->value = 0; if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOZ)) pair->value = riscv_cboz_block_size; break; case RISCV_HWPROBE_KEY_ZICBOM_BLOCK_SIZE: - pair->value = 0; if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOM)) pair->value = riscv_cbom_block_size; break; case RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE: - pair->value = 0; if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOP)) pair->value = riscv_cbop_block_size; break; -- 2.43.0 _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 1/3] riscv: hwprobe: initialize pair->value in hwprobe_one_pair() 2026-07-25 0:15 ` [PATCH v3 1/3] riscv: hwprobe: initialize pair->value in hwprobe_one_pair() Andy Chiu @ 2026-08-05 16:56 ` Jesse Taube 0 siblings, 0 replies; 14+ messages in thread From: Jesse Taube @ 2026-08-05 16:56 UTC (permalink / raw) To: Andy Chiu Cc: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Evan Green, Charlie Jenkins, linux-riscv, bergner, kito.cheng, dfustini, greentime.hu, Andrew Jones, Deepak Gupta, Xu Lu, Yao Zihong, Jingwei Wang, Clément Léger On Fri, Jul 24, 2026 at 9:14 PM Andy Chiu <tchiu@tenstorrent.com> wrote: > > The vendor-extension handlers reached from hwprobe_one_pair() > (hwprobe_isa_vendor_ext_thead_0() and friends) only OR the present bits > into pair->value via VENDOR_EXTENSION_SUPPORTED() and clear their own > missing bits; they assume the caller has already zeroed pair->value. > > That holds for hwprobe_get_values() (it zeroes each pair) and > hwprobe_get_cpus() (it re-initializes its scratch pair per key), but not > for complete_hwprobe_vdso_data(), which reuses a single pair across all > keys For loop moment LOL > without re-zeroing. A vendor key therefore inherits stale bits from > the previously probed key, and the wrong value is cached in the vDSO > all_cpu_hwprobe_values[] and handed to userspace on the fast patih. > > Zero pair->value once at the top of hwprobe_one_pair() so every handler > starts from a clean value regardless of the caller, and drop the now > redundant zeroing in the *_BLOCK_SIZE cases. hwprobe_isa_ext0() keeps its > own zeroing because hwprobe_ext0_has() calls it directly, bypassing > hwprobe_one_pair(). > > Fixes: a5ea53da65c5 ("riscv: hwprobe: Add thead vendor extension probing") > Signed-off-by: Andy Chiu <tchiu@tenstorrent.com> Reviewed-by: Jesse Taube <jtaubepe@redhat.com> > --- > Changelog v3: > - new patch since v3 > --- > arch/riscv/kernel/sys_hwprobe.c | 5 ++--- > 1 file changed, 2 insertions(+), 3 deletions(-) > > diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c > index caf6762427c8..d2bb70fed8b9 100644 > --- a/arch/riscv/kernel/sys_hwprobe.c > +++ b/arch/riscv/kernel/sys_hwprobe.c > @@ -295,6 +295,8 @@ static u64 hwprobe_vec_misaligned(const struct cpumask *cpus) > static void hwprobe_one_pair(struct riscv_hwprobe *pair, > const struct cpumask *cpus) > { > + pair->value = 0; > + > switch (pair->key) { > case RISCV_HWPROBE_KEY_MVENDORID: > case RISCV_HWPROBE_KEY_MARCHID: > @@ -329,17 +331,14 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, > break; > > case RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE: > - pair->value = 0; > if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOZ)) > pair->value = riscv_cboz_block_size; > break; > case RISCV_HWPROBE_KEY_ZICBOM_BLOCK_SIZE: > - pair->value = 0; > if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOM)) > pair->value = riscv_cbom_block_size; > break; > case RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE: > - pair->value = 0; > if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOP)) > pair->value = riscv_cbop_block_size; > break; > -- > 2.43.0 > > > _______________________________________________ > linux-riscv mailing list > linux-riscv@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-riscv > _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user 2026-07-25 0:15 ` Andy Chiu @ 2026-07-25 0:15 ` Andy Chiu -1 siblings, 0 replies; 14+ messages in thread From: Andy Chiu @ 2026-07-25 0:15 UTC (permalink / raw) To: Jonathan Corbet, Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Samuel Holland, linux-doc, linux-riscv Cc: bergner, kito.cheng, Andy Chiu, dfustini, greentime.hu, Andrew Jones, Guodong Xu, Aleksa Paunovic, Pincheng Wang, Xu Lu, Yao Zihong, Jingwei Wang, Deepak Gupta, Clément Léger Userland IFUNC resolvers use hwprobe to decide whether to dispatch to vectorized routines. But RISCV_HWPROBE_KEY_IMA_EXT_0 only reports what is present in hardware, not what the calling process may actually use: when Vector is disabled for a process via prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF), it is still reported as present. A resolver that trusts this and runs a vector instruction crashes with SIGILL. Add RISCV_HWPROBE_KEY_EXT_ENABLED, a positional modifier key that carries no value of its own. Within a single request, keys placed after it report extensions that are both present and enabled for the calling process, while keys before it keep reporting hardware presence. This masks out V and its V-dependent sub-extensions when V is disabled for the process, and lets userland obtain both views in one query: [ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ] present modifier enabled The enabled view depends on per-process state, so it cannot be served from the vDSO's process-independent cache; requests carrying the modifier are deferred to the syscall. Unknown keys are still reported as -1, so the feature is detectable and existing users are unaffected. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220795 Link: https://lore.kernel.org/all/20260107000609.63892-1-andybnac@gmail.com/ Signed-off-by: Andy Chiu <tchiu@tenstorrent.com> --- Changelog v2: - rebase on top of latest for-next - add test_avail to sifive's vector depending extensions --- Documentation/arch/riscv/hwprobe.rst | 27 ++++++++++++++ arch/riscv/include/asm/hwprobe.h | 2 +- .../asm/vendor_extensions/sifive_hwprobe.h | 6 ++-- .../asm/vendor_extensions/thead_hwprobe.h | 6 ++-- arch/riscv/include/uapi/asm/hwprobe.h | 2 ++ arch/riscv/kernel/sys_hwprobe.c | 36 +++++++++++++------ arch/riscv/kernel/vdso/hwprobe.c | 25 +++++++++---- .../kernel/vendor_extensions/sifive_hwprobe.c | 16 ++++++--- .../kernel/vendor_extensions/thead_hwprobe.c | 9 +++-- 9 files changed, 100 insertions(+), 29 deletions(-) diff --git a/Documentation/arch/riscv/hwprobe.rst b/Documentation/arch/riscv/hwprobe.rst index d9928641deb9..b9b3cf23c212 100644 --- a/Documentation/arch/riscv/hwprobe.rst +++ b/Documentation/arch/riscv/hwprobe.rst @@ -401,3 +401,30 @@ The following keys are defined: as defined in version 1.0 of the RISC-V Control-flow Integrity (CFI) extensions specification, ratified in commit 302a2d45c243 ("Update build-pdf.yml") of riscv-cfi. + + * :c:macro:`RISCV_HWPROBE_KEY_EXT_ENABLED`: A modifier key. It reports no + value of its own (its value is always 0) and instead changes how the + extension-bitmask keys that follow it in the same request are reported. + Keys placed before it report the extensions that are present in hardware, + as usual. Keys placed after it additionally require each reported extension + to be enabled for the calling process, i.e. usable without receiving a + SIGILL. + + Currently this applies to Vector. When V has been disabled for the process + with ``prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF)``, + :c:macro:`RISCV_HWPROBE_IMA_V` and the V-dependent sub-extensions are + cleared from any :c:macro:`RISCV_HWPROBE_KEY_IMA_EXT_0` that follows the + modifier, and :c:macro:`RISCV_HWPROBE_VENDOR_EXT_XTHEADVECTOR` is cleared + from any :c:macro:`RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0` that follows it, + while a preceding key still reports them as present. A single request can + therefore return both the hardware-present set and the process-usable + set:: + + struct riscv_hwprobe pairs[3] = { + { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present in hardware */ + { .key = RISCV_HWPROBE_KEY_EXT_ENABLED, }, /* modifier */ + { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present and enabled */ + }; + + The effect is positional: reordering the pairs changes which keys have the + enablement filter applied. diff --git a/arch/riscv/include/asm/hwprobe.h b/arch/riscv/include/asm/hwprobe.h index 8b9f5e1cf4cb..e8364a3eaeef 100644 --- a/arch/riscv/include/asm/hwprobe.h +++ b/arch/riscv/include/asm/hwprobe.h @@ -8,7 +8,7 @@ #include <uapi/asm/hwprobe.h> -#define RISCV_HWPROBE_MAX_KEY 16 +#define RISCV_HWPROBE_MAX_KEY 17 static inline bool riscv_hwprobe_key_is_valid(__s64 key) { diff --git a/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h index 90a61abd033c..c961c78a7927 100644 --- a/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h +++ b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h @@ -7,10 +7,12 @@ #include <uapi/asm/hwprobe.h> #ifdef CONFIG_RISCV_ISA_VENDOR_EXT_SIFIVE -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus); +void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, + const struct cpumask *cpus, bool test_avail); #else static inline void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, - const struct cpumask *cpus) + const struct cpumask *cpus, + bool test_avail) { pair->value = 0; } diff --git a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h index 65a9c5612466..3e1c4271383f 100644 --- a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h +++ b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h @@ -7,10 +7,12 @@ #include <uapi/asm/hwprobe.h> #ifdef CONFIG_RISCV_ISA_VENDOR_EXT_THEAD -void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus); +void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, + const struct cpumask *cpus, bool test_avail); #else static inline void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, - const struct cpumask *cpus) + const struct cpumask *cpus, + bool test_avail) { pair->value = 0; } diff --git a/arch/riscv/include/uapi/asm/hwprobe.h b/arch/riscv/include/uapi/asm/hwprobe.h index 9139edba0aec..561483172ce5 100644 --- a/arch/riscv/include/uapi/asm/hwprobe.h +++ b/arch/riscv/include/uapi/asm/hwprobe.h @@ -116,6 +116,8 @@ struct riscv_hwprobe { #define RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE 15 #define RISCV_HWPROBE_KEY_IMA_EXT_1 16 #define RISCV_HWPROBE_EXT_ZICFISS (1ULL << 0) +/* Modifier key working as a signal to the kernel to gather enablement status */ +#define RISCV_HWPROBE_KEY_EXT_ENABLED 17 /* Increase RISCV_HWPROBE_MAX_KEY when adding items. */ diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c index d2bb70fed8b9..5949e3d9146f 100644 --- a/arch/riscv/kernel/sys_hwprobe.c +++ b/arch/riscv/kernel/sys_hwprobe.c @@ -79,8 +79,9 @@ static void hwprobe_arch_id(struct riscv_hwprobe *pair, } static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, - const struct cpumask *cpus) + const struct cpumask *cpus, bool report_avail) { + bool report_v; int cpu; u64 missing = 0; @@ -91,7 +92,8 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, if (riscv_isa_extension_available(NULL, c)) pair->value |= RISCV_HWPROBE_IMA_C; - if (has_vector() && riscv_isa_extension_available(NULL, v)) + report_v = report_avail ? riscv_v_vstate_ctrl_user_allowed() : true; + if (has_vector() && riscv_isa_extension_available(NULL, v) && report_v) pair->value |= RISCV_HWPROBE_IMA_V; /* @@ -146,7 +148,7 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, * All the following extensions must depend on the kernel * support of V. */ - if (has_vector()) { + if (has_vector() && report_v) { EXT_KEY(isainfo->isa, ZVBB, pair->value, missing); EXT_KEY(isainfo->isa, ZVBC, pair->value, missing); EXT_KEY(isainfo->isa, ZVE32F, pair->value, missing); @@ -215,7 +217,7 @@ static bool hwprobe_ext0_has(const struct cpumask *cpus, u64 ext) { struct riscv_hwprobe pair; - hwprobe_isa_ext0(&pair, cpus); + hwprobe_isa_ext0(&pair, cpus, false); return (pair.value & ext); } @@ -293,7 +295,7 @@ static u64 hwprobe_vec_misaligned(const struct cpumask *cpus) #endif static void hwprobe_one_pair(struct riscv_hwprobe *pair, - const struct cpumask *cpus) + const struct cpumask *cpus, bool test_avail) { pair->value = 0; @@ -314,7 +316,7 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, break; case RISCV_HWPROBE_KEY_IMA_EXT_0: - hwprobe_isa_ext0(pair, cpus); + hwprobe_isa_ext0(pair, cpus, test_avail); break; case RISCV_HWPROBE_KEY_IMA_EXT_1: @@ -351,11 +353,11 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, break; case RISCV_HWPROBE_KEY_VENDOR_EXT_SIFIVE_0: - hwprobe_isa_vendor_ext_sifive_0(pair, cpus); + hwprobe_isa_vendor_ext_sifive_0(pair, cpus, test_avail); break; case RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0: - hwprobe_isa_vendor_ext_thead_0(pair, cpus); + hwprobe_isa_vendor_ext_thead_0(pair, cpus, test_avail); break; case RISCV_HWPROBE_KEY_VENDOR_EXT_MIPS_0: hwprobe_isa_vendor_ext_mips_0(pair, cpus); @@ -378,6 +380,7 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs, unsigned long __user *cpus_user, unsigned int flags) { + bool test_avail = false; size_t out; int ret; cpumask_t cpus; @@ -418,7 +421,10 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs, return -EFAULT; pair.value = 0; - hwprobe_one_pair(&pair, &cpus); + if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED) + test_avail = true; + else + hwprobe_one_pair(&pair, &cpus, test_avail); ret = put_user(pair.key, &pairs->key); if (ret == 0) ret = put_user(pair.value, &pairs->value); @@ -437,6 +443,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, { cpumask_t cpus, one_cpu; bool clear_all = false; + bool test_avail = false; size_t i; int ret; @@ -476,6 +483,10 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, if (ret) return -EFAULT; } + if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED) { + test_avail = true; + continue; + } if (clear_all) continue; @@ -485,7 +496,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, for_each_cpu(cpu, &cpus) { cpumask_set_cpu(cpu, &one_cpu); - hwprobe_one_pair(&tmp, &one_cpu); + hwprobe_one_pair(&tmp, &one_cpu, test_avail); if (!riscv_hwprobe_pair_cmp(&tmp, &pair)) cpumask_clear_cpu(cpu, &cpus); @@ -535,8 +546,11 @@ static int complete_hwprobe_vdso_data(void) * save a syscall in the common case. */ for (key = 0; key <= RISCV_HWPROBE_MAX_KEY; key++) { + if (key == RISCV_HWPROBE_KEY_EXT_ENABLED) + continue; + pair.key = key; - hwprobe_one_pair(&pair, cpu_online_mask); + hwprobe_one_pair(&pair, cpu_online_mask, false); WARN_ON_ONCE(pair.key < 0); diff --git a/arch/riscv/kernel/vdso/hwprobe.c b/arch/riscv/kernel/vdso/hwprobe.c index 8f45500d0a6e..f40c21e44ef9 100644 --- a/arch/riscv/kernel/vdso/hwprobe.c +++ b/arch/riscv/kernel/vdso/hwprobe.c @@ -14,7 +14,7 @@ extern int riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpusetsize, unsigned long *cpus, - unsigned int flags) + unsigned int flags, bool avail_test) { const struct vdso_arch_data *avd = &vdso_u_arch_data; bool all_cpus = !cpusetsize && !cpus; @@ -27,7 +27,8 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, * homogeneous, then this function can handle requests for arbitrary * masks. */ - if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || unlikely(!avd->ready)) + if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || + unlikely(!avd->ready) || avail_test) return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); /* This is something we can handle, fill out the pairs. */ @@ -48,7 +49,7 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpusetsize, unsigned long *cpus, - unsigned int flags) + unsigned int flags, bool avail_test) { const struct vdso_arch_data *avd = &vdso_u_arch_data; struct riscv_hwprobe *p = pairs; @@ -68,7 +69,8 @@ static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count, } } - if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || !avd->homogeneous_cpus) + if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || + !avd->homogeneous_cpus || avail_test) return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); while (p < end) { @@ -105,10 +107,21 @@ int __vdso_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpusetsize, unsigned long *cpus, unsigned int flags) { + struct riscv_hwprobe *p = pairs; + bool avail_test = false; + size_t i; + + for (i = 0; i < pair_count; i++) { + if (p[i].key == RISCV_HWPROBE_KEY_EXT_ENABLED) { + avail_test = true; + break; + } + } + if (flags & RISCV_HWPROBE_WHICH_CPUS) return riscv_vdso_get_cpus(pairs, pair_count, cpusetsize, - cpus, flags); + cpus, flags, avail_test); return riscv_vdso_get_values(pairs, pair_count, cpusetsize, - cpus, flags); + cpus, flags, avail_test); } diff --git a/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c index 1f77f6309763..379ea16bd89f 100644 --- a/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c +++ b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c @@ -6,17 +6,23 @@ #include <linux/cpumask.h> #include <linux/types.h> +#include <asm/vector.h> #include <uapi/asm/hwprobe.h> #include <uapi/asm/vendor/sifive.h> -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus) +void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus, + bool test_avail) { + bool report_v = test_avail ? riscv_v_vstate_ctrl_user_allowed() : true; + VENDOR_EXTENSION_SUPPORTED(pair, cpus, riscv_isa_vendor_ext_list_sifive.per_hart_isa_bitmap, { - VENDOR_EXT_KEY(XSFVQMACCDOD); - VENDOR_EXT_KEY(XSFVQMACCQOQ); - VENDOR_EXT_KEY(XSFVFNRCLIPXFQF); - VENDOR_EXT_KEY(XSFVFWMACCQQQ); + if (report_v) { + VENDOR_EXT_KEY(XSFVQMACCDOD); + VENDOR_EXT_KEY(XSFVQMACCQOQ); + VENDOR_EXT_KEY(XSFVFNRCLIPXFQF); + VENDOR_EXT_KEY(XSFVFWMACCQQQ); + } }); } diff --git a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c index 2eba34011786..28c58a7123d5 100644 --- a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c +++ b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c @@ -6,14 +6,19 @@ #include <linux/cpumask.h> #include <linux/types.h> +#include <asm/vector.h> #include <uapi/asm/hwprobe.h> #include <uapi/asm/vendor/thead.h> -void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus) +void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus, + bool test_avail) { + bool report_v = test_avail ? riscv_v_vstate_ctrl_user_allowed() : true; + VENDOR_EXTENSION_SUPPORTED(pair, cpus, riscv_isa_vendor_ext_list_thead.per_hart_isa_bitmap, { - VENDOR_EXT_KEY(XTHEADVECTOR); + if (report_v) + VENDOR_EXT_KEY(XTHEADVECTOR); }); } -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user @ 2026-07-25 0:15 ` Andy Chiu 0 siblings, 0 replies; 14+ messages in thread From: Andy Chiu @ 2026-07-25 0:15 UTC (permalink / raw) To: Jonathan Corbet, Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Samuel Holland, linux-doc, linux-riscv Cc: bergner, kito.cheng, Andy Chiu, dfustini, greentime.hu, Andrew Jones, Guodong Xu, Aleksa Paunovic, Pincheng Wang, Xu Lu, Yao Zihong, Jingwei Wang, Deepak Gupta, Clément Léger Userland IFUNC resolvers use hwprobe to decide whether to dispatch to vectorized routines. But RISCV_HWPROBE_KEY_IMA_EXT_0 only reports what is present in hardware, not what the calling process may actually use: when Vector is disabled for a process via prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF), it is still reported as present. A resolver that trusts this and runs a vector instruction crashes with SIGILL. Add RISCV_HWPROBE_KEY_EXT_ENABLED, a positional modifier key that carries no value of its own. Within a single request, keys placed after it report extensions that are both present and enabled for the calling process, while keys before it keep reporting hardware presence. This masks out V and its V-dependent sub-extensions when V is disabled for the process, and lets userland obtain both views in one query: [ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ] present modifier enabled The enabled view depends on per-process state, so it cannot be served from the vDSO's process-independent cache; requests carrying the modifier are deferred to the syscall. Unknown keys are still reported as -1, so the feature is detectable and existing users are unaffected. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220795 Link: https://lore.kernel.org/all/20260107000609.63892-1-andybnac@gmail.com/ Signed-off-by: Andy Chiu <tchiu@tenstorrent.com> --- Changelog v2: - rebase on top of latest for-next - add test_avail to sifive's vector depending extensions --- Documentation/arch/riscv/hwprobe.rst | 27 ++++++++++++++ arch/riscv/include/asm/hwprobe.h | 2 +- .../asm/vendor_extensions/sifive_hwprobe.h | 6 ++-- .../asm/vendor_extensions/thead_hwprobe.h | 6 ++-- arch/riscv/include/uapi/asm/hwprobe.h | 2 ++ arch/riscv/kernel/sys_hwprobe.c | 36 +++++++++++++------ arch/riscv/kernel/vdso/hwprobe.c | 25 +++++++++---- .../kernel/vendor_extensions/sifive_hwprobe.c | 16 ++++++--- .../kernel/vendor_extensions/thead_hwprobe.c | 9 +++-- 9 files changed, 100 insertions(+), 29 deletions(-) diff --git a/Documentation/arch/riscv/hwprobe.rst b/Documentation/arch/riscv/hwprobe.rst index d9928641deb9..b9b3cf23c212 100644 --- a/Documentation/arch/riscv/hwprobe.rst +++ b/Documentation/arch/riscv/hwprobe.rst @@ -401,3 +401,30 @@ The following keys are defined: as defined in version 1.0 of the RISC-V Control-flow Integrity (CFI) extensions specification, ratified in commit 302a2d45c243 ("Update build-pdf.yml") of riscv-cfi. + + * :c:macro:`RISCV_HWPROBE_KEY_EXT_ENABLED`: A modifier key. It reports no + value of its own (its value is always 0) and instead changes how the + extension-bitmask keys that follow it in the same request are reported. + Keys placed before it report the extensions that are present in hardware, + as usual. Keys placed after it additionally require each reported extension + to be enabled for the calling process, i.e. usable without receiving a + SIGILL. + + Currently this applies to Vector. When V has been disabled for the process + with ``prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF)``, + :c:macro:`RISCV_HWPROBE_IMA_V` and the V-dependent sub-extensions are + cleared from any :c:macro:`RISCV_HWPROBE_KEY_IMA_EXT_0` that follows the + modifier, and :c:macro:`RISCV_HWPROBE_VENDOR_EXT_XTHEADVECTOR` is cleared + from any :c:macro:`RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0` that follows it, + while a preceding key still reports them as present. A single request can + therefore return both the hardware-present set and the process-usable + set:: + + struct riscv_hwprobe pairs[3] = { + { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present in hardware */ + { .key = RISCV_HWPROBE_KEY_EXT_ENABLED, }, /* modifier */ + { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present and enabled */ + }; + + The effect is positional: reordering the pairs changes which keys have the + enablement filter applied. diff --git a/arch/riscv/include/asm/hwprobe.h b/arch/riscv/include/asm/hwprobe.h index 8b9f5e1cf4cb..e8364a3eaeef 100644 --- a/arch/riscv/include/asm/hwprobe.h +++ b/arch/riscv/include/asm/hwprobe.h @@ -8,7 +8,7 @@ #include <uapi/asm/hwprobe.h> -#define RISCV_HWPROBE_MAX_KEY 16 +#define RISCV_HWPROBE_MAX_KEY 17 static inline bool riscv_hwprobe_key_is_valid(__s64 key) { diff --git a/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h index 90a61abd033c..c961c78a7927 100644 --- a/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h +++ b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h @@ -7,10 +7,12 @@ #include <uapi/asm/hwprobe.h> #ifdef CONFIG_RISCV_ISA_VENDOR_EXT_SIFIVE -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus); +void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, + const struct cpumask *cpus, bool test_avail); #else static inline void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, - const struct cpumask *cpus) + const struct cpumask *cpus, + bool test_avail) { pair->value = 0; } diff --git a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h index 65a9c5612466..3e1c4271383f 100644 --- a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h +++ b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h @@ -7,10 +7,12 @@ #include <uapi/asm/hwprobe.h> #ifdef CONFIG_RISCV_ISA_VENDOR_EXT_THEAD -void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus); +void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, + const struct cpumask *cpus, bool test_avail); #else static inline void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, - const struct cpumask *cpus) + const struct cpumask *cpus, + bool test_avail) { pair->value = 0; } diff --git a/arch/riscv/include/uapi/asm/hwprobe.h b/arch/riscv/include/uapi/asm/hwprobe.h index 9139edba0aec..561483172ce5 100644 --- a/arch/riscv/include/uapi/asm/hwprobe.h +++ b/arch/riscv/include/uapi/asm/hwprobe.h @@ -116,6 +116,8 @@ struct riscv_hwprobe { #define RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE 15 #define RISCV_HWPROBE_KEY_IMA_EXT_1 16 #define RISCV_HWPROBE_EXT_ZICFISS (1ULL << 0) +/* Modifier key working as a signal to the kernel to gather enablement status */ +#define RISCV_HWPROBE_KEY_EXT_ENABLED 17 /* Increase RISCV_HWPROBE_MAX_KEY when adding items. */ diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c index d2bb70fed8b9..5949e3d9146f 100644 --- a/arch/riscv/kernel/sys_hwprobe.c +++ b/arch/riscv/kernel/sys_hwprobe.c @@ -79,8 +79,9 @@ static void hwprobe_arch_id(struct riscv_hwprobe *pair, } static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, - const struct cpumask *cpus) + const struct cpumask *cpus, bool report_avail) { + bool report_v; int cpu; u64 missing = 0; @@ -91,7 +92,8 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, if (riscv_isa_extension_available(NULL, c)) pair->value |= RISCV_HWPROBE_IMA_C; - if (has_vector() && riscv_isa_extension_available(NULL, v)) + report_v = report_avail ? riscv_v_vstate_ctrl_user_allowed() : true; + if (has_vector() && riscv_isa_extension_available(NULL, v) && report_v) pair->value |= RISCV_HWPROBE_IMA_V; /* @@ -146,7 +148,7 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, * All the following extensions must depend on the kernel * support of V. */ - if (has_vector()) { + if (has_vector() && report_v) { EXT_KEY(isainfo->isa, ZVBB, pair->value, missing); EXT_KEY(isainfo->isa, ZVBC, pair->value, missing); EXT_KEY(isainfo->isa, ZVE32F, pair->value, missing); @@ -215,7 +217,7 @@ static bool hwprobe_ext0_has(const struct cpumask *cpus, u64 ext) { struct riscv_hwprobe pair; - hwprobe_isa_ext0(&pair, cpus); + hwprobe_isa_ext0(&pair, cpus, false); return (pair.value & ext); } @@ -293,7 +295,7 @@ static u64 hwprobe_vec_misaligned(const struct cpumask *cpus) #endif static void hwprobe_one_pair(struct riscv_hwprobe *pair, - const struct cpumask *cpus) + const struct cpumask *cpus, bool test_avail) { pair->value = 0; @@ -314,7 +316,7 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, break; case RISCV_HWPROBE_KEY_IMA_EXT_0: - hwprobe_isa_ext0(pair, cpus); + hwprobe_isa_ext0(pair, cpus, test_avail); break; case RISCV_HWPROBE_KEY_IMA_EXT_1: @@ -351,11 +353,11 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, break; case RISCV_HWPROBE_KEY_VENDOR_EXT_SIFIVE_0: - hwprobe_isa_vendor_ext_sifive_0(pair, cpus); + hwprobe_isa_vendor_ext_sifive_0(pair, cpus, test_avail); break; case RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0: - hwprobe_isa_vendor_ext_thead_0(pair, cpus); + hwprobe_isa_vendor_ext_thead_0(pair, cpus, test_avail); break; case RISCV_HWPROBE_KEY_VENDOR_EXT_MIPS_0: hwprobe_isa_vendor_ext_mips_0(pair, cpus); @@ -378,6 +380,7 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs, unsigned long __user *cpus_user, unsigned int flags) { + bool test_avail = false; size_t out; int ret; cpumask_t cpus; @@ -418,7 +421,10 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs, return -EFAULT; pair.value = 0; - hwprobe_one_pair(&pair, &cpus); + if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED) + test_avail = true; + else + hwprobe_one_pair(&pair, &cpus, test_avail); ret = put_user(pair.key, &pairs->key); if (ret == 0) ret = put_user(pair.value, &pairs->value); @@ -437,6 +443,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, { cpumask_t cpus, one_cpu; bool clear_all = false; + bool test_avail = false; size_t i; int ret; @@ -476,6 +483,10 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, if (ret) return -EFAULT; } + if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED) { + test_avail = true; + continue; + } if (clear_all) continue; @@ -485,7 +496,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, for_each_cpu(cpu, &cpus) { cpumask_set_cpu(cpu, &one_cpu); - hwprobe_one_pair(&tmp, &one_cpu); + hwprobe_one_pair(&tmp, &one_cpu, test_avail); if (!riscv_hwprobe_pair_cmp(&tmp, &pair)) cpumask_clear_cpu(cpu, &cpus); @@ -535,8 +546,11 @@ static int complete_hwprobe_vdso_data(void) * save a syscall in the common case. */ for (key = 0; key <= RISCV_HWPROBE_MAX_KEY; key++) { + if (key == RISCV_HWPROBE_KEY_EXT_ENABLED) + continue; + pair.key = key; - hwprobe_one_pair(&pair, cpu_online_mask); + hwprobe_one_pair(&pair, cpu_online_mask, false); WARN_ON_ONCE(pair.key < 0); diff --git a/arch/riscv/kernel/vdso/hwprobe.c b/arch/riscv/kernel/vdso/hwprobe.c index 8f45500d0a6e..f40c21e44ef9 100644 --- a/arch/riscv/kernel/vdso/hwprobe.c +++ b/arch/riscv/kernel/vdso/hwprobe.c @@ -14,7 +14,7 @@ extern int riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpusetsize, unsigned long *cpus, - unsigned int flags) + unsigned int flags, bool avail_test) { const struct vdso_arch_data *avd = &vdso_u_arch_data; bool all_cpus = !cpusetsize && !cpus; @@ -27,7 +27,8 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, * homogeneous, then this function can handle requests for arbitrary * masks. */ - if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || unlikely(!avd->ready)) + if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || + unlikely(!avd->ready) || avail_test) return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); /* This is something we can handle, fill out the pairs. */ @@ -48,7 +49,7 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpusetsize, unsigned long *cpus, - unsigned int flags) + unsigned int flags, bool avail_test) { const struct vdso_arch_data *avd = &vdso_u_arch_data; struct riscv_hwprobe *p = pairs; @@ -68,7 +69,8 @@ static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count, } } - if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || !avd->homogeneous_cpus) + if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || + !avd->homogeneous_cpus || avail_test) return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); while (p < end) { @@ -105,10 +107,21 @@ int __vdso_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpusetsize, unsigned long *cpus, unsigned int flags) { + struct riscv_hwprobe *p = pairs; + bool avail_test = false; + size_t i; + + for (i = 0; i < pair_count; i++) { + if (p[i].key == RISCV_HWPROBE_KEY_EXT_ENABLED) { + avail_test = true; + break; + } + } + if (flags & RISCV_HWPROBE_WHICH_CPUS) return riscv_vdso_get_cpus(pairs, pair_count, cpusetsize, - cpus, flags); + cpus, flags, avail_test); return riscv_vdso_get_values(pairs, pair_count, cpusetsize, - cpus, flags); + cpus, flags, avail_test); } diff --git a/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c index 1f77f6309763..379ea16bd89f 100644 --- a/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c +++ b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c @@ -6,17 +6,23 @@ #include <linux/cpumask.h> #include <linux/types.h> +#include <asm/vector.h> #include <uapi/asm/hwprobe.h> #include <uapi/asm/vendor/sifive.h> -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus) +void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus, + bool test_avail) { + bool report_v = test_avail ? riscv_v_vstate_ctrl_user_allowed() : true; + VENDOR_EXTENSION_SUPPORTED(pair, cpus, riscv_isa_vendor_ext_list_sifive.per_hart_isa_bitmap, { - VENDOR_EXT_KEY(XSFVQMACCDOD); - VENDOR_EXT_KEY(XSFVQMACCQOQ); - VENDOR_EXT_KEY(XSFVFNRCLIPXFQF); - VENDOR_EXT_KEY(XSFVFWMACCQQQ); + if (report_v) { + VENDOR_EXT_KEY(XSFVQMACCDOD); + VENDOR_EXT_KEY(XSFVQMACCQOQ); + VENDOR_EXT_KEY(XSFVFNRCLIPXFQF); + VENDOR_EXT_KEY(XSFVFWMACCQQQ); + } }); } diff --git a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c index 2eba34011786..28c58a7123d5 100644 --- a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c +++ b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c @@ -6,14 +6,19 @@ #include <linux/cpumask.h> #include <linux/types.h> +#include <asm/vector.h> #include <uapi/asm/hwprobe.h> #include <uapi/asm/vendor/thead.h> -void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus) +void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus, + bool test_avail) { + bool report_v = test_avail ? riscv_v_vstate_ctrl_user_allowed() : true; + VENDOR_EXTENSION_SUPPORTED(pair, cpus, riscv_isa_vendor_ext_list_thead.per_hart_isa_bitmap, { - VENDOR_EXT_KEY(XTHEADVECTOR); + if (report_v) + VENDOR_EXT_KEY(XTHEADVECTOR); }); } -- 2.43.0 _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user 2026-07-25 0:15 ` Andy Chiu @ 2026-08-05 17:38 ` Jesse Taube -1 siblings, 0 replies; 14+ messages in thread From: Jesse Taube @ 2026-08-05 17:38 UTC (permalink / raw) To: Andy Chiu Cc: Jonathan Corbet, Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Samuel Holland, linux-doc, linux-riscv, bergner, kito.cheng, dfustini, greentime.hu, Andrew Jones, Guodong Xu, Aleksa Paunovic, Pincheng Wang, Xu Lu, Yao Zihong, Jingwei Wang, Deepak Gupta, Clément Léger On Fri, Jul 24, 2026 at 8:17 PM Andy Chiu <tchiu@tenstorrent.com> wrote: > > Userland IFUNC resolvers use hwprobe to decide whether to dispatch to > vectorized routines. But RISCV_HWPROBE_KEY_IMA_EXT_0 only reports what > is present in hardware, not what the calling process may actually use: > when Vector is disabled for a process via > prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF), it is still > reported as present. A resolver that trusts this and runs a vector > instruction crashes with SIGILL. > > Add RISCV_HWPROBE_KEY_EXT_ENABLED, a positional modifier key that carries > no value of its own. Within a single request, keys placed after it report > extensions that are both present and enabled for the calling process, > while keys before it keep reporting hardware presence. This masks out V > and its V-dependent sub-extensions when V is disabled for the process, and > lets userland obtain both views in one query: > > [ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ] > present modifier enabled > > The enabled view depends on per-process state, so it cannot be served from > the vDSO's process-independent cache; requests carrying the modifier are > deferred to the syscall. Unknown keys are still reported as -1, so the > feature is detectable and existing users are unaffected. > > Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220795 > Link: https://lore.kernel.org/all/20260107000609.63892-1-andybnac@gmail.com/ > Signed-off-by: Andy Chiu <tchiu@tenstorrent.com> > --- > Changelog v2: > - rebase on top of latest for-next > - add test_avail to sifive's vector depending extensions > --- > Documentation/arch/riscv/hwprobe.rst | 27 ++++++++++++++ > arch/riscv/include/asm/hwprobe.h | 2 +- > .../asm/vendor_extensions/sifive_hwprobe.h | 6 ++-- > .../asm/vendor_extensions/thead_hwprobe.h | 6 ++-- > arch/riscv/include/uapi/asm/hwprobe.h | 2 ++ > arch/riscv/kernel/sys_hwprobe.c | 36 +++++++++++++------ > arch/riscv/kernel/vdso/hwprobe.c | 25 +++++++++---- > .../kernel/vendor_extensions/sifive_hwprobe.c | 16 ++++++--- > .../kernel/vendor_extensions/thead_hwprobe.c | 9 +++-- > 9 files changed, 100 insertions(+), 29 deletions(-) > > diff --git a/Documentation/arch/riscv/hwprobe.rst b/Documentation/arch/riscv/hwprobe.rst > index d9928641deb9..b9b3cf23c212 100644 > --- a/Documentation/arch/riscv/hwprobe.rst > +++ b/Documentation/arch/riscv/hwprobe.rst > @@ -401,3 +401,30 @@ The following keys are defined: > as defined in version 1.0 of the RISC-V Control-flow Integrity (CFI) > extensions specification, ratified in commit 302a2d45c243 > ("Update build-pdf.yml") of riscv-cfi. > + > + * :c:macro:`RISCV_HWPROBE_KEY_EXT_ENABLED`: A modifier key. It reports no > + value of its own (its value is always 0) and instead changes how the > + extension-bitmask keys that follow it in the same request are reported. > + Keys placed before it report the extensions that are present in hardware, > + as usual. Keys placed after it additionally require each reported extension > + to be enabled for the calling process, i.e. usable without receiving a > + SIGILL. > + > + Currently this applies to Vector. When V has been disabled for the process > + with ``prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF)``, > + :c:macro:`RISCV_HWPROBE_IMA_V` and the V-dependent sub-extensions are > + cleared from any :c:macro:`RISCV_HWPROBE_KEY_IMA_EXT_0` that follows the > + modifier, and :c:macro:`RISCV_HWPROBE_VENDOR_EXT_XTHEADVECTOR` is cleared > + from any :c:macro:`RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0` that follows it, > + while a preceding key still reports them as present. A single request can > + therefore return both the hardware-present set and the process-usable > + set:: > + > + struct riscv_hwprobe pairs[3] = { > + { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present in hardware */ > + { .key = RISCV_HWPROBE_KEY_EXT_ENABLED, }, /* modifier */ > + { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present and enabled */ > + }; > + > + The effect is positional: reordering the pairs changes which keys have the > + enablement filter applied. > diff --git a/arch/riscv/include/asm/hwprobe.h b/arch/riscv/include/asm/hwprobe.h > index 8b9f5e1cf4cb..e8364a3eaeef 100644 > --- a/arch/riscv/include/asm/hwprobe.h > +++ b/arch/riscv/include/asm/hwprobe.h > @@ -8,7 +8,7 @@ > > #include <uapi/asm/hwprobe.h> > > -#define RISCV_HWPROBE_MAX_KEY 16 > +#define RISCV_HWPROBE_MAX_KEY 17 > > static inline bool riscv_hwprobe_key_is_valid(__s64 key) > { > diff --git a/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h > index 90a61abd033c..c961c78a7927 100644 > --- a/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h > +++ b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h > @@ -7,10 +7,12 @@ > #include <uapi/asm/hwprobe.h> > > #ifdef CONFIG_RISCV_ISA_VENDOR_EXT_SIFIVE > -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus); > +void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, > + const struct cpumask *cpus, bool test_avail); > #else > static inline void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, > - const struct cpumask *cpus) > + const struct cpumask *cpus, > + bool test_avail) > { > pair->value = 0; > } > diff --git a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h > index 65a9c5612466..3e1c4271383f 100644 > --- a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h > +++ b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h > @@ -7,10 +7,12 @@ > #include <uapi/asm/hwprobe.h> > > #ifdef CONFIG_RISCV_ISA_VENDOR_EXT_THEAD > -void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus); > +void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, > + const struct cpumask *cpus, bool test_avail); > #else > static inline void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, > - const struct cpumask *cpus) > + const struct cpumask *cpus, > + bool test_avail) > { > pair->value = 0; > } > diff --git a/arch/riscv/include/uapi/asm/hwprobe.h b/arch/riscv/include/uapi/asm/hwprobe.h > index 9139edba0aec..561483172ce5 100644 > --- a/arch/riscv/include/uapi/asm/hwprobe.h > +++ b/arch/riscv/include/uapi/asm/hwprobe.h > @@ -116,6 +116,8 @@ struct riscv_hwprobe { > #define RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE 15 > #define RISCV_HWPROBE_KEY_IMA_EXT_1 16 > #define RISCV_HWPROBE_EXT_ZICFISS (1ULL << 0) > +/* Modifier key working as a signal to the kernel to gather enablement status */ > +#define RISCV_HWPROBE_KEY_EXT_ENABLED 17 > > /* Increase RISCV_HWPROBE_MAX_KEY when adding items. */ > > diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c > index d2bb70fed8b9..5949e3d9146f 100644 > --- a/arch/riscv/kernel/sys_hwprobe.c > +++ b/arch/riscv/kernel/sys_hwprobe.c > @@ -79,8 +79,9 @@ static void hwprobe_arch_id(struct riscv_hwprobe *pair, > } > > static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, > - const struct cpumask *cpus) > + const struct cpumask *cpus, bool report_avail) > { > + bool report_v; > int cpu; > u64 missing = 0; > > @@ -91,7 +92,8 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, > if (riscv_isa_extension_available(NULL, c)) > pair->value |= RISCV_HWPROBE_IMA_C; > > - if (has_vector() && riscv_isa_extension_available(NULL, v)) > + report_v = report_avail ? riscv_v_vstate_ctrl_user_allowed() : true; > + if (has_vector() && riscv_isa_extension_available(NULL, v) && report_v) > pair->value |= RISCV_HWPROBE_IMA_V; > > /* > @@ -146,7 +148,7 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, > * All the following extensions must depend on the kernel > * support of V. > */ > - if (has_vector()) { > + if (has_vector() && report_v) { > EXT_KEY(isainfo->isa, ZVBB, pair->value, missing); > EXT_KEY(isainfo->isa, ZVBC, pair->value, missing); > EXT_KEY(isainfo->isa, ZVE32F, pair->value, missing); > @@ -215,7 +217,7 @@ static bool hwprobe_ext0_has(const struct cpumask *cpus, u64 ext) > { > struct riscv_hwprobe pair; > > - hwprobe_isa_ext0(&pair, cpus); > + hwprobe_isa_ext0(&pair, cpus, false); > return (pair.value & ext); > } > > @@ -293,7 +295,7 @@ static u64 hwprobe_vec_misaligned(const struct cpumask *cpus) > #endif > > static void hwprobe_one_pair(struct riscv_hwprobe *pair, > - const struct cpumask *cpus) > + const struct cpumask *cpus, bool test_avail) > { > pair->value = 0; > > @@ -314,7 +316,7 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, > break; > > case RISCV_HWPROBE_KEY_IMA_EXT_0: > - hwprobe_isa_ext0(pair, cpus); > + hwprobe_isa_ext0(pair, cpus, test_avail); > break; > > case RISCV_HWPROBE_KEY_IMA_EXT_1: > @@ -351,11 +353,11 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, > break; > > case RISCV_HWPROBE_KEY_VENDOR_EXT_SIFIVE_0: > - hwprobe_isa_vendor_ext_sifive_0(pair, cpus); > + hwprobe_isa_vendor_ext_sifive_0(pair, cpus, test_avail); > break; > > case RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0: > - hwprobe_isa_vendor_ext_thead_0(pair, cpus); > + hwprobe_isa_vendor_ext_thead_0(pair, cpus, test_avail); > break; > case RISCV_HWPROBE_KEY_VENDOR_EXT_MIPS_0: > hwprobe_isa_vendor_ext_mips_0(pair, cpus); It might be good to add a warning if `RISCV_HWPROBE_KEY_EXT_ENABLED` is ever passed to `hwprobe_one_pair` in the future as it should never be passed here. > @@ -378,6 +380,7 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs, > unsigned long __user *cpus_user, > unsigned int flags) > { > + bool test_avail = false; > size_t out; > int ret; > cpumask_t cpus; > @@ -418,7 +421,10 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs, > return -EFAULT; > > pair.value = 0; > - hwprobe_one_pair(&pair, &cpus); > + if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED) > + test_avail = true; > + else > + hwprobe_one_pair(&pair, &cpus, test_avail); > ret = put_user(pair.key, &pairs->key); > if (ret == 0) > ret = put_user(pair.value, &pairs->value); > @@ -437,6 +443,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, > { > cpumask_t cpus, one_cpu; > bool clear_all = false; > + bool test_avail = false; > size_t i; > int ret; > > @@ -476,6 +483,10 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, > if (ret) > return -EFAULT; > } > + if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED) { > + test_avail = true; > + continue; > + } > > if (clear_all) > continue; > @@ -485,7 +496,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, > for_each_cpu(cpu, &cpus) { > cpumask_set_cpu(cpu, &one_cpu); > > - hwprobe_one_pair(&tmp, &one_cpu); > + hwprobe_one_pair(&tmp, &one_cpu, test_avail); > > if (!riscv_hwprobe_pair_cmp(&tmp, &pair)) > cpumask_clear_cpu(cpu, &cpus); > @@ -535,8 +546,11 @@ static int complete_hwprobe_vdso_data(void) > * save a syscall in the common case. By the way this got removed in for-next ("Revert "riscv: hwprobe: Fix stale vDSO data for late-initialized keys at boot"") > */ > for (key = 0; key <= RISCV_HWPROBE_MAX_KEY; key++) { > + if (key == RISCV_HWPROBE_KEY_EXT_ENABLED) > + continue; > + > pair.key = key; > - hwprobe_one_pair(&pair, cpu_online_mask); > + hwprobe_one_pair(&pair, cpu_online_mask, false); > > WARN_ON_ONCE(pair.key < 0); > > diff --git a/arch/riscv/kernel/vdso/hwprobe.c b/arch/riscv/kernel/vdso/hwprobe.c > index 8f45500d0a6e..f40c21e44ef9 100644 > --- a/arch/riscv/kernel/vdso/hwprobe.c > +++ b/arch/riscv/kernel/vdso/hwprobe.c > @@ -14,7 +14,7 @@ extern int riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, > > static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, > size_t cpusetsize, unsigned long *cpus, > - unsigned int flags) > + unsigned int flags, bool avail_test) > { > const struct vdso_arch_data *avd = &vdso_u_arch_data; > bool all_cpus = !cpusetsize && !cpus; > @@ -27,7 +27,8 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, > * homogeneous, then this function can handle requests for arbitrary > * masks. > */ > - if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || unlikely(!avd->ready)) > + if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || > + unlikely(!avd->ready) || avail_test) > return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); > > /* This is something we can handle, fill out the pairs. */ > @@ -48,7 +49,7 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, > > static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count, > size_t cpusetsize, unsigned long *cpus, > - unsigned int flags) > + unsigned int flags, bool avail_test) > { > const struct vdso_arch_data *avd = &vdso_u_arch_data; > struct riscv_hwprobe *p = pairs; > @@ -68,7 +69,8 @@ static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count, > } > } > > - if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || !avd->homogeneous_cpus) > + if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || > + !avd->homogeneous_cpus || avail_test) > return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); > > while (p < end) { > @@ -105,10 +107,21 @@ int __vdso_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, > size_t cpusetsize, unsigned long *cpus, > unsigned int flags) > { > + struct riscv_hwprobe *p = pairs; > + bool avail_test = false; > + size_t i; > + > + for (i = 0; i < pair_count; i++) { > + if (p[i].key == RISCV_HWPROBE_KEY_EXT_ENABLED) { > + avail_test = true; NIT: `avail_test` is very similar to `test_avail`, which confused me at first. I would change `avail_test` to `has_avail_test` or `does_avail_test` so its more readable. > + break; > + } > + } > + > if (flags & RISCV_HWPROBE_WHICH_CPUS) > return riscv_vdso_get_cpus(pairs, pair_count, cpusetsize, > - cpus, flags); > + cpus, flags, avail_test); > > return riscv_vdso_get_values(pairs, pair_count, cpusetsize, > - cpus, flags); > + cpus, flags, avail_test); > } > diff --git a/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c > index 1f77f6309763..379ea16bd89f 100644 > --- a/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c > +++ b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c > @@ -6,17 +6,23 @@ > > #include <linux/cpumask.h> > #include <linux/types.h> > +#include <asm/vector.h> > > #include <uapi/asm/hwprobe.h> > #include <uapi/asm/vendor/sifive.h> > > -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus) > +void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus, > + bool test_avail) > { > + bool report_v = test_avail ? riscv_v_vstate_ctrl_user_allowed() : true; > + > VENDOR_EXTENSION_SUPPORTED(pair, cpus, > riscv_isa_vendor_ext_list_sifive.per_hart_isa_bitmap, { > - VENDOR_EXT_KEY(XSFVQMACCDOD); > - VENDOR_EXT_KEY(XSFVQMACCQOQ); > - VENDOR_EXT_KEY(XSFVFNRCLIPXFQF); > - VENDOR_EXT_KEY(XSFVFWMACCQQQ); > + if (report_v) { > + VENDOR_EXT_KEY(XSFVQMACCDOD); > + VENDOR_EXT_KEY(XSFVQMACCQOQ); > + VENDOR_EXT_KEY(XSFVFNRCLIPXFQF); > + VENDOR_EXT_KEY(XSFVFWMACCQQQ); > + } > }); > } > diff --git a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c > index 2eba34011786..28c58a7123d5 100644 > --- a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c > +++ b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c > @@ -6,14 +6,19 @@ > > #include <linux/cpumask.h> > #include <linux/types.h> > +#include <asm/vector.h> > > #include <uapi/asm/hwprobe.h> > #include <uapi/asm/vendor/thead.h> > > -void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus) > +void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus, > + bool test_avail) > { > + bool report_v = test_avail ? riscv_v_vstate_ctrl_user_allowed() : true; > + > VENDOR_EXTENSION_SUPPORTED(pair, cpus, > riscv_isa_vendor_ext_list_thead.per_hart_isa_bitmap, { > - VENDOR_EXT_KEY(XTHEADVECTOR); > + if (report_v) > + VENDOR_EXT_KEY(XTHEADVECTOR); > }); > } > -- > 2.43.0 > > > _______________________________________________ > linux-riscv mailing list > linux-riscv@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-riscv > ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user @ 2026-08-05 17:38 ` Jesse Taube 0 siblings, 0 replies; 14+ messages in thread From: Jesse Taube @ 2026-08-05 17:38 UTC (permalink / raw) To: Andy Chiu Cc: Jonathan Corbet, Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Samuel Holland, linux-doc, linux-riscv, bergner, kito.cheng, dfustini, greentime.hu, Andrew Jones, Guodong Xu, Aleksa Paunovic, Pincheng Wang, Xu Lu, Yao Zihong, Jingwei Wang, Deepak Gupta, Clément Léger On Fri, Jul 24, 2026 at 8:17 PM Andy Chiu <tchiu@tenstorrent.com> wrote: > > Userland IFUNC resolvers use hwprobe to decide whether to dispatch to > vectorized routines. But RISCV_HWPROBE_KEY_IMA_EXT_0 only reports what > is present in hardware, not what the calling process may actually use: > when Vector is disabled for a process via > prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF), it is still > reported as present. A resolver that trusts this and runs a vector > instruction crashes with SIGILL. > > Add RISCV_HWPROBE_KEY_EXT_ENABLED, a positional modifier key that carries > no value of its own. Within a single request, keys placed after it report > extensions that are both present and enabled for the calling process, > while keys before it keep reporting hardware presence. This masks out V > and its V-dependent sub-extensions when V is disabled for the process, and > lets userland obtain both views in one query: > > [ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ] > present modifier enabled > > The enabled view depends on per-process state, so it cannot be served from > the vDSO's process-independent cache; requests carrying the modifier are > deferred to the syscall. Unknown keys are still reported as -1, so the > feature is detectable and existing users are unaffected. > > Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220795 > Link: https://lore.kernel.org/all/20260107000609.63892-1-andybnac@gmail.com/ > Signed-off-by: Andy Chiu <tchiu@tenstorrent.com> > --- > Changelog v2: > - rebase on top of latest for-next > - add test_avail to sifive's vector depending extensions > --- > Documentation/arch/riscv/hwprobe.rst | 27 ++++++++++++++ > arch/riscv/include/asm/hwprobe.h | 2 +- > .../asm/vendor_extensions/sifive_hwprobe.h | 6 ++-- > .../asm/vendor_extensions/thead_hwprobe.h | 6 ++-- > arch/riscv/include/uapi/asm/hwprobe.h | 2 ++ > arch/riscv/kernel/sys_hwprobe.c | 36 +++++++++++++------ > arch/riscv/kernel/vdso/hwprobe.c | 25 +++++++++---- > .../kernel/vendor_extensions/sifive_hwprobe.c | 16 ++++++--- > .../kernel/vendor_extensions/thead_hwprobe.c | 9 +++-- > 9 files changed, 100 insertions(+), 29 deletions(-) > > diff --git a/Documentation/arch/riscv/hwprobe.rst b/Documentation/arch/riscv/hwprobe.rst > index d9928641deb9..b9b3cf23c212 100644 > --- a/Documentation/arch/riscv/hwprobe.rst > +++ b/Documentation/arch/riscv/hwprobe.rst > @@ -401,3 +401,30 @@ The following keys are defined: > as defined in version 1.0 of the RISC-V Control-flow Integrity (CFI) > extensions specification, ratified in commit 302a2d45c243 > ("Update build-pdf.yml") of riscv-cfi. > + > + * :c:macro:`RISCV_HWPROBE_KEY_EXT_ENABLED`: A modifier key. It reports no > + value of its own (its value is always 0) and instead changes how the > + extension-bitmask keys that follow it in the same request are reported. > + Keys placed before it report the extensions that are present in hardware, > + as usual. Keys placed after it additionally require each reported extension > + to be enabled for the calling process, i.e. usable without receiving a > + SIGILL. > + > + Currently this applies to Vector. When V has been disabled for the process > + with ``prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF)``, > + :c:macro:`RISCV_HWPROBE_IMA_V` and the V-dependent sub-extensions are > + cleared from any :c:macro:`RISCV_HWPROBE_KEY_IMA_EXT_0` that follows the > + modifier, and :c:macro:`RISCV_HWPROBE_VENDOR_EXT_XTHEADVECTOR` is cleared > + from any :c:macro:`RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0` that follows it, > + while a preceding key still reports them as present. A single request can > + therefore return both the hardware-present set and the process-usable > + set:: > + > + struct riscv_hwprobe pairs[3] = { > + { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present in hardware */ > + { .key = RISCV_HWPROBE_KEY_EXT_ENABLED, }, /* modifier */ > + { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present and enabled */ > + }; > + > + The effect is positional: reordering the pairs changes which keys have the > + enablement filter applied. > diff --git a/arch/riscv/include/asm/hwprobe.h b/arch/riscv/include/asm/hwprobe.h > index 8b9f5e1cf4cb..e8364a3eaeef 100644 > --- a/arch/riscv/include/asm/hwprobe.h > +++ b/arch/riscv/include/asm/hwprobe.h > @@ -8,7 +8,7 @@ > > #include <uapi/asm/hwprobe.h> > > -#define RISCV_HWPROBE_MAX_KEY 16 > +#define RISCV_HWPROBE_MAX_KEY 17 > > static inline bool riscv_hwprobe_key_is_valid(__s64 key) > { > diff --git a/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h > index 90a61abd033c..c961c78a7927 100644 > --- a/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h > +++ b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h > @@ -7,10 +7,12 @@ > #include <uapi/asm/hwprobe.h> > > #ifdef CONFIG_RISCV_ISA_VENDOR_EXT_SIFIVE > -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus); > +void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, > + const struct cpumask *cpus, bool test_avail); > #else > static inline void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, > - const struct cpumask *cpus) > + const struct cpumask *cpus, > + bool test_avail) > { > pair->value = 0; > } > diff --git a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h > index 65a9c5612466..3e1c4271383f 100644 > --- a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h > +++ b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h > @@ -7,10 +7,12 @@ > #include <uapi/asm/hwprobe.h> > > #ifdef CONFIG_RISCV_ISA_VENDOR_EXT_THEAD > -void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus); > +void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, > + const struct cpumask *cpus, bool test_avail); > #else > static inline void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, > - const struct cpumask *cpus) > + const struct cpumask *cpus, > + bool test_avail) > { > pair->value = 0; > } > diff --git a/arch/riscv/include/uapi/asm/hwprobe.h b/arch/riscv/include/uapi/asm/hwprobe.h > index 9139edba0aec..561483172ce5 100644 > --- a/arch/riscv/include/uapi/asm/hwprobe.h > +++ b/arch/riscv/include/uapi/asm/hwprobe.h > @@ -116,6 +116,8 @@ struct riscv_hwprobe { > #define RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE 15 > #define RISCV_HWPROBE_KEY_IMA_EXT_1 16 > #define RISCV_HWPROBE_EXT_ZICFISS (1ULL << 0) > +/* Modifier key working as a signal to the kernel to gather enablement status */ > +#define RISCV_HWPROBE_KEY_EXT_ENABLED 17 > > /* Increase RISCV_HWPROBE_MAX_KEY when adding items. */ > > diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c > index d2bb70fed8b9..5949e3d9146f 100644 > --- a/arch/riscv/kernel/sys_hwprobe.c > +++ b/arch/riscv/kernel/sys_hwprobe.c > @@ -79,8 +79,9 @@ static void hwprobe_arch_id(struct riscv_hwprobe *pair, > } > > static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, > - const struct cpumask *cpus) > + const struct cpumask *cpus, bool report_avail) > { > + bool report_v; > int cpu; > u64 missing = 0; > > @@ -91,7 +92,8 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, > if (riscv_isa_extension_available(NULL, c)) > pair->value |= RISCV_HWPROBE_IMA_C; > > - if (has_vector() && riscv_isa_extension_available(NULL, v)) > + report_v = report_avail ? riscv_v_vstate_ctrl_user_allowed() : true; > + if (has_vector() && riscv_isa_extension_available(NULL, v) && report_v) > pair->value |= RISCV_HWPROBE_IMA_V; > > /* > @@ -146,7 +148,7 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair, > * All the following extensions must depend on the kernel > * support of V. > */ > - if (has_vector()) { > + if (has_vector() && report_v) { > EXT_KEY(isainfo->isa, ZVBB, pair->value, missing); > EXT_KEY(isainfo->isa, ZVBC, pair->value, missing); > EXT_KEY(isainfo->isa, ZVE32F, pair->value, missing); > @@ -215,7 +217,7 @@ static bool hwprobe_ext0_has(const struct cpumask *cpus, u64 ext) > { > struct riscv_hwprobe pair; > > - hwprobe_isa_ext0(&pair, cpus); > + hwprobe_isa_ext0(&pair, cpus, false); > return (pair.value & ext); > } > > @@ -293,7 +295,7 @@ static u64 hwprobe_vec_misaligned(const struct cpumask *cpus) > #endif > > static void hwprobe_one_pair(struct riscv_hwprobe *pair, > - const struct cpumask *cpus) > + const struct cpumask *cpus, bool test_avail) > { > pair->value = 0; > > @@ -314,7 +316,7 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, > break; > > case RISCV_HWPROBE_KEY_IMA_EXT_0: > - hwprobe_isa_ext0(pair, cpus); > + hwprobe_isa_ext0(pair, cpus, test_avail); > break; > > case RISCV_HWPROBE_KEY_IMA_EXT_1: > @@ -351,11 +353,11 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, > break; > > case RISCV_HWPROBE_KEY_VENDOR_EXT_SIFIVE_0: > - hwprobe_isa_vendor_ext_sifive_0(pair, cpus); > + hwprobe_isa_vendor_ext_sifive_0(pair, cpus, test_avail); > break; > > case RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0: > - hwprobe_isa_vendor_ext_thead_0(pair, cpus); > + hwprobe_isa_vendor_ext_thead_0(pair, cpus, test_avail); > break; > case RISCV_HWPROBE_KEY_VENDOR_EXT_MIPS_0: > hwprobe_isa_vendor_ext_mips_0(pair, cpus); It might be good to add a warning if `RISCV_HWPROBE_KEY_EXT_ENABLED` is ever passed to `hwprobe_one_pair` in the future as it should never be passed here. > @@ -378,6 +380,7 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs, > unsigned long __user *cpus_user, > unsigned int flags) > { > + bool test_avail = false; > size_t out; > int ret; > cpumask_t cpus; > @@ -418,7 +421,10 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs, > return -EFAULT; > > pair.value = 0; > - hwprobe_one_pair(&pair, &cpus); > + if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED) > + test_avail = true; > + else > + hwprobe_one_pair(&pair, &cpus, test_avail); > ret = put_user(pair.key, &pairs->key); > if (ret == 0) > ret = put_user(pair.value, &pairs->value); > @@ -437,6 +443,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, > { > cpumask_t cpus, one_cpu; > bool clear_all = false; > + bool test_avail = false; > size_t i; > int ret; > > @@ -476,6 +483,10 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, > if (ret) > return -EFAULT; > } > + if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED) { > + test_avail = true; > + continue; > + } > > if (clear_all) > continue; > @@ -485,7 +496,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, > for_each_cpu(cpu, &cpus) { > cpumask_set_cpu(cpu, &one_cpu); > > - hwprobe_one_pair(&tmp, &one_cpu); > + hwprobe_one_pair(&tmp, &one_cpu, test_avail); > > if (!riscv_hwprobe_pair_cmp(&tmp, &pair)) > cpumask_clear_cpu(cpu, &cpus); > @@ -535,8 +546,11 @@ static int complete_hwprobe_vdso_data(void) > * save a syscall in the common case. By the way this got removed in for-next ("Revert "riscv: hwprobe: Fix stale vDSO data for late-initialized keys at boot"") > */ > for (key = 0; key <= RISCV_HWPROBE_MAX_KEY; key++) { > + if (key == RISCV_HWPROBE_KEY_EXT_ENABLED) > + continue; > + > pair.key = key; > - hwprobe_one_pair(&pair, cpu_online_mask); > + hwprobe_one_pair(&pair, cpu_online_mask, false); > > WARN_ON_ONCE(pair.key < 0); > > diff --git a/arch/riscv/kernel/vdso/hwprobe.c b/arch/riscv/kernel/vdso/hwprobe.c > index 8f45500d0a6e..f40c21e44ef9 100644 > --- a/arch/riscv/kernel/vdso/hwprobe.c > +++ b/arch/riscv/kernel/vdso/hwprobe.c > @@ -14,7 +14,7 @@ extern int riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, > > static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, > size_t cpusetsize, unsigned long *cpus, > - unsigned int flags) > + unsigned int flags, bool avail_test) > { > const struct vdso_arch_data *avd = &vdso_u_arch_data; > bool all_cpus = !cpusetsize && !cpus; > @@ -27,7 +27,8 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, > * homogeneous, then this function can handle requests for arbitrary > * masks. > */ > - if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || unlikely(!avd->ready)) > + if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || > + unlikely(!avd->ready) || avail_test) > return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); > > /* This is something we can handle, fill out the pairs. */ > @@ -48,7 +49,7 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count, > > static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count, > size_t cpusetsize, unsigned long *cpus, > - unsigned int flags) > + unsigned int flags, bool avail_test) > { > const struct vdso_arch_data *avd = &vdso_u_arch_data; > struct riscv_hwprobe *p = pairs; > @@ -68,7 +69,8 @@ static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count, > } > } > > - if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || !avd->homogeneous_cpus) > + if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || > + !avd->homogeneous_cpus || avail_test) > return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags); > > while (p < end) { > @@ -105,10 +107,21 @@ int __vdso_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, > size_t cpusetsize, unsigned long *cpus, > unsigned int flags) > { > + struct riscv_hwprobe *p = pairs; > + bool avail_test = false; > + size_t i; > + > + for (i = 0; i < pair_count; i++) { > + if (p[i].key == RISCV_HWPROBE_KEY_EXT_ENABLED) { > + avail_test = true; NIT: `avail_test` is very similar to `test_avail`, which confused me at first. I would change `avail_test` to `has_avail_test` or `does_avail_test` so its more readable. > + break; > + } > + } > + > if (flags & RISCV_HWPROBE_WHICH_CPUS) > return riscv_vdso_get_cpus(pairs, pair_count, cpusetsize, > - cpus, flags); > + cpus, flags, avail_test); > > return riscv_vdso_get_values(pairs, pair_count, cpusetsize, > - cpus, flags); > + cpus, flags, avail_test); > } > diff --git a/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c > index 1f77f6309763..379ea16bd89f 100644 > --- a/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c > +++ b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c > @@ -6,17 +6,23 @@ > > #include <linux/cpumask.h> > #include <linux/types.h> > +#include <asm/vector.h> > > #include <uapi/asm/hwprobe.h> > #include <uapi/asm/vendor/sifive.h> > > -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus) > +void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus, > + bool test_avail) > { > + bool report_v = test_avail ? riscv_v_vstate_ctrl_user_allowed() : true; > + > VENDOR_EXTENSION_SUPPORTED(pair, cpus, > riscv_isa_vendor_ext_list_sifive.per_hart_isa_bitmap, { > - VENDOR_EXT_KEY(XSFVQMACCDOD); > - VENDOR_EXT_KEY(XSFVQMACCQOQ); > - VENDOR_EXT_KEY(XSFVFNRCLIPXFQF); > - VENDOR_EXT_KEY(XSFVFWMACCQQQ); > + if (report_v) { > + VENDOR_EXT_KEY(XSFVQMACCDOD); > + VENDOR_EXT_KEY(XSFVQMACCQOQ); > + VENDOR_EXT_KEY(XSFVFNRCLIPXFQF); > + VENDOR_EXT_KEY(XSFVFWMACCQQQ); > + } > }); > } > diff --git a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c > index 2eba34011786..28c58a7123d5 100644 > --- a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c > +++ b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c > @@ -6,14 +6,19 @@ > > #include <linux/cpumask.h> > #include <linux/types.h> > +#include <asm/vector.h> > > #include <uapi/asm/hwprobe.h> > #include <uapi/asm/vendor/thead.h> > > -void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus) > +void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus, > + bool test_avail) > { > + bool report_v = test_avail ? riscv_v_vstate_ctrl_user_allowed() : true; > + > VENDOR_EXTENSION_SUPPORTED(pair, cpus, > riscv_isa_vendor_ext_list_thead.per_hart_isa_bitmap, { > - VENDOR_EXT_KEY(XTHEADVECTOR); > + if (report_v) > + VENDOR_EXT_KEY(XTHEADVECTOR); > }); > } > -- > 2.43.0 > > > _______________________________________________ > linux-riscv mailing list > linux-riscv@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-riscv > _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user 2026-07-25 0:15 ` Andy Chiu @ 2026-08-13 23:38 ` Mark Harris -1 siblings, 0 replies; 14+ messages in thread From: Mark Harris @ 2026-08-13 23:38 UTC (permalink / raw) To: tchiu Cc: Mark Harris, Jonathan Corbet, Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Samuel Holland, bergner, kito.cheng, dfustini, greentime.hu, Andrew Jones, Guodong Xu, Aleksa Paunovic, Pincheng Wang, Xu Lu, Yao Zihong, Jingwei Wang, Deepak Gupta, Clément Léger, linux-doc, linux-riscv Andy Chiu wrote: > Userland IFUNC resolvers use hwprobe to decide whether to dispatch to > vectorized routines. But RISCV_HWPROBE_KEY_IMA_EXT_0 only reports what > is present in hardware, not what the calling process may actually use: > when Vector is disabled for a process via > prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF), it is still > reported as present. A resolver that trusts this and runs a vector > instruction crashes with SIGILL. > > Add RISCV_HWPROBE_KEY_EXT_ENABLED, a positional modifier key that carries > no value of its own. Within a single request, keys placed after it report > extensions that are both present and enabled for the calling process, > while keys before it keep reporting hardware presence. This masks out V > and its V-dependent sub-extensions when V is disabled for the process, and > lets userland obtain both views in one query: > > [ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ] > present modifier enabled > > The enabled view depends on per-process state, so it cannot be served from > the vDSO's process-independent cache; requests carrying the modifier are > deferred to the syscall. Unknown keys are still reported as -1, so the > feature is detectable and existing users are unaffected. Currently, each key has a value and the order of the keys does not matter. If the interface needed to be extended to support process-specific values, or thread-specific values (the prctl() is thread-specific), I would expect either new keys to retrieve those process-specific or thread-specific values, or a new flag bit to indicate that existing keys should be reinterpreted in a new manner. Using a key as a modifier with no value and changing the keys to be order-dependent seems like an unnecessarily confusing ugly hack that we would have to live with for decades to come, just to slightly simplify code needed in the short term to handle older kernels. Additionally, the approach of falling back to the syscall whenever the modifier key is used could lead to each check for the vector extension, in an ifunc resolver or almost anywhere else, having to perform a syscall. Callers wanting to use other extensions may even use this modifier when checking for them, because given the choice of knowing whether the extension is potentially available or is available, the latter sounds like what they should be using to be future-proof, even if there is not currently a way to disable the extension. That could lead to a syscall for every extension check, defeating the vDSO cache. On some non-RISC-V platforms glibc allows its own use of specific CPU capabilities to be disabled through tunables (e.g., GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX2 ./my_x86_64_program), and as of glibc 2.44 tunables can also be set in /etc/tunables.conf and applied system-wide, even to specific programs. This is more convenient than a prctl and it would be nice if RISC-V extensions could also be disabled for specific programs through tunables, for any use where its availability is checked beforehand (in an ifunc resolver or elsewhere), without the need to go to the kernel for each check. Because ifunc resolvers may not have access to external symbols beyond __riscv_hwprobe(), it is really attractive to be able to obtain extension availability information using the same function. But that doesn't mean that the kernel has to be involved. The function is in libc, so it could call the vDSO function and then optionally modify the resulting values according to its own process-specific information about enabled or disabled extensions. This extra information would ideally come from the initial hwcaps (which reflect the vector prctl) and any tunables, so it is process-wide, can support arbitrary extensions, and can be checked without any kernel syscalls and without callers having to know or care whether a particular extension can be disabled. If the prctl was used to re-enable vector in some thread that may not affect it, but that seems like the desired behavior, at least for ifunc resolvers, since they are assumed to produce the same result in any thread and at any point during the process lifetime. If implemented using a new flag, the libc __riscv_hwprobe() function could just call the existing vDSO function but with the new flag masked out, and then if the flag is set, modify the resulting values. That would probably be the simplest and easiest to understand API and would not require kernel syscalls. - Mark ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user @ 2026-08-13 23:38 ` Mark Harris 0 siblings, 0 replies; 14+ messages in thread From: Mark Harris @ 2026-08-13 23:38 UTC (permalink / raw) To: tchiu Cc: Mark Harris, Jonathan Corbet, Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Samuel Holland, bergner, kito.cheng, dfustini, greentime.hu, Andrew Jones, Guodong Xu, Aleksa Paunovic, Pincheng Wang, Xu Lu, Yao Zihong, Jingwei Wang, Deepak Gupta, Clément Léger, linux-doc, linux-riscv Andy Chiu wrote: > Userland IFUNC resolvers use hwprobe to decide whether to dispatch to > vectorized routines. But RISCV_HWPROBE_KEY_IMA_EXT_0 only reports what > is present in hardware, not what the calling process may actually use: > when Vector is disabled for a process via > prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF), it is still > reported as present. A resolver that trusts this and runs a vector > instruction crashes with SIGILL. > > Add RISCV_HWPROBE_KEY_EXT_ENABLED, a positional modifier key that carries > no value of its own. Within a single request, keys placed after it report > extensions that are both present and enabled for the calling process, > while keys before it keep reporting hardware presence. This masks out V > and its V-dependent sub-extensions when V is disabled for the process, and > lets userland obtain both views in one query: > > [ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ] > present modifier enabled > > The enabled view depends on per-process state, so it cannot be served from > the vDSO's process-independent cache; requests carrying the modifier are > deferred to the syscall. Unknown keys are still reported as -1, so the > feature is detectable and existing users are unaffected. Currently, each key has a value and the order of the keys does not matter. If the interface needed to be extended to support process-specific values, or thread-specific values (the prctl() is thread-specific), I would expect either new keys to retrieve those process-specific or thread-specific values, or a new flag bit to indicate that existing keys should be reinterpreted in a new manner. Using a key as a modifier with no value and changing the keys to be order-dependent seems like an unnecessarily confusing ugly hack that we would have to live with for decades to come, just to slightly simplify code needed in the short term to handle older kernels. Additionally, the approach of falling back to the syscall whenever the modifier key is used could lead to each check for the vector extension, in an ifunc resolver or almost anywhere else, having to perform a syscall. Callers wanting to use other extensions may even use this modifier when checking for them, because given the choice of knowing whether the extension is potentially available or is available, the latter sounds like what they should be using to be future-proof, even if there is not currently a way to disable the extension. That could lead to a syscall for every extension check, defeating the vDSO cache. On some non-RISC-V platforms glibc allows its own use of specific CPU capabilities to be disabled through tunables (e.g., GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX2 ./my_x86_64_program), and as of glibc 2.44 tunables can also be set in /etc/tunables.conf and applied system-wide, even to specific programs. This is more convenient than a prctl and it would be nice if RISC-V extensions could also be disabled for specific programs through tunables, for any use where its availability is checked beforehand (in an ifunc resolver or elsewhere), without the need to go to the kernel for each check. Because ifunc resolvers may not have access to external symbols beyond __riscv_hwprobe(), it is really attractive to be able to obtain extension availability information using the same function. But that doesn't mean that the kernel has to be involved. The function is in libc, so it could call the vDSO function and then optionally modify the resulting values according to its own process-specific information about enabled or disabled extensions. This extra information would ideally come from the initial hwcaps (which reflect the vector prctl) and any tunables, so it is process-wide, can support arbitrary extensions, and can be checked without any kernel syscalls and without callers having to know or care whether a particular extension can be disabled. If the prctl was used to re-enable vector in some thread that may not affect it, but that seems like the desired behavior, at least for ifunc resolvers, since they are assumed to produce the same result in any thread and at any point during the process lifetime. If implemented using a new flag, the libc __riscv_hwprobe() function could just call the existing vDSO function but with the new flag masked out, and then if the flag is set, modify the resulting values. That would probably be the simplest and easiest to understand API and would not require kernel syscalls. - Mark _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 3/3] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier 2026-07-25 0:15 ` Andy Chiu @ 2026-07-25 0:15 ` Andy Chiu -1 siblings, 0 replies; 14+ messages in thread From: Andy Chiu @ 2026-07-25 0:15 UTC (permalink / raw) To: Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, linux-kselftest, linux-riscv Cc: bergner, kito.cheng, Andy Chiu, dfustini, greentime.hu, Zong Li 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@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 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 3/3] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier @ 2026-07-25 0:15 ` Andy Chiu 0 siblings, 0 replies; 14+ messages in thread From: Andy Chiu @ 2026-07-25 0:15 UTC (permalink / raw) To: Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, linux-kselftest, linux-riscv Cc: bergner, kito.cheng, Andy Chiu, dfustini, greentime.hu, Zong Li 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@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 _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/3] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier 2026-07-25 0:15 ` Andy Chiu @ 2026-08-05 19:20 ` Jesse Taube -1 siblings, 0 replies; 14+ messages in thread From: Jesse Taube @ 2026-08-05 19:20 UTC (permalink / raw) To: Andy Chiu Cc: Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, linux-kselftest, linux-riscv, bergner, kito.cheng, dfustini, greentime.hu, Zong Li On Fri, Jul 24, 2026 at 8:17 PM Andy Chiu <tchiu@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@tenstorrent.com> Tested-by: Jesse Taube <jtaubepe@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@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-riscv > ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/3] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier @ 2026-08-05 19:20 ` Jesse Taube 0 siblings, 0 replies; 14+ messages in thread From: Jesse Taube @ 2026-08-05 19:20 UTC (permalink / raw) To: Andy Chiu Cc: Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, linux-kselftest, linux-riscv, bergner, kito.cheng, dfustini, greentime.hu, Zong Li On Fri, Jul 24, 2026 at 8:17 PM Andy Chiu <tchiu@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@tenstorrent.com> Tested-by: Jesse Taube <jtaubepe@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@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-riscv > _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-08-13 23:42 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-25 0:15 [PATCH v3 0/3] riscv: hwprobe: report per-process extension availability Andy Chiu 2026-07-25 0:15 ` Andy Chiu 2026-07-25 0:15 ` [PATCH v3 1/3] riscv: hwprobe: initialize pair->value in hwprobe_one_pair() Andy Chiu 2026-08-05 16:56 ` Jesse Taube 2026-07-25 0:15 ` [PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user Andy Chiu 2026-07-25 0:15 ` Andy Chiu 2026-08-05 17:38 ` Jesse Taube 2026-08-05 17:38 ` Jesse Taube 2026-08-13 23:38 ` Mark Harris 2026-08-13 23:38 ` Mark Harris 2026-07-25 0:15 ` [PATCH v3 3/3] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier Andy Chiu 2026-07-25 0:15 ` Andy Chiu 2026-08-05 19:20 ` Jesse Taube 2026-08-05 19:20 ` Jesse Taube
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.