[RFC PATCH] riscv: add userspace interface to voluntarily release vector state

Samuel Holland samuel.holland at sifive.com
Mon Mar 16 21:41:07 PDT 2026


Hi,

On 2026-03-16 9:22 PM, daichengrong wrote:
> Vector registers in RVV can be large, and saving/restoring them on
> context switches introduces overhead. Some workloads only use
> vector instructions in short phases, after which the vector state
> does not need to be preserved.
> 
> This patch introduces a userspace-controlled mechanism:
> 
> - Userspace can declare that it no longer needs the vector state.
> - Kernel will skip saving/restoring vector registers during context
>   switch while the declaration is active.
> - If the thread executes vector instructions after releasing its
>   vector state, the kernel will revoke the declaration automatically.
> 
> This reduces unnecessary vector context switch overhead and improves
> performance in workloads with intermittent vector usage.
> 
> This is an RFC patch to solicit feedback on the API design and
> implementation approach.
> 
> Signed-off-by: daichengrong <daichengrong at iscas.ac.cn>
> ---
>  arch/riscv/include/asm/processor.h |  1 +
>  arch/riscv/include/asm/syscall.h   |  2 ++
>  arch/riscv/include/asm/vector.h    |  7 +++++--
>  arch/riscv/kernel/process.c        |  1 +
>  arch/riscv/kernel/sys_riscv.c      | 12 ++++++++++++
>  scripts/syscall.tbl                |  1 +
>  6 files changed, 22 insertions(+), 2 deletions(-)
> 
> diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h
> index 4c3dd94d0f63..b59f1456918b 100644
> --- a/arch/riscv/include/asm/processor.h
> +++ b/arch/riscv/include/asm/processor.h
> @@ -113,6 +113,7 @@ struct thread_struct {
>  	unsigned long envcfg;
>  	unsigned long sum;
>  	u32 riscv_v_flags;
> +	unsigned long riscv_v_release_flags;
>  	u32 vstate_ctrl;
>  	struct __riscv_v_ext_state vstate;
>  	unsigned long align_ctl;
> diff --git a/arch/riscv/include/asm/syscall.h b/arch/riscv/include/asm/syscall.h
> index 8067e666a4ca..f6be37b01a67 100644
> --- a/arch/riscv/include/asm/syscall.h
> +++ b/arch/riscv/include/asm/syscall.h
> @@ -121,4 +121,6 @@ asmlinkage long sys_riscv_flush_icache(uintptr_t, uintptr_t, uintptr_t);
>  
>  asmlinkage long sys_riscv_hwprobe(struct riscv_hwprobe *, size_t, size_t,
>  				  unsigned long *, unsigned int);
> +// asmlinkage long sys_riscv_release_vector_register(uintptr_t);
> +asmlinkage long sys_riscv_release_vector_register(void);
>  #endif	/* _ASM_RISCV_SYSCALL_H */
> diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h
> index 00cb9c0982b1..4bccccc20cc3 100644
> --- a/arch/riscv/include/asm/vector.h
> +++ b/arch/riscv/include/asm/vector.h
> @@ -309,6 +309,7 @@ static inline void riscv_v_vstate_save(struct __riscv_v_ext_state *vstate,
>  	if (__riscv_v_vstate_check(regs->status, DIRTY)) {
>  		__riscv_v_vstate_save(vstate, vstate->datap);
>  		__riscv_v_vstate_clean(regs);
> +		WRITE_ONCE(current->thread.riscv_v_release_flags, 0);
>  	}
>  }
>  
> @@ -325,8 +326,10 @@ static inline void riscv_v_vstate_set_restore(struct task_struct *task,
>  					      struct pt_regs *regs)
>  {
>  	if (riscv_v_vstate_query(regs)) {
> -		set_tsk_thread_flag(task, TIF_RISCV_V_DEFER_RESTORE);
> -		riscv_v_vstate_on(regs);
> +		if (!READ_ONCE(current->thread.riscv_v_release_flags)) {
> +			set_tsk_thread_flag(task, TIF_RISCV_V_DEFER_RESTORE);
> +			riscv_v_vstate_on(regs);
> +		}

This is a security bug, because it leaks the previous task's vector registers.

>  	}
>  }
>  
> diff --git a/arch/riscv/kernel/process.c b/arch/riscv/kernel/process.c
> index aacb23978f93..f1f36a3c7914 100644
> --- a/arch/riscv/kernel/process.c
> +++ b/arch/riscv/kernel/process.c
> @@ -279,6 +279,7 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
>  		p->thread.ra = (unsigned long)ret_from_fork_user_asm;
>  	}
>  	p->thread.riscv_v_flags = 0;
> +	p->thread.riscv_v_release_flags = 0;
>  	if (has_vector() || has_xtheadvector())
>  		riscv_v_thread_alloc(p);
>  	p->thread.sp = (unsigned long)childregs; /* kernel sp */
> diff --git a/arch/riscv/kernel/sys_riscv.c b/arch/riscv/kernel/sys_riscv.c
> index 22fc9b3268be..934ddc06858d 100644
> --- a/arch/riscv/kernel/sys_riscv.c
> +++ b/arch/riscv/kernel/sys_riscv.c
> @@ -8,6 +8,7 @@
>  #include <linux/syscalls.h>
>  #include <asm/cacheflush.h>
>  #include <asm-generic/mman-common.h>
> +#include <asm/vector.h>
>  
>  static long riscv_sys_mmap(unsigned long addr, unsigned long len,
>  			   unsigned long prot, unsigned long flags,
> @@ -78,6 +79,17 @@ SYSCALL_DEFINE3(riscv_flush_icache, uintptr_t, start, uintptr_t, end,
>  	return 0;
>  }
>  
> +SYSCALL_DEFINE0(riscv_release_vector_register)
> +{
> +	struct pt_regs *regs = task_pt_regs(current);
> +
> +	if (__riscv_v_vstate_check(regs->status, DIRTY))
> +		__riscv_v_vstate_clean(regs);
> +
> +	WRITE_ONCE(current->thread.riscv_v_release_flags, 1);

To avoid leaking register state at context switch, you must either:
  1) set the vector registers to some safe contents (e.g. the initial state) or
  2) set VS=off

So if RVV is used rarely enough that you are willing to pay the cost of a trap
when you next use it, this function can be as simple as:

  riscv_v_vstate_off(task_pt_regs(current));

> +	return 0;
> +}
> +
>  /* Not defined using SYSCALL_DEFINE0 to avoid error injection */
>  asmlinkage long __riscv_sys_ni_syscall(const struct pt_regs *__unused)
>  {
> diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl
> index 7a42b32b6577..1d0a493b87c3 100644
> --- a/scripts/syscall.tbl
> +++ b/scripts/syscall.tbl
> @@ -302,6 +302,7 @@
>  
>  244	or1k	or1k_atomic			sys_or1k_atomic
>  
> +257	riscv	riscv_release_vector_register	sys_riscv_release_vector_register
>  258	riscv	riscv_hwprobe			sys_riscv_hwprobe
>  259	riscv	riscv_flush_icache		sys_riscv_flush_icache

You may also consider adding a flag to prctl(PR_RISCV_V_SET_CONTROL) instead of
a new syscall. So it would look something like:

  prctl(PR_RISCV_V_SET_CONTROL,
        PR_RISCV_V_VSTATE_CTRL_ON | PR_RISCV_V_VSTATE_CTRL_RESET);

(Arguably it is a bug that riscv_v_ctrl_set() doesn't call riscv_v_vstate_off()
for PR_RISCV_V_VSTATE_CTRL_OFF already.)

Regards,
Samuel




More information about the linux-riscv mailing list