2023-06-05 11:07:05 +00:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright ( C ) 2023 SiFive
* Author : Andy Chiu < andy . chiu @ sifive . com >
*/
# include <linux/export.h>
2023-06-05 11:07:08 +00:00
# include <linux/sched/signal.h>
# include <linux/types.h>
# include <linux/slab.h>
# include <linux/sched.h>
# include <linux/uaccess.h>
2023-06-05 11:07:18 +00:00
# include <linux/prctl.h>
2023-06-05 11:07:05 +00:00
2023-06-05 11:07:08 +00:00
# include <asm/thread_info.h>
# include <asm/processor.h>
# include <asm/insn.h>
2023-06-05 11:07:05 +00:00
# include <asm/vector.h>
# include <asm/csr.h>
# include <asm/elf.h>
2023-06-05 11:07:08 +00:00
# include <asm/ptrace.h>
2023-06-05 11:07:05 +00:00
# include <asm/bug.h>
2023-06-05 11:07:18 +00:00
static bool riscv_v_implicit_uacc = IS_ENABLED ( CONFIG_RISCV_ISA_V_DEFAULT_ENABLE ) ;
2023-06-05 11:07:05 +00:00
unsigned long riscv_v_vsize __read_mostly ;
EXPORT_SYMBOL_GPL ( riscv_v_vsize ) ;
int riscv_v_setup_vsize ( void )
{
unsigned long this_vsize ;
/* There are 32 vector registers with vlenb length. */
riscv_v_enable ( ) ;
this_vsize = csr_read ( CSR_VLENB ) * 32 ;
riscv_v_disable ( ) ;
if ( ! riscv_v_vsize ) {
riscv_v_vsize = this_vsize ;
return 0 ;
}
if ( riscv_v_vsize ! = this_vsize ) {
WARN ( 1 , " RISCV_ISA_V only supports one vlenb on SMP systems " ) ;
return - EOPNOTSUPP ;
}
return 0 ;
}
2023-06-05 11:07:08 +00:00
static bool insn_is_vector ( u32 insn_buf )
{
u32 opcode = insn_buf & __INSN_OPCODE_MASK ;
u32 width , csr ;
/*
* All V - related instructions , including CSR operations are 4 - Byte . So ,
* do not handle if the instruction length is not 4 - Byte .
*/
if ( unlikely ( GET_INSN_LENGTH ( insn_buf ) ! = 4 ) )
return false ;
switch ( opcode ) {
case RVV_OPCODE_VECTOR :
return true ;
case RVV_OPCODE_VL :
case RVV_OPCODE_VS :
width = RVV_EXRACT_VL_VS_WIDTH ( insn_buf ) ;
if ( width = = RVV_VL_VS_WIDTH_8 | | width = = RVV_VL_VS_WIDTH_16 | |
width = = RVV_VL_VS_WIDTH_32 | | width = = RVV_VL_VS_WIDTH_64 )
return true ;
break ;
case RVG_OPCODE_SYSTEM :
csr = RVG_EXTRACT_SYSTEM_CSR ( insn_buf ) ;
if ( ( csr > = CSR_VSTART & & csr < = CSR_VCSR ) | |
( csr > = CSR_VL & & csr < = CSR_VLENB ) )
return true ;
}
return false ;
}
static int riscv_v_thread_zalloc ( void )
{
void * datap ;
datap = kzalloc ( riscv_v_vsize , GFP_KERNEL ) ;
if ( ! datap )
return - ENOMEM ;
current - > thread . vstate . datap = datap ;
memset ( & current - > thread . vstate , 0 , offsetof ( struct __riscv_v_ext_state ,
datap ) ) ;
return 0 ;
}
2023-06-05 11:07:18 +00:00
# define VSTATE_CTRL_GET_CUR(x) ((x) & PR_RISCV_V_VSTATE_CTRL_CUR_MASK)
# define VSTATE_CTRL_GET_NEXT(x) (((x) & PR_RISCV_V_VSTATE_CTRL_NEXT_MASK) >> 2)
# define VSTATE_CTRL_MAKE_NEXT(x) (((x) << 2) & PR_RISCV_V_VSTATE_CTRL_NEXT_MASK)
# define VSTATE_CTRL_GET_INHERIT(x) (!!((x) & PR_RISCV_V_VSTATE_CTRL_INHERIT))
static inline int riscv_v_ctrl_get_cur ( struct task_struct * tsk )
{
return VSTATE_CTRL_GET_CUR ( tsk - > thread . vstate_ctrl ) ;
}
static inline int riscv_v_ctrl_get_next ( struct task_struct * tsk )
{
return VSTATE_CTRL_GET_NEXT ( tsk - > thread . vstate_ctrl ) ;
}
static inline bool riscv_v_ctrl_test_inherit ( struct task_struct * tsk )
{
return VSTATE_CTRL_GET_INHERIT ( tsk - > thread . vstate_ctrl ) ;
}
static inline void riscv_v_ctrl_set ( struct task_struct * tsk , int cur , int nxt ,
bool inherit )
{
unsigned long ctrl ;
ctrl = cur & PR_RISCV_V_VSTATE_CTRL_CUR_MASK ;
ctrl | = VSTATE_CTRL_MAKE_NEXT ( nxt ) ;
if ( inherit )
ctrl | = PR_RISCV_V_VSTATE_CTRL_INHERIT ;
tsk - > thread . vstate_ctrl = ctrl ;
}
bool riscv_v_vstate_ctrl_user_allowed ( void )
{
return riscv_v_ctrl_get_cur ( current ) = = PR_RISCV_V_VSTATE_CTRL_ON ;
}
EXPORT_SYMBOL_GPL ( riscv_v_vstate_ctrl_user_allowed ) ;
2023-06-05 11:07:08 +00:00
bool riscv_v_first_use_handler ( struct pt_regs * regs )
{
u32 __user * epc = ( u32 __user * ) regs - > epc ;
u32 insn = ( u32 ) regs - > badaddr ;
/* Do not handle if V is not supported, or disabled */
if ( ! ( ELF_HWCAP & COMPAT_HWCAP_ISA_V ) )
return false ;
/* If V has been enabled then it is not the first-use trap */
if ( riscv_v_vstate_query ( regs ) )
return false ;
/* Get the instruction */
if ( ! insn ) {
if ( __get_user ( insn , epc ) )
return false ;
}
/* Filter out non-V instructions */
if ( ! insn_is_vector ( insn ) )
return false ;
/* Sanity check. datap should be null by the time of the first-use trap */
WARN_ON ( current - > thread . vstate . datap ) ;
/*
* Now we sure that this is a V instruction . And it executes in the
* context where VS has been off . So , try to allocate the user ' s V
* context and resume execution .
*/
if ( riscv_v_thread_zalloc ( ) ) {
force_sig ( SIGBUS ) ;
return true ;
}
riscv_v_vstate_on ( regs ) ;
2023-06-27 01:55:54 +00:00
riscv_v_vstate_restore ( current , regs ) ;
2023-06-05 11:07:08 +00:00
return true ;
}
2023-06-05 11:07:18 +00:00
void riscv_v_vstate_ctrl_init ( struct task_struct * tsk )
{
bool inherit ;
int cur , next ;
if ( ! has_vector ( ) )
return ;
next = riscv_v_ctrl_get_next ( tsk ) ;
if ( ! next ) {
2023-06-05 11:07:19 +00:00
if ( READ_ONCE ( riscv_v_implicit_uacc ) )
2023-06-05 11:07:18 +00:00
cur = PR_RISCV_V_VSTATE_CTRL_ON ;
else
cur = PR_RISCV_V_VSTATE_CTRL_OFF ;
} else {
cur = next ;
}
/* Clear next mask if inherit-bit is not set */
inherit = riscv_v_ctrl_test_inherit ( tsk ) ;
if ( ! inherit )
next = PR_RISCV_V_VSTATE_CTRL_DEFAULT ;
riscv_v_ctrl_set ( tsk , cur , next , inherit ) ;
}
long riscv_v_vstate_ctrl_get_current ( void )
{
if ( ! has_vector ( ) )
return - EINVAL ;
return current - > thread . vstate_ctrl & PR_RISCV_V_VSTATE_CTRL_MASK ;
}
long riscv_v_vstate_ctrl_set_current ( unsigned long arg )
{
bool inherit ;
int cur , next ;
if ( ! has_vector ( ) )
return - EINVAL ;
if ( arg & ~ PR_RISCV_V_VSTATE_CTRL_MASK )
return - EINVAL ;
cur = VSTATE_CTRL_GET_CUR ( arg ) ;
switch ( cur ) {
case PR_RISCV_V_VSTATE_CTRL_OFF :
/* Do not allow user to turn off V if current is not off */
if ( riscv_v_ctrl_get_cur ( current ) ! = PR_RISCV_V_VSTATE_CTRL_OFF )
return - EPERM ;
break ;
case PR_RISCV_V_VSTATE_CTRL_ON :
break ;
case PR_RISCV_V_VSTATE_CTRL_DEFAULT :
cur = riscv_v_ctrl_get_cur ( current ) ;
break ;
default :
return - EINVAL ;
}
next = VSTATE_CTRL_GET_NEXT ( arg ) ;
inherit = VSTATE_CTRL_GET_INHERIT ( arg ) ;
switch ( next ) {
case PR_RISCV_V_VSTATE_CTRL_DEFAULT :
case PR_RISCV_V_VSTATE_CTRL_OFF :
case PR_RISCV_V_VSTATE_CTRL_ON :
riscv_v_ctrl_set ( current , cur , next , inherit ) ;
return 0 ;
}
return - EINVAL ;
}
2023-06-05 11:07:19 +00:00
# ifdef CONFIG_SYSCTL
static struct ctl_table riscv_v_default_vstate_table [ ] = {
{
. procname = " riscv_v_default_allow " ,
. data = & riscv_v_implicit_uacc ,
. maxlen = sizeof ( riscv_v_implicit_uacc ) ,
. mode = 0644 ,
. proc_handler = proc_dobool ,
} ,
{ }
} ;
static int __init riscv_v_sysctl_init ( void )
{
if ( has_vector ( ) )
if ( ! register_sysctl ( " abi " , riscv_v_default_vstate_table ) )
return - EINVAL ;
return 0 ;
}
# else /* ! CONFIG_SYSCTL */
static int __init riscv_v_sysctl_init ( void ) { return 0 ; }
# endif /* ! CONFIG_SYSCTL */
static int riscv_v_init ( void )
{
return riscv_v_sysctl_init ( ) ;
}
core_initcall ( riscv_v_init ) ;