2012-11-22 06:34:16 +04:00
/*
2014-06-26 23:11:34 +04:00
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* KVM / MIPS : Binary Patching for privileged instructions , reduces traps .
*
* Copyright ( C ) 2012 MIPS Technologies , Inc . All rights reserved .
* Authors : Sanjay Lal < sanjayl @ kymasys . com >
*/
2012-11-22 06:34:16 +04:00
# include <linux/errno.h>
# include <linux/err.h>
# include <linux/kvm_host.h>
# include <linux/module.h>
# include <linux/vmalloc.h>
# include <linux/fs.h>
# include <linux/bootmem.h>
2014-05-29 13:16:25 +04:00
# include <asm/cacheflush.h>
2012-11-22 06:34:16 +04:00
# include "kvm_mips_comm.h"
# define SYNCI_TEMPLATE 0x041f0000
# define SYNCI_BASE(x) (((x) >> 21) & 0x1f)
# define SYNCI_OFFSET ((x) & 0xffff)
# define LW_TEMPLATE 0x8c000000
# define CLEAR_TEMPLATE 0x00000020
# define SW_TEMPLATE 0xac000000
2014-06-26 23:11:34 +04:00
int kvm_mips_trans_cache_index ( uint32_t inst , uint32_t * opc ,
struct kvm_vcpu * vcpu )
2012-11-22 06:34:16 +04:00
{
int result = 0 ;
unsigned long kseg0_opc ;
uint32_t synci_inst = 0x0 ;
/* Replace the CACHE instruction, with a NOP */
kseg0_opc =
CKSEG0ADDR ( kvm_mips_translate_guest_kseg0_to_hpa
( vcpu , ( unsigned long ) opc ) ) ;
memcpy ( ( void * ) kseg0_opc , ( void * ) & synci_inst , sizeof ( uint32_t ) ) ;
2014-05-29 13:16:25 +04:00
local_flush_icache_range ( kseg0_opc , kseg0_opc + 32 ) ;
2012-11-22 06:34:16 +04:00
return result ;
}
/*
2014-06-26 23:11:34 +04:00
* Address based CACHE instructions are transformed into synci ( s ) . A little
* heavy for just D - cache invalidates , but avoids an expensive trap
2012-11-22 06:34:16 +04:00
*/
2014-06-26 23:11:34 +04:00
int kvm_mips_trans_cache_va ( uint32_t inst , uint32_t * opc ,
struct kvm_vcpu * vcpu )
2012-11-22 06:34:16 +04:00
{
int result = 0 ;
unsigned long kseg0_opc ;
uint32_t synci_inst = SYNCI_TEMPLATE , base , offset ;
base = ( inst > > 21 ) & 0x1f ;
offset = inst & 0xffff ;
synci_inst | = ( base < < 21 ) ;
synci_inst | = offset ;
kseg0_opc =
CKSEG0ADDR ( kvm_mips_translate_guest_kseg0_to_hpa
( vcpu , ( unsigned long ) opc ) ) ;
memcpy ( ( void * ) kseg0_opc , ( void * ) & synci_inst , sizeof ( uint32_t ) ) ;
2014-05-29 13:16:25 +04:00
local_flush_icache_range ( kseg0_opc , kseg0_opc + 32 ) ;
2012-11-22 06:34:16 +04:00
return result ;
}
2014-06-26 23:11:34 +04:00
int kvm_mips_trans_mfc0 ( uint32_t inst , uint32_t * opc , struct kvm_vcpu * vcpu )
2012-11-22 06:34:16 +04:00
{
int32_t rt , rd , sel ;
uint32_t mfc0_inst ;
unsigned long kseg0_opc , flags ;
rt = ( inst > > 16 ) & 0x1f ;
rd = ( inst > > 11 ) & 0x1f ;
sel = inst & 0x7 ;
if ( ( rd = = MIPS_CP0_ERRCTL ) & & ( sel = = 0 ) ) {
mfc0_inst = CLEAR_TEMPLATE ;
mfc0_inst | = ( ( rt & 0x1f ) < < 16 ) ;
} else {
mfc0_inst = LW_TEMPLATE ;
mfc0_inst | = ( ( rt & 0x1f ) < < 16 ) ;
mfc0_inst | =
offsetof ( struct mips_coproc ,
reg [ rd ] [ sel ] ) + offsetof ( struct kvm_mips_commpage ,
cop0 ) ;
}
if ( KVM_GUEST_KSEGX ( opc ) = = KVM_GUEST_KSEG0 ) {
kseg0_opc =
CKSEG0ADDR ( kvm_mips_translate_guest_kseg0_to_hpa
( vcpu , ( unsigned long ) opc ) ) ;
memcpy ( ( void * ) kseg0_opc , ( void * ) & mfc0_inst , sizeof ( uint32_t ) ) ;
2014-05-29 13:16:25 +04:00
local_flush_icache_range ( kseg0_opc , kseg0_opc + 32 ) ;
2012-11-22 06:34:16 +04:00
} else if ( KVM_GUEST_KSEGX ( ( unsigned long ) opc ) = = KVM_GUEST_KSEG23 ) {
local_irq_save ( flags ) ;
memcpy ( ( void * ) opc , ( void * ) & mfc0_inst , sizeof ( uint32_t ) ) ;
2014-05-29 13:16:25 +04:00
local_flush_icache_range ( ( unsigned long ) opc ,
( unsigned long ) opc + 32 ) ;
2012-11-22 06:34:16 +04:00
local_irq_restore ( flags ) ;
} else {
kvm_err ( " %s: Invalid address: %p \n " , __func__ , opc ) ;
return - EFAULT ;
}
return 0 ;
}
2014-06-26 23:11:34 +04:00
int kvm_mips_trans_mtc0 ( uint32_t inst , uint32_t * opc , struct kvm_vcpu * vcpu )
2012-11-22 06:34:16 +04:00
{
int32_t rt , rd , sel ;
uint32_t mtc0_inst = SW_TEMPLATE ;
unsigned long kseg0_opc , flags ;
rt = ( inst > > 16 ) & 0x1f ;
rd = ( inst > > 11 ) & 0x1f ;
sel = inst & 0x7 ;
mtc0_inst | = ( ( rt & 0x1f ) < < 16 ) ;
mtc0_inst | =
offsetof ( struct mips_coproc ,
reg [ rd ] [ sel ] ) + offsetof ( struct kvm_mips_commpage , cop0 ) ;
if ( KVM_GUEST_KSEGX ( opc ) = = KVM_GUEST_KSEG0 ) {
kseg0_opc =
CKSEG0ADDR ( kvm_mips_translate_guest_kseg0_to_hpa
( vcpu , ( unsigned long ) opc ) ) ;
memcpy ( ( void * ) kseg0_opc , ( void * ) & mtc0_inst , sizeof ( uint32_t ) ) ;
2014-05-29 13:16:25 +04:00
local_flush_icache_range ( kseg0_opc , kseg0_opc + 32 ) ;
2012-11-22 06:34:16 +04:00
} else if ( KVM_GUEST_KSEGX ( ( unsigned long ) opc ) = = KVM_GUEST_KSEG23 ) {
local_irq_save ( flags ) ;
memcpy ( ( void * ) opc , ( void * ) & mtc0_inst , sizeof ( uint32_t ) ) ;
2014-05-29 13:16:25 +04:00
local_flush_icache_range ( ( unsigned long ) opc ,
( unsigned long ) opc + 32 ) ;
2012-11-22 06:34:16 +04:00
local_irq_restore ( flags ) ;
} else {
kvm_err ( " %s: Invalid address: %p \n " , __func__ , opc ) ;
return - EFAULT ;
}
return 0 ;
}