Re: [PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user
Jesse Taube <[email protected]> Wed, 5 Aug 2026 13:38:33 -0400
| Newsgroups | org.infradead.lists.linux-riscv,org.kernel.vger.linux-doc |
|---|---|
| Message-ID | <CADRr4bccaiDeMF+R94Sd8hetoi84Z0p9Dxin5TCm3iAy8r9phA@mail.gmail.com> |
On Fri, Jul 24, 2026 at 8:17 PM Andy Chiu <[email protected]> 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/[email protected]/ > Signed-off-by: Andy Chiu <[email protected]> > --- > 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 > [email protected] > http://lists.infradead.org/mailman/listinfo/linux-riscv > _______________________________________________ linux-riscv mailing list [email protected] http://lists.infradead.org/mailman/listinfo/linux-riscv