2009-10-30 08:47:12 +03:00
/*
* 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 .
*
* Copyright SUSE Linux Products GmbH 2009
*
* Authors : Alexander Graf < agraf @ suse . de >
*/
# include <linux/types.h>
# include <linux/string.h>
# include <linux/kvm.h>
# include <linux/kvm_host.h>
# include <linux/highmem.h>
# include <asm/tlbflush.h>
# include <asm/kvm_ppc.h>
# include <asm/kvm_book3s.h>
/* #define DEBUG_MMU */
# ifdef DEBUG_MMU
# define dprintk(X...) printk(KERN_INFO X)
# else
# define dprintk(X...) do { } while(0)
# endif
static void kvmppc_mmu_book3s_64_reset_msr ( struct kvm_vcpu * vcpu )
{
kvmppc_set_msr ( vcpu , MSR_SF ) ;
}
static struct kvmppc_slb * kvmppc_mmu_book3s_64_find_slbe (
2011-06-29 04:17:33 +04:00
struct kvm_vcpu * vcpu ,
2009-10-30 08:47:12 +03:00
gva_t eaddr )
{
int i ;
u64 esid = GET_ESID ( eaddr ) ;
u64 esid_1t = GET_ESID_1T ( eaddr ) ;
2011-06-29 04:17:33 +04:00
for ( i = 0 ; i < vcpu - > arch . slb_nr ; i + + ) {
2009-10-30 08:47:12 +03:00
u64 cmp_esid = esid ;
2011-06-29 04:17:33 +04:00
if ( ! vcpu - > arch . slb [ i ] . valid )
2009-10-30 08:47:12 +03:00
continue ;
2011-06-29 04:17:33 +04:00
if ( vcpu - > arch . slb [ i ] . tb )
2009-10-30 08:47:12 +03:00
cmp_esid = esid_1t ;
2011-06-29 04:17:33 +04:00
if ( vcpu - > arch . slb [ i ] . esid = = cmp_esid )
return & vcpu - > arch . slb [ i ] ;
2009-10-30 08:47:12 +03:00
}
dprintk ( " KVM: No SLB entry found for 0x%lx [%llx | %llx] \n " ,
eaddr , esid , esid_1t ) ;
2011-06-29 04:17:33 +04:00
for ( i = 0 ; i < vcpu - > arch . slb_nr ; i + + ) {
if ( vcpu - > arch . slb [ i ] . vsid )
2010-01-10 05:27:47 +03:00
dprintk ( " %d: %c%c%c %llx %llx \n " , i ,
2011-06-29 04:17:33 +04:00
vcpu - > arch . slb [ i ] . valid ? ' v ' : ' ' ,
vcpu - > arch . slb [ i ] . large ? ' l ' : ' ' ,
vcpu - > arch . slb [ i ] . tb ? ' t ' : ' ' ,
vcpu - > arch . slb [ i ] . esid ,
vcpu - > arch . slb [ i ] . vsid ) ;
2009-10-30 08:47:12 +03:00
}
return NULL ;
}
static u64 kvmppc_mmu_book3s_64_ea_to_vp ( struct kvm_vcpu * vcpu , gva_t eaddr ,
bool data )
{
struct kvmppc_slb * slb ;
2011-06-29 04:17:33 +04:00
slb = kvmppc_mmu_book3s_64_find_slbe ( vcpu , eaddr ) ;
2009-10-30 08:47:12 +03:00
if ( ! slb )
return 0 ;
2010-01-10 05:27:47 +03:00
if ( slb - > tb )
2009-10-30 08:47:12 +03:00
return ( ( ( u64 ) eaddr > > 12 ) & 0xfffffff ) |
( ( ( u64 ) slb - > vsid ) < < 28 ) ;
return ( ( ( u64 ) eaddr > > 12 ) & 0xffff ) | ( ( ( u64 ) slb - > vsid ) < < 16 ) ;
}
static int kvmppc_mmu_book3s_64_get_pagesize ( struct kvmppc_slb * slbe )
{
return slbe - > large ? 24 : 12 ;
}
static u32 kvmppc_mmu_book3s_64_get_page ( struct kvmppc_slb * slbe , gva_t eaddr )
{
int p = kvmppc_mmu_book3s_64_get_pagesize ( slbe ) ;
return ( ( eaddr & 0xfffffff ) > > p ) ;
}
static hva_t kvmppc_mmu_book3s_64_get_pteg (
struct kvmppc_vcpu_book3s * vcpu_book3s ,
struct kvmppc_slb * slbe , gva_t eaddr ,
bool second )
{
u64 hash , pteg , htabsize ;
u32 page ;
hva_t r ;
page = kvmppc_mmu_book3s_64_get_page ( slbe , eaddr ) ;
htabsize = ( ( 1 < < ( ( vcpu_book3s - > sdr1 & 0x1f ) + 11 ) ) - 1 ) ;
hash = slbe - > vsid ^ page ;
if ( second )
hash = ~ hash ;
hash & = ( ( 1ULL < < 39ULL ) - 1ULL ) ;
hash & = htabsize ;
hash < < = 7ULL ;
pteg = vcpu_book3s - > sdr1 & 0xfffffffffffc0000ULL ;
pteg | = hash ;
dprintk ( " MMU: page=0x%x sdr1=0x%llx pteg=0x%llx vsid=0x%llx \n " ,
page , vcpu_book3s - > sdr1 , pteg , slbe - > vsid ) ;
2011-08-08 17:06:55 +04:00
/* When running a PAPR guest, SDR1 contains a HVA address instead
of a GPA */
if ( vcpu_book3s - > vcpu . arch . papr_enabled )
r = pteg ;
else
r = gfn_to_hva ( vcpu_book3s - > vcpu . kvm , pteg > > PAGE_SHIFT ) ;
2009-10-30 08:47:12 +03:00
if ( kvm_is_error_hva ( r ) )
return r ;
return r | ( pteg & ~ PAGE_MASK ) ;
}
static u64 kvmppc_mmu_book3s_64_get_avpn ( struct kvmppc_slb * slbe , gva_t eaddr )
{
int p = kvmppc_mmu_book3s_64_get_pagesize ( slbe ) ;
u64 avpn ;
avpn = kvmppc_mmu_book3s_64_get_page ( slbe , eaddr ) ;
avpn | = slbe - > vsid < < ( 28 - p ) ;
if ( p < 24 )
avpn > > = ( ( 80 - p ) - 56 ) - 8 ;
else
avpn < < = 8 ;
return avpn ;
}
static int kvmppc_mmu_book3s_64_xlate ( struct kvm_vcpu * vcpu , gva_t eaddr ,
struct kvmppc_pte * gpte , bool data )
{
struct kvmppc_vcpu_book3s * vcpu_book3s = to_book3s ( vcpu ) ;
struct kvmppc_slb * slbe ;
hva_t ptegp ;
u64 pteg [ 16 ] ;
u64 avpn = 0 ;
int i ;
u8 key = 0 ;
bool found = false ;
bool perm_err = false ;
int second = 0 ;
2010-07-29 16:47:54 +04:00
ulong mp_ea = vcpu - > arch . magic_page_ea ;
/* Magic page override */
if ( unlikely ( mp_ea ) & &
unlikely ( ( eaddr & ~ 0xfffULL ) = = ( mp_ea & ~ 0xfffULL ) ) & &
! ( vcpu - > arch . shared - > msr & MSR_PR ) ) {
gpte - > eaddr = eaddr ;
gpte - > vpage = kvmppc_mmu_book3s_64_ea_to_vp ( vcpu , eaddr , data ) ;
gpte - > raddr = vcpu - > arch . magic_page_pa | ( gpte - > raddr & 0xfff ) ;
gpte - > raddr & = KVM_PAM ;
gpte - > may_execute = true ;
gpte - > may_read = true ;
gpte - > may_write = true ;
return 0 ;
}
2009-10-30 08:47:12 +03:00
2011-06-29 04:17:33 +04:00
slbe = kvmppc_mmu_book3s_64_find_slbe ( vcpu , eaddr ) ;
2009-10-30 08:47:12 +03:00
if ( ! slbe )
goto no_seg_found ;
do_second :
ptegp = kvmppc_mmu_book3s_64_get_pteg ( vcpu_book3s , slbe , eaddr , second ) ;
if ( kvm_is_error_hva ( ptegp ) )
goto no_page_found ;
avpn = kvmppc_mmu_book3s_64_get_avpn ( slbe , eaddr ) ;
if ( copy_from_user ( pteg , ( void __user * ) ptegp , sizeof ( pteg ) ) ) {
printk ( KERN_ERR " KVM can't copy data from 0x%lx! \n " , ptegp ) ;
goto no_page_found ;
}
2010-07-29 16:47:43 +04:00
if ( ( vcpu - > arch . shared - > msr & MSR_PR ) & & slbe - > Kp )
2009-10-30 08:47:12 +03:00
key = 4 ;
2010-07-29 16:47:43 +04:00
else if ( ! ( vcpu - > arch . shared - > msr & MSR_PR ) & & slbe - > Ks )
2009-10-30 08:47:12 +03:00
key = 4 ;
for ( i = 0 ; i < 16 ; i + = 2 ) {
u64 v = pteg [ i ] ;
u64 r = pteg [ i + 1 ] ;
/* Valid check */
if ( ! ( v & HPTE_V_VALID ) )
continue ;
/* Hash check */
if ( ( v & HPTE_V_SECONDARY ) ! = second )
continue ;
/* AVPN compare */
if ( HPTE_V_AVPN_VAL ( avpn ) = = HPTE_V_AVPN_VAL ( v ) ) {
u8 pp = ( r & HPTE_R_PP ) | key ;
int eaddr_mask = 0xFFF ;
gpte - > eaddr = eaddr ;
gpte - > vpage = kvmppc_mmu_book3s_64_ea_to_vp ( vcpu ,
eaddr ,
data ) ;
if ( slbe - > large )
eaddr_mask = 0xFFFFFF ;
gpte - > raddr = ( r & HPTE_R_RPN ) | ( eaddr & eaddr_mask ) ;
gpte - > may_execute = ( ( r & HPTE_R_N ) ? false : true ) ;
gpte - > may_read = false ;
gpte - > may_write = false ;
switch ( pp ) {
case 0 :
case 1 :
case 2 :
case 6 :
gpte - > may_write = true ;
/* fall through */
case 3 :
case 5 :
case 7 :
gpte - > may_read = true ;
break ;
}
if ( ! gpte - > may_read ) {
perm_err = true ;
continue ;
}
dprintk ( " KVM MMU: Translated 0x%lx [0x%llx] -> 0x%llx "
2010-04-20 04:49:46 +04:00
" -> 0x%lx \n " ,
2009-10-30 08:47:12 +03:00
eaddr , avpn , gpte - > vpage , gpte - > raddr ) ;
found = true ;
break ;
}
}
/* Update PTE R and C bits, so the guest's swapper knows we used the
* page */
if ( found ) {
u32 oldr = pteg [ i + 1 ] ;
if ( gpte - > may_read ) {
/* Set the accessed flag */
pteg [ i + 1 ] | = HPTE_R_R ;
}
if ( gpte - > may_write ) {
/* Set the dirty flag */
pteg [ i + 1 ] | = HPTE_R_C ;
} else {
dprintk ( " KVM: Mapping read-only page! \n " ) ;
}
/* Write back into the PTEG */
if ( pteg [ i + 1 ] ! = oldr )
copy_to_user ( ( void __user * ) ptegp , pteg , sizeof ( pteg ) ) ;
return 0 ;
} else {
dprintk ( " KVM MMU: No PTE found (ea=0x%lx sdr1=0x%llx "
" ptegp=0x%lx) \n " ,
eaddr , to_book3s ( vcpu ) - > sdr1 , ptegp ) ;
for ( i = 0 ; i < 16 ; i + = 2 )
dprintk ( " %02d: 0x%llx - 0x%llx (0x%llx) \n " ,
i , pteg [ i ] , pteg [ i + 1 ] , avpn ) ;
if ( ! second ) {
second = HPTE_V_SECONDARY ;
goto do_second ;
}
}
no_page_found :
if ( perm_err )
return - EPERM ;
return - ENOENT ;
no_seg_found :
dprintk ( " KVM MMU: Trigger segment fault \n " ) ;
return - EINVAL ;
}
static void kvmppc_mmu_book3s_64_slbmte ( struct kvm_vcpu * vcpu , u64 rs , u64 rb )
{
struct kvmppc_vcpu_book3s * vcpu_book3s ;
u64 esid , esid_1t ;
int slb_nr ;
struct kvmppc_slb * slbe ;
dprintk ( " KVM MMU: slbmte(0x%llx, 0x%llx) \n " , rs , rb ) ;
vcpu_book3s = to_book3s ( vcpu ) ;
esid = GET_ESID ( rb ) ;
esid_1t = GET_ESID_1T ( rb ) ;
slb_nr = rb & 0xfff ;
2011-06-29 04:17:33 +04:00
if ( slb_nr > vcpu - > arch . slb_nr )
2009-10-30 08:47:12 +03:00
return ;
2011-06-29 04:17:33 +04:00
slbe = & vcpu - > arch . slb [ slb_nr ] ;
2009-10-30 08:47:12 +03:00
slbe - > large = ( rs & SLB_VSID_L ) ? 1 : 0 ;
2010-01-10 05:27:47 +03:00
slbe - > tb = ( rs & SLB_VSID_B_1T ) ? 1 : 0 ;
slbe - > esid = slbe - > tb ? esid_1t : esid ;
2009-10-30 08:47:12 +03:00
slbe - > vsid = rs > > 12 ;
slbe - > valid = ( rb & SLB_ESID_V ) ? 1 : 0 ;
slbe - > Ks = ( rs & SLB_VSID_KS ) ? 1 : 0 ;
slbe - > Kp = ( rs & SLB_VSID_KP ) ? 1 : 0 ;
slbe - > nx = ( rs & SLB_VSID_N ) ? 1 : 0 ;
slbe - > class = ( rs & SLB_VSID_C ) ? 1 : 0 ;
slbe - > orige = rb & ( ESID_MASK | SLB_ESID_V ) ;
slbe - > origv = rs ;
/* Map the new segment */
kvmppc_mmu_map_segment ( vcpu , esid < < SID_SHIFT ) ;
}
static u64 kvmppc_mmu_book3s_64_slbmfee ( struct kvm_vcpu * vcpu , u64 slb_nr )
{
struct kvmppc_slb * slbe ;
2011-06-29 04:17:33 +04:00
if ( slb_nr > vcpu - > arch . slb_nr )
2009-10-30 08:47:12 +03:00
return 0 ;
2011-06-29 04:17:33 +04:00
slbe = & vcpu - > arch . slb [ slb_nr ] ;
2009-10-30 08:47:12 +03:00
return slbe - > orige ;
}
static u64 kvmppc_mmu_book3s_64_slbmfev ( struct kvm_vcpu * vcpu , u64 slb_nr )
{
struct kvmppc_slb * slbe ;
2011-06-29 04:17:33 +04:00
if ( slb_nr > vcpu - > arch . slb_nr )
2009-10-30 08:47:12 +03:00
return 0 ;
2011-06-29 04:17:33 +04:00
slbe = & vcpu - > arch . slb [ slb_nr ] ;
2009-10-30 08:47:12 +03:00
return slbe - > origv ;
}
static void kvmppc_mmu_book3s_64_slbie ( struct kvm_vcpu * vcpu , u64 ea )
{
struct kvmppc_slb * slbe ;
dprintk ( " KVM MMU: slbie(0x%llx) \n " , ea ) ;
2011-06-29 04:17:33 +04:00
slbe = kvmppc_mmu_book3s_64_find_slbe ( vcpu , ea ) ;
2009-10-30 08:47:12 +03:00
if ( ! slbe )
return ;
dprintk ( " KVM MMU: slbie(0x%llx, 0x%llx) \n " , ea , slbe - > esid ) ;
slbe - > valid = false ;
kvmppc_mmu_map_segment ( vcpu , ea ) ;
}
static void kvmppc_mmu_book3s_64_slbia ( struct kvm_vcpu * vcpu )
{
int i ;
dprintk ( " KVM MMU: slbia() \n " ) ;
2011-06-29 04:17:33 +04:00
for ( i = 1 ; i < vcpu - > arch . slb_nr ; i + + )
vcpu - > arch . slb [ i ] . valid = false ;
2009-10-30 08:47:12 +03:00
2010-07-29 16:47:43 +04:00
if ( vcpu - > arch . shared - > msr & MSR_IR ) {
2009-10-30 08:47:12 +03:00
kvmppc_mmu_flush_segments ( vcpu ) ;
2010-04-16 02:11:40 +04:00
kvmppc_mmu_map_segment ( vcpu , kvmppc_get_pc ( vcpu ) ) ;
2009-10-30 08:47:12 +03:00
}
}
static void kvmppc_mmu_book3s_64_mtsrin ( struct kvm_vcpu * vcpu , u32 srnum ,
ulong value )
{
u64 rb = 0 , rs = 0 ;
2009-12-19 20:07:39 +03:00
/*
* According to Book3 2.01 mtsrin is implemented as :
*
* The SLB entry specified by ( RB ) 32 : 35 is loaded from register
* RS , as follows .
*
* SLBE Bit Source SLB Field
*
* 0 : 31 0x0000 _0000 ESID - 0 : 31
* 32 : 35 ( RB ) 32 : 35 ESID - 32 : 35
* 36 0 b1 V
* 37 : 61 0x00 _0000 | | 0 b0 VSID - 0 : 24
* 62 : 88 ( RS ) 37 : 63 VSID - 25 : 51
* 89 : 91 ( RS ) 33 : 35 Ks Kp N
* 92 ( RS ) 36 L ( ( RS ) 36 must be 0 b0 )
* 93 0 b0 C
*/
dprintk ( " KVM MMU: mtsrin(0x%x, 0x%lx) \n " , srnum , value ) ;
2009-10-30 08:47:12 +03:00
/* ESID = srnum */
rb | = ( srnum & 0xf ) < < 28 ;
/* Set the valid bit */
rb | = 1 < < 27 ;
/* Index = ESID */
rb | = srnum ;
/* VSID = VSID */
rs | = ( value & 0xfffffff ) < < 12 ;
/* flags = flags */
2009-12-19 20:07:39 +03:00
rs | = ( ( value > > 28 ) & 0x7 ) < < 9 ;
2009-10-30 08:47:12 +03:00
kvmppc_mmu_book3s_64_slbmte ( vcpu , rs , rb ) ;
}
static void kvmppc_mmu_book3s_64_tlbie ( struct kvm_vcpu * vcpu , ulong va ,
bool large )
{
u64 mask = 0xFFFFFFFFFULL ;
dprintk ( " KVM MMU: tlbie(0x%lx) \n " , va ) ;
if ( large )
mask = 0xFFFFFF000ULL ;
kvmppc_mmu_pte_vflush ( vcpu , va > > 12 , mask ) ;
}
2010-04-20 04:49:46 +04:00
static int kvmppc_mmu_book3s_64_esid_to_vsid ( struct kvm_vcpu * vcpu , ulong esid ,
2009-10-30 08:47:12 +03:00
u64 * vsid )
{
2010-04-20 04:49:48 +04:00
ulong ea = esid < < SID_SHIFT ;
struct kvmppc_slb * slb ;
u64 gvsid = esid ;
2010-07-29 16:47:54 +04:00
ulong mp_ea = vcpu - > arch . magic_page_ea ;
2010-04-20 04:49:48 +04:00
2010-07-29 16:47:43 +04:00
if ( vcpu - > arch . shared - > msr & ( MSR_DR | MSR_IR ) ) {
2011-06-29 04:17:33 +04:00
slb = kvmppc_mmu_book3s_64_find_slbe ( vcpu , ea ) ;
2010-04-20 04:49:48 +04:00
if ( slb )
gvsid = slb - > vsid ;
}
2010-07-29 16:47:43 +04:00
switch ( vcpu - > arch . shared - > msr & ( MSR_DR | MSR_IR ) ) {
2009-10-30 08:47:12 +03:00
case 0 :
2010-04-20 04:49:48 +04:00
* vsid = VSID_REAL | esid ;
2009-10-30 08:47:12 +03:00
break ;
case MSR_IR :
2010-04-20 04:49:48 +04:00
* vsid = VSID_REAL_IR | gvsid ;
2009-10-30 08:47:12 +03:00
break ;
case MSR_DR :
2010-04-20 04:49:48 +04:00
* vsid = VSID_REAL_DR | gvsid ;
2009-10-30 08:47:12 +03:00
break ;
case MSR_DR | MSR_IR :
2010-04-20 04:49:48 +04:00
if ( ! slb )
2010-07-29 16:47:54 +04:00
goto no_slb ;
2009-10-30 08:47:12 +03:00
2010-04-20 04:49:48 +04:00
* vsid = gvsid ;
2009-10-30 08:47:12 +03:00
break ;
default :
BUG ( ) ;
break ;
}
2010-07-29 16:47:43 +04:00
if ( vcpu - > arch . shared - > msr & MSR_PR )
2010-04-20 04:49:51 +04:00
* vsid | = VSID_PR ;
2009-10-30 08:47:12 +03:00
return 0 ;
2010-07-29 16:47:54 +04:00
no_slb :
/* Catch magic page case */
if ( unlikely ( mp_ea ) & &
unlikely ( esid = = ( mp_ea > > SID_SHIFT ) ) & &
! ( vcpu - > arch . shared - > msr & MSR_PR ) ) {
* vsid = VSID_REAL | esid ;
return 0 ;
}
return - EINVAL ;
2009-10-30 08:47:12 +03:00
}
static bool kvmppc_mmu_book3s_64_is_dcbz32 ( struct kvm_vcpu * vcpu )
{
return ( to_book3s ( vcpu ) - > hid [ 5 ] & 0x80 ) ;
}
void kvmppc_mmu_book3s_64_init ( struct kvm_vcpu * vcpu )
{
struct kvmppc_mmu * mmu = & vcpu - > arch . mmu ;
mmu - > mfsrin = NULL ;
mmu - > mtsrin = kvmppc_mmu_book3s_64_mtsrin ;
mmu - > slbmte = kvmppc_mmu_book3s_64_slbmte ;
mmu - > slbmfee = kvmppc_mmu_book3s_64_slbmfee ;
mmu - > slbmfev = kvmppc_mmu_book3s_64_slbmfev ;
mmu - > slbie = kvmppc_mmu_book3s_64_slbie ;
mmu - > slbia = kvmppc_mmu_book3s_64_slbia ;
mmu - > xlate = kvmppc_mmu_book3s_64_xlate ;
mmu - > reset_msr = kvmppc_mmu_book3s_64_reset_msr ;
mmu - > tlbie = kvmppc_mmu_book3s_64_tlbie ;
mmu - > esid_to_vsid = kvmppc_mmu_book3s_64_esid_to_vsid ;
mmu - > ea_to_vp = kvmppc_mmu_book3s_64_ea_to_vp ;
mmu - > is_dcbz32 = kvmppc_mmu_book3s_64_is_dcbz32 ;
2009-11-30 06:02:02 +03:00
vcpu - > arch . hflags | = BOOK3S_HFLAG_SLB ;
2009-10-30 08:47:12 +03:00
}