2013-01-20 18:28:06 -05:00
/*
* Copyright ( C ) 2012 - Virtual Open Systems and Columbia University
* Author : Christoffer Dall < c . dall @ virtualopensystems . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License , version 2 , as
* published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
2013-01-20 18:28:09 -05:00
# include <linux/mm.h>
# include <linux/kvm_host.h>
# include <asm/kvm_arm.h>
2013-01-20 18:28:06 -05:00
# include <asm/kvm_emulate.h>
2012-12-08 18:13:18 +00:00
# include <asm/opcodes.h>
2013-01-20 18:28:09 -05:00
# include <trace/events/kvm.h>
# include "trace.h"
2013-01-20 18:28:06 -05:00
# define VCPU_NR_MODES 6
# define VCPU_REG_OFFSET_USR 0
# define VCPU_REG_OFFSET_FIQ 1
# define VCPU_REG_OFFSET_IRQ 2
# define VCPU_REG_OFFSET_SVC 3
# define VCPU_REG_OFFSET_ABT 4
# define VCPU_REG_OFFSET_UND 5
# define REG_OFFSET(_reg) \
( offsetof ( struct kvm_regs , _reg ) / sizeof ( u32 ) )
# define USR_REG_OFFSET(_num) REG_OFFSET(usr_regs.uregs[_num])
static const unsigned long vcpu_reg_offsets [ VCPU_NR_MODES ] [ 15 ] = {
/* USR/SYS Registers */
[ VCPU_REG_OFFSET_USR ] = {
USR_REG_OFFSET ( 0 ) , USR_REG_OFFSET ( 1 ) , USR_REG_OFFSET ( 2 ) ,
USR_REG_OFFSET ( 3 ) , USR_REG_OFFSET ( 4 ) , USR_REG_OFFSET ( 5 ) ,
USR_REG_OFFSET ( 6 ) , USR_REG_OFFSET ( 7 ) , USR_REG_OFFSET ( 8 ) ,
USR_REG_OFFSET ( 9 ) , USR_REG_OFFSET ( 10 ) , USR_REG_OFFSET ( 11 ) ,
USR_REG_OFFSET ( 12 ) , USR_REG_OFFSET ( 13 ) , USR_REG_OFFSET ( 14 ) ,
} ,
/* FIQ Registers */
[ VCPU_REG_OFFSET_FIQ ] = {
USR_REG_OFFSET ( 0 ) , USR_REG_OFFSET ( 1 ) , USR_REG_OFFSET ( 2 ) ,
USR_REG_OFFSET ( 3 ) , USR_REG_OFFSET ( 4 ) , USR_REG_OFFSET ( 5 ) ,
USR_REG_OFFSET ( 6 ) , USR_REG_OFFSET ( 7 ) ,
REG_OFFSET ( fiq_regs [ 0 ] ) , /* r8 */
REG_OFFSET ( fiq_regs [ 1 ] ) , /* r9 */
REG_OFFSET ( fiq_regs [ 2 ] ) , /* r10 */
REG_OFFSET ( fiq_regs [ 3 ] ) , /* r11 */
REG_OFFSET ( fiq_regs [ 4 ] ) , /* r12 */
REG_OFFSET ( fiq_regs [ 5 ] ) , /* r13 */
REG_OFFSET ( fiq_regs [ 6 ] ) , /* r14 */
} ,
/* IRQ Registers */
[ VCPU_REG_OFFSET_IRQ ] = {
USR_REG_OFFSET ( 0 ) , USR_REG_OFFSET ( 1 ) , USR_REG_OFFSET ( 2 ) ,
USR_REG_OFFSET ( 3 ) , USR_REG_OFFSET ( 4 ) , USR_REG_OFFSET ( 5 ) ,
USR_REG_OFFSET ( 6 ) , USR_REG_OFFSET ( 7 ) , USR_REG_OFFSET ( 8 ) ,
USR_REG_OFFSET ( 9 ) , USR_REG_OFFSET ( 10 ) , USR_REG_OFFSET ( 11 ) ,
USR_REG_OFFSET ( 12 ) ,
REG_OFFSET ( irq_regs [ 0 ] ) , /* r13 */
REG_OFFSET ( irq_regs [ 1 ] ) , /* r14 */
} ,
/* SVC Registers */
[ VCPU_REG_OFFSET_SVC ] = {
USR_REG_OFFSET ( 0 ) , USR_REG_OFFSET ( 1 ) , USR_REG_OFFSET ( 2 ) ,
USR_REG_OFFSET ( 3 ) , USR_REG_OFFSET ( 4 ) , USR_REG_OFFSET ( 5 ) ,
USR_REG_OFFSET ( 6 ) , USR_REG_OFFSET ( 7 ) , USR_REG_OFFSET ( 8 ) ,
USR_REG_OFFSET ( 9 ) , USR_REG_OFFSET ( 10 ) , USR_REG_OFFSET ( 11 ) ,
USR_REG_OFFSET ( 12 ) ,
REG_OFFSET ( svc_regs [ 0 ] ) , /* r13 */
REG_OFFSET ( svc_regs [ 1 ] ) , /* r14 */
} ,
/* ABT Registers */
[ VCPU_REG_OFFSET_ABT ] = {
USR_REG_OFFSET ( 0 ) , USR_REG_OFFSET ( 1 ) , USR_REG_OFFSET ( 2 ) ,
USR_REG_OFFSET ( 3 ) , USR_REG_OFFSET ( 4 ) , USR_REG_OFFSET ( 5 ) ,
USR_REG_OFFSET ( 6 ) , USR_REG_OFFSET ( 7 ) , USR_REG_OFFSET ( 8 ) ,
USR_REG_OFFSET ( 9 ) , USR_REG_OFFSET ( 10 ) , USR_REG_OFFSET ( 11 ) ,
USR_REG_OFFSET ( 12 ) ,
REG_OFFSET ( abt_regs [ 0 ] ) , /* r13 */
REG_OFFSET ( abt_regs [ 1 ] ) , /* r14 */
} ,
/* UND Registers */
[ VCPU_REG_OFFSET_UND ] = {
USR_REG_OFFSET ( 0 ) , USR_REG_OFFSET ( 1 ) , USR_REG_OFFSET ( 2 ) ,
USR_REG_OFFSET ( 3 ) , USR_REG_OFFSET ( 4 ) , USR_REG_OFFSET ( 5 ) ,
USR_REG_OFFSET ( 6 ) , USR_REG_OFFSET ( 7 ) , USR_REG_OFFSET ( 8 ) ,
USR_REG_OFFSET ( 9 ) , USR_REG_OFFSET ( 10 ) , USR_REG_OFFSET ( 11 ) ,
USR_REG_OFFSET ( 12 ) ,
REG_OFFSET ( und_regs [ 0 ] ) , /* r13 */
REG_OFFSET ( und_regs [ 1 ] ) , /* r14 */
} ,
} ;
/*
* Return a pointer to the register number valid in the current mode of
* the virtual CPU .
*/
2012-10-03 11:17:02 +01:00
unsigned long * vcpu_reg ( struct kvm_vcpu * vcpu , u8 reg_num )
2013-01-20 18:28:06 -05:00
{
2012-10-03 11:17:02 +01:00
unsigned long * reg_array = ( unsigned long * ) & vcpu - > arch . regs ;
unsigned long mode = * vcpu_cpsr ( vcpu ) & MODE_MASK ;
2013-01-20 18:28:06 -05:00
switch ( mode ) {
case USR_MODE . . . SVC_MODE :
mode & = ~ MODE32_BIT ; /* 0 ... 3 */
break ;
case ABT_MODE :
mode = VCPU_REG_OFFSET_ABT ;
break ;
case UND_MODE :
mode = VCPU_REG_OFFSET_UND ;
break ;
case SYSTEM_MODE :
mode = VCPU_REG_OFFSET_USR ;
break ;
default :
BUG ( ) ;
}
return reg_array + vcpu_reg_offsets [ mode ] [ reg_num ] ;
}
/*
* Return the SPSR for the current mode of the virtual CPU .
*/
2012-10-03 11:17:02 +01:00
unsigned long * vcpu_spsr ( struct kvm_vcpu * vcpu )
2013-01-20 18:28:06 -05:00
{
2012-10-03 11:17:02 +01:00
unsigned long mode = * vcpu_cpsr ( vcpu ) & MODE_MASK ;
2013-01-20 18:28:06 -05:00
switch ( mode ) {
case SVC_MODE :
return & vcpu - > arch . regs . KVM_ARM_SVC_spsr ;
case ABT_MODE :
return & vcpu - > arch . regs . KVM_ARM_ABT_spsr ;
case UND_MODE :
return & vcpu - > arch . regs . KVM_ARM_UND_spsr ;
case IRQ_MODE :
return & vcpu - > arch . regs . KVM_ARM_IRQ_spsr ;
case FIQ_MODE :
return & vcpu - > arch . regs . KVM_ARM_FIQ_spsr ;
default :
BUG ( ) ;
}
}
2013-01-20 18:28:09 -05:00
2012-12-08 18:13:18 +00:00
/*
* A conditional instruction is allowed to trap , even though it
* wouldn ' t be executed . So let ' s re - implement the hardware , in
* software !
*/
bool kvm_condition_valid ( struct kvm_vcpu * vcpu )
{
unsigned long cpsr , cond , insn ;
/*
* Exception Code 0 can only happen if we set HCR . TGE to 1 , to
* catch undefined instructions , and then we won ' t get past
* the arm_exit_handlers test anyway .
*/
BUG_ON ( ! kvm_vcpu_trap_get_class ( vcpu ) ) ;
/* Top two bits non-zero? Unconditional. */
if ( kvm_vcpu_get_hsr ( vcpu ) > > 30 )
return true ;
cpsr = * vcpu_cpsr ( vcpu ) ;
/* Is condition field valid? */
if ( ( kvm_vcpu_get_hsr ( vcpu ) & HSR_CV ) > > HSR_CV_SHIFT )
cond = ( kvm_vcpu_get_hsr ( vcpu ) & HSR_COND ) > > HSR_COND_SHIFT ;
else {
/* This can happen in Thumb mode: examine IT state. */
unsigned long it ;
it = ( ( cpsr > > 8 ) & 0xFC ) | ( ( cpsr > > 25 ) & 0x3 ) ;
/* it == 0 => unconditional. */
if ( it = = 0 )
return true ;
/* The cond for this insn works out as the top 4 bits. */
cond = ( it > > 4 ) ;
}
/* Shift makes it look like an ARM-mode instruction */
insn = cond < < 28 ;
return arm_check_condition ( insn , cpsr ) ! = ARM_OPCODE_CONDTEST_FAIL ;
}
2013-01-20 18:28:09 -05:00
/**
* adjust_itstate - adjust ITSTATE when emulating instructions in IT - block
* @ vcpu : The VCPU pointer
*
* When exceptions occur while instructions are executed in Thumb IF - THEN
* blocks , the ITSTATE field of the CPSR is not advanved ( updated ) , so we have
* to do this little bit of work manually . The fields map like this :
*
* IT [ 7 : 0 ] - > CPSR [ 26 : 25 ] , CPSR [ 15 : 10 ]
*/
static void kvm_adjust_itstate ( struct kvm_vcpu * vcpu )
{
unsigned long itbits , cond ;
unsigned long cpsr = * vcpu_cpsr ( vcpu ) ;
bool is_arm = ! ( cpsr & PSR_T_BIT ) ;
BUG_ON ( is_arm & & ( cpsr & PSR_IT_MASK ) ) ;
if ( ! ( cpsr & PSR_IT_MASK ) )
return ;
cond = ( cpsr & 0xe000 ) > > 13 ;
itbits = ( cpsr & 0x1c00 ) > > ( 10 - 2 ) ;
itbits | = ( cpsr & ( 0x3 < < 25 ) ) > > 25 ;
/* Perform ITAdvance (see page A-52 in ARM DDI 0406C) */
if ( ( itbits & 0x7 ) = = 0 )
itbits = cond = 0 ;
else
itbits = ( itbits < < 1 ) & 0x1f ;
cpsr & = ~ PSR_IT_MASK ;
cpsr | = cond < < 13 ;
cpsr | = ( itbits & 0x1c ) < < ( 10 - 2 ) ;
cpsr | = ( itbits & 0x3 ) < < 25 ;
* vcpu_cpsr ( vcpu ) = cpsr ;
}
/**
* kvm_skip_instr - skip a trapped instruction and proceed to the next
* @ vcpu : The vcpu pointer
*/
void kvm_skip_instr ( struct kvm_vcpu * vcpu , bool is_wide_instr )
{
bool is_thumb ;
is_thumb = ! ! ( * vcpu_cpsr ( vcpu ) & PSR_T_BIT ) ;
if ( is_thumb & & ! is_wide_instr )
* vcpu_pc ( vcpu ) + = 2 ;
else
* vcpu_pc ( vcpu ) + = 4 ;
kvm_adjust_itstate ( vcpu ) ;
}
/******************************************************************************
* Inject exceptions into the guest
*/
static u32 exc_vector_base ( struct kvm_vcpu * vcpu )
{
u32 sctlr = vcpu - > arch . cp15 [ c1_SCTLR ] ;
u32 vbar = vcpu - > arch . cp15 [ c12_VBAR ] ;
if ( sctlr & SCTLR_V )
return 0xffff0000 ;
else /* always have security exceptions */
return vbar ;
}
/**
* kvm_inject_undefined - inject an undefined exception into the guest
* @ vcpu : The VCPU to receive the undefined exception
*
* It is assumed that this code is called from the VCPU thread and that the
* VCPU therefore is not currently executing guest code .
*
* Modelled after TakeUndefInstrException ( ) pseudocode .
*/
void kvm_inject_undefined ( struct kvm_vcpu * vcpu )
{
2012-10-03 11:17:02 +01:00
unsigned long new_lr_value ;
unsigned long new_spsr_value ;
unsigned long cpsr = * vcpu_cpsr ( vcpu ) ;
2013-01-20 18:28:09 -05:00
u32 sctlr = vcpu - > arch . cp15 [ c1_SCTLR ] ;
bool is_thumb = ( cpsr & PSR_T_BIT ) ;
u32 vect_offset = 4 ;
u32 return_offset = ( is_thumb ) ? 2 : 4 ;
new_spsr_value = cpsr ;
new_lr_value = * vcpu_pc ( vcpu ) - return_offset ;
* vcpu_cpsr ( vcpu ) = ( cpsr & ~ MODE_MASK ) | UND_MODE ;
* vcpu_cpsr ( vcpu ) | = PSR_I_BIT ;
* vcpu_cpsr ( vcpu ) & = ~ ( PSR_IT_MASK | PSR_J_BIT | PSR_E_BIT | PSR_T_BIT ) ;
if ( sctlr & SCTLR_TE )
* vcpu_cpsr ( vcpu ) | = PSR_T_BIT ;
if ( sctlr & SCTLR_EE )
* vcpu_cpsr ( vcpu ) | = PSR_E_BIT ;
/* Note: These now point to UND banked copies */
* vcpu_spsr ( vcpu ) = cpsr ;
* vcpu_reg ( vcpu , 14 ) = new_lr_value ;
/* Branch to exception vector */
* vcpu_pc ( vcpu ) = exc_vector_base ( vcpu ) + vect_offset ;
}
/*
* Modelled after TakeDataAbortException ( ) and TakePrefetchAbortException
* pseudocode .
*/
static void inject_abt ( struct kvm_vcpu * vcpu , bool is_pabt , unsigned long addr )
{
2012-10-03 11:17:02 +01:00
unsigned long new_lr_value ;
unsigned long new_spsr_value ;
unsigned long cpsr = * vcpu_cpsr ( vcpu ) ;
2013-01-20 18:28:09 -05:00
u32 sctlr = vcpu - > arch . cp15 [ c1_SCTLR ] ;
bool is_thumb = ( cpsr & PSR_T_BIT ) ;
u32 vect_offset ;
u32 return_offset = ( is_thumb ) ? 4 : 0 ;
bool is_lpae ;
new_spsr_value = cpsr ;
new_lr_value = * vcpu_pc ( vcpu ) + return_offset ;
* vcpu_cpsr ( vcpu ) = ( cpsr & ~ MODE_MASK ) | ABT_MODE ;
* vcpu_cpsr ( vcpu ) | = PSR_I_BIT | PSR_A_BIT ;
* vcpu_cpsr ( vcpu ) & = ~ ( PSR_IT_MASK | PSR_J_BIT | PSR_E_BIT | PSR_T_BIT ) ;
if ( sctlr & SCTLR_TE )
* vcpu_cpsr ( vcpu ) | = PSR_T_BIT ;
if ( sctlr & SCTLR_EE )
* vcpu_cpsr ( vcpu ) | = PSR_E_BIT ;
/* Note: These now point to ABT banked copies */
* vcpu_spsr ( vcpu ) = cpsr ;
* vcpu_reg ( vcpu , 14 ) = new_lr_value ;
if ( is_pabt )
vect_offset = 12 ;
else
vect_offset = 16 ;
/* Branch to exception vector */
* vcpu_pc ( vcpu ) = exc_vector_base ( vcpu ) + vect_offset ;
if ( is_pabt ) {
2013-09-11 18:34:22 +05:30
/* Set IFAR and IFSR */
2013-01-20 18:28:09 -05:00
vcpu - > arch . cp15 [ c6_IFAR ] = addr ;
is_lpae = ( vcpu - > arch . cp15 [ c2_TTBCR ] > > 31 ) ;
/* Always give debug fault for now - should give guest a clue */
if ( is_lpae )
vcpu - > arch . cp15 [ c5_IFSR ] = 1 < < 9 | 0x22 ;
else
vcpu - > arch . cp15 [ c5_IFSR ] = 2 ;
} else { /* !iabt */
/* Set DFAR and DFSR */
vcpu - > arch . cp15 [ c6_DFAR ] = addr ;
is_lpae = ( vcpu - > arch . cp15 [ c2_TTBCR ] > > 31 ) ;
/* Always give debug fault for now - should give guest a clue */
if ( is_lpae )
vcpu - > arch . cp15 [ c5_DFSR ] = 1 < < 9 | 0x22 ;
else
vcpu - > arch . cp15 [ c5_DFSR ] = 2 ;
}
}
/**
* kvm_inject_dabt - inject a data abort into the guest
* @ vcpu : The VCPU to receive the undefined exception
* @ addr : The address to report in the DFAR
*
* It is assumed that this code is called from the VCPU thread and that the
* VCPU therefore is not currently executing guest code .
*/
void kvm_inject_dabt ( struct kvm_vcpu * vcpu , unsigned long addr )
{
inject_abt ( vcpu , false , addr ) ;
}
/**
* kvm_inject_pabt - inject a prefetch abort into the guest
* @ vcpu : The VCPU to receive the undefined exception
* @ addr : The address to report in the DFAR
*
* It is assumed that this code is called from the VCPU thread and that the
* VCPU therefore is not currently executing guest code .
*/
void kvm_inject_pabt ( struct kvm_vcpu * vcpu , unsigned long addr )
{
inject_abt ( vcpu , true , addr ) ;
}