2009-10-30 08:47:11 +03:00
/*
* Copyright ( C ) 2009 SUSE Linux Products GmbH . All rights reserved .
*
* Authors :
* Alexander Graf < agraf @ suse . de >
* Kevin Wolf < mail @ kevin - wolf . de >
*
* 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 .
*/
# include <linux/kvm_host.h>
# include <asm/kvm_ppc.h>
# include <asm/kvm_book3s.h>
# include <asm/mmu-hash64.h>
# include <asm/machdep.h>
# include <asm/mmu_context.h>
# include <asm/hw_irq.h>
# define PTE_SIZE 12
# define VSID_ALL 0
/* #define DEBUG_MMU */
/* #define DEBUG_SLB */
# ifdef DEBUG_MMU
# define dprintk_mmu(a, ...) printk(KERN_INFO a, __VA_ARGS__)
# else
# define dprintk_mmu(a, ...) do { } while(0)
# endif
# ifdef DEBUG_SLB
# define dprintk_slb(a, ...) printk(KERN_INFO a, __VA_ARGS__)
# else
# define dprintk_slb(a, ...) do { } while(0)
# endif
static void invalidate_pte ( struct hpte_cache * pte )
{
dprintk_mmu ( " KVM: Flushing SPT %d: 0x%llx (0x%llx) -> 0x%llx \n " ,
i , pte - > pte . eaddr , pte - > pte . vpage , pte - > host_va ) ;
ppc_md . hpte_invalidate ( pte - > slot , pte - > host_va ,
MMU_PAGE_4K , MMU_SEGSIZE_256M ,
false ) ;
pte - > host_va = 0 ;
kvm_release_pfn_dirty ( pte - > pfn ) ;
}
void kvmppc_mmu_pte_flush ( struct kvm_vcpu * vcpu , u64 guest_ea , u64 ea_mask )
{
int i ;
dprintk_mmu ( " KVM: Flushing %d Shadow PTEs: 0x%llx & 0x%llx \n " ,
vcpu - > arch . hpte_cache_offset , guest_ea , ea_mask ) ;
BUG_ON ( vcpu - > arch . hpte_cache_offset > HPTEG_CACHE_NUM ) ;
guest_ea & = ea_mask ;
for ( i = 0 ; i < vcpu - > arch . hpte_cache_offset ; i + + ) {
struct hpte_cache * pte ;
pte = & vcpu - > arch . hpte_cache [ i ] ;
if ( ! pte - > host_va )
continue ;
if ( ( pte - > pte . eaddr & ea_mask ) = = guest_ea ) {
invalidate_pte ( pte ) ;
}
}
/* Doing a complete flush -> start from scratch */
if ( ! ea_mask )
vcpu - > arch . hpte_cache_offset = 0 ;
}
void kvmppc_mmu_pte_vflush ( struct kvm_vcpu * vcpu , u64 guest_vp , u64 vp_mask )
{
int i ;
dprintk_mmu ( " KVM: Flushing %d Shadow vPTEs: 0x%llx & 0x%llx \n " ,
vcpu - > arch . hpte_cache_offset , guest_vp , vp_mask ) ;
BUG_ON ( vcpu - > arch . hpte_cache_offset > HPTEG_CACHE_NUM ) ;
guest_vp & = vp_mask ;
for ( i = 0 ; i < vcpu - > arch . hpte_cache_offset ; i + + ) {
struct hpte_cache * pte ;
pte = & vcpu - > arch . hpte_cache [ i ] ;
if ( ! pte - > host_va )
continue ;
if ( ( pte - > pte . vpage & vp_mask ) = = guest_vp ) {
invalidate_pte ( pte ) ;
}
}
}
void kvmppc_mmu_pte_pflush ( struct kvm_vcpu * vcpu , u64 pa_start , u64 pa_end )
{
int i ;
dprintk_mmu ( " KVM: Flushing %d Shadow pPTEs: 0x%llx & 0x%llx \n " ,
vcpu - > arch . hpte_cache_offset , guest_pa , pa_mask ) ;
BUG_ON ( vcpu - > arch . hpte_cache_offset > HPTEG_CACHE_NUM ) ;
for ( i = 0 ; i < vcpu - > arch . hpte_cache_offset ; i + + ) {
struct hpte_cache * pte ;
pte = & vcpu - > arch . hpte_cache [ i ] ;
if ( ! pte - > host_va )
continue ;
if ( ( pte - > pte . raddr > = pa_start ) & &
( pte - > pte . raddr < pa_end ) ) {
invalidate_pte ( pte ) ;
}
}
}
struct kvmppc_pte * kvmppc_mmu_find_pte ( struct kvm_vcpu * vcpu , u64 ea , bool data )
{
int i ;
u64 guest_vp ;
guest_vp = vcpu - > arch . mmu . ea_to_vp ( vcpu , ea , false ) ;
for ( i = 0 ; i < vcpu - > arch . hpte_cache_offset ; i + + ) {
struct hpte_cache * pte ;
pte = & vcpu - > arch . hpte_cache [ i ] ;
if ( ! pte - > host_va )
continue ;
if ( pte - > pte . vpage = = guest_vp )
return & pte - > pte ;
}
return NULL ;
}
static int kvmppc_mmu_hpte_cache_next ( struct kvm_vcpu * vcpu )
{
if ( vcpu - > arch . hpte_cache_offset = = HPTEG_CACHE_NUM )
kvmppc_mmu_pte_flush ( vcpu , 0 , 0 ) ;
return vcpu - > arch . hpte_cache_offset + + ;
}
/* We keep 512 gvsid->hvsid entries, mapping the guest ones to the array using
* a hash , so we don ' t waste cycles on looping */
static u16 kvmppc_sid_hash ( struct kvm_vcpu * vcpu , u64 gvsid )
{
return ( u16 ) ( ( ( gvsid > > ( SID_MAP_BITS * 7 ) ) & SID_MAP_MASK ) ^
( ( gvsid > > ( SID_MAP_BITS * 6 ) ) & SID_MAP_MASK ) ^
( ( gvsid > > ( SID_MAP_BITS * 5 ) ) & SID_MAP_MASK ) ^
( ( gvsid > > ( SID_MAP_BITS * 4 ) ) & SID_MAP_MASK ) ^
( ( gvsid > > ( SID_MAP_BITS * 3 ) ) & SID_MAP_MASK ) ^
( ( gvsid > > ( SID_MAP_BITS * 2 ) ) & SID_MAP_MASK ) ^
( ( gvsid > > ( SID_MAP_BITS * 1 ) ) & SID_MAP_MASK ) ^
( ( gvsid > > ( SID_MAP_BITS * 0 ) ) & SID_MAP_MASK ) ) ;
}
static struct kvmppc_sid_map * find_sid_vsid ( struct kvm_vcpu * vcpu , u64 gvsid )
{
struct kvmppc_sid_map * map ;
u16 sid_map_mask ;
if ( vcpu - > arch . msr & MSR_PR )
gvsid | = VSID_PR ;
sid_map_mask = kvmppc_sid_hash ( vcpu , gvsid ) ;
map = & to_book3s ( vcpu ) - > sid_map [ sid_map_mask ] ;
if ( map - > guest_vsid = = gvsid ) {
dprintk_slb ( " SLB: Searching 0x%llx -> 0x%llx \n " ,
gvsid , map - > host_vsid ) ;
return map ;
}
map = & to_book3s ( vcpu ) - > sid_map [ SID_MAP_MASK - sid_map_mask ] ;
if ( map - > guest_vsid = = gvsid ) {
dprintk_slb ( " SLB: Searching 0x%llx -> 0x%llx \n " ,
gvsid , map - > host_vsid ) ;
return map ;
}
dprintk_slb ( " SLB: Searching 0x%llx -> not found \n " , gvsid ) ;
return NULL ;
}
int kvmppc_mmu_map_page ( struct kvm_vcpu * vcpu , struct kvmppc_pte * orig_pte )
{
pfn_t hpaddr ;
ulong hash , hpteg , va ;
u64 vsid ;
int ret ;
int rflags = 0x192 ;
int vflags = 0 ;
int attempt = 0 ;
struct kvmppc_sid_map * map ;
/* Get host physical address for gpa */
hpaddr = gfn_to_pfn ( vcpu - > kvm , orig_pte - > raddr > > PAGE_SHIFT ) ;
if ( kvm_is_error_hva ( hpaddr ) ) {
printk ( KERN_INFO " Couldn't get guest page for gfn %llx! \n " , orig_pte - > eaddr ) ;
return - EINVAL ;
}
hpaddr < < = PAGE_SHIFT ;
# if PAGE_SHIFT == 12
# elif PAGE_SHIFT == 16
hpaddr | = orig_pte - > raddr & 0xf000 ;
# else
# error Unknown page size
# endif
/* and write the mapping ea -> hpa into the pt */
vcpu - > arch . mmu . esid_to_vsid ( vcpu , orig_pte - > eaddr > > SID_SHIFT , & vsid ) ;
map = find_sid_vsid ( vcpu , vsid ) ;
if ( ! map ) {
kvmppc_mmu_map_segment ( vcpu , orig_pte - > eaddr ) ;
map = find_sid_vsid ( vcpu , vsid ) ;
}
BUG_ON ( ! map ) ;
vsid = map - > host_vsid ;
va = hpt_va ( orig_pte - > eaddr , vsid , MMU_SEGSIZE_256M ) ;
if ( ! orig_pte - > may_write )
rflags | = HPTE_R_PP ;
else
mark_page_dirty ( vcpu - > kvm , orig_pte - > raddr > > PAGE_SHIFT ) ;
if ( ! orig_pte - > may_execute )
rflags | = HPTE_R_N ;
hash = hpt_hash ( va , PTE_SIZE , MMU_SEGSIZE_256M ) ;
map_again :
hpteg = ( ( hash & htab_hash_mask ) * HPTES_PER_GROUP ) ;
/* In case we tried normal mapping already, let's nuke old entries */
if ( attempt > 1 )
if ( ppc_md . hpte_remove ( hpteg ) < 0 )
return - 1 ;
ret = ppc_md . hpte_insert ( hpteg , va , hpaddr , rflags , vflags , MMU_PAGE_4K , MMU_SEGSIZE_256M ) ;
if ( ret < 0 ) {
/* If we couldn't map a primary PTE, try a secondary */
hash = ~ hash ;
2010-02-19 13:00:46 +03:00
vflags ^ = HPTE_V_SECONDARY ;
2009-10-30 08:47:11 +03:00
attempt + + ;
goto map_again ;
} else {
int hpte_id = kvmppc_mmu_hpte_cache_next ( vcpu ) ;
struct hpte_cache * pte = & vcpu - > arch . hpte_cache [ hpte_id ] ;
dprintk_mmu ( " KVM: %c%c Map 0x%llx: [%lx] 0x%lx (0x%llx) -> %lx \n " ,
( ( rflags & HPTE_R_PP ) = = 3 ) ? ' - ' : ' w ' ,
( rflags & HPTE_R_N ) ? ' - ' : ' x ' ,
orig_pte - > eaddr , hpteg , va , orig_pte - > vpage , hpaddr ) ;
pte - > slot = hpteg + ( ret & 7 ) ;
pte - > host_va = va ;
pte - > pte = * orig_pte ;
pte - > pfn = hpaddr > > PAGE_SHIFT ;
}
return 0 ;
}
static struct kvmppc_sid_map * create_sid_map ( struct kvm_vcpu * vcpu , u64 gvsid )
{
struct kvmppc_sid_map * map ;
struct kvmppc_vcpu_book3s * vcpu_book3s = to_book3s ( vcpu ) ;
u16 sid_map_mask ;
static int backwards_map = 0 ;
if ( vcpu - > arch . msr & MSR_PR )
gvsid | = VSID_PR ;
/* We might get collisions that trap in preceding order, so let's
map them differently */
sid_map_mask = kvmppc_sid_hash ( vcpu , gvsid ) ;
if ( backwards_map )
sid_map_mask = SID_MAP_MASK - sid_map_mask ;
map = & to_book3s ( vcpu ) - > sid_map [ sid_map_mask ] ;
/* Make sure we're taking the other map next time */
backwards_map = ! backwards_map ;
/* Uh-oh ... out of mappings. Let's flush! */
if ( vcpu_book3s - > vsid_next = = vcpu_book3s - > vsid_max ) {
vcpu_book3s - > vsid_next = vcpu_book3s - > vsid_first ;
memset ( vcpu_book3s - > sid_map , 0 ,
sizeof ( struct kvmppc_sid_map ) * SID_MAP_NUM ) ;
kvmppc_mmu_pte_flush ( vcpu , 0 , 0 ) ;
kvmppc_mmu_flush_segments ( vcpu ) ;
}
map - > host_vsid = vcpu_book3s - > vsid_next + + ;
map - > guest_vsid = gvsid ;
map - > valid = true ;
return map ;
}
static int kvmppc_mmu_next_segment ( struct kvm_vcpu * vcpu , ulong esid )
{
int i ;
int max_slb_size = 64 ;
int found_inval = - 1 ;
int r ;
if ( ! get_paca ( ) - > kvm_slb_max )
get_paca ( ) - > kvm_slb_max = 1 ;
/* Are we overwriting? */
for ( i = 1 ; i < get_paca ( ) - > kvm_slb_max ; i + + ) {
if ( ! ( get_paca ( ) - > kvm_slb [ i ] . esid & SLB_ESID_V ) )
found_inval = i ;
else if ( ( get_paca ( ) - > kvm_slb [ i ] . esid & ESID_MASK ) = = esid )
return i ;
}
/* Found a spare entry that was invalidated before */
if ( found_inval > 0 )
return found_inval ;
/* No spare invalid entry, so create one */
if ( mmu_slb_size < 64 )
max_slb_size = mmu_slb_size ;
/* Overflowing -> purge */
if ( ( get_paca ( ) - > kvm_slb_max ) = = max_slb_size )
kvmppc_mmu_flush_segments ( vcpu ) ;
r = get_paca ( ) - > kvm_slb_max ;
get_paca ( ) - > kvm_slb_max + + ;
return r ;
}
int kvmppc_mmu_map_segment ( struct kvm_vcpu * vcpu , ulong eaddr )
{
u64 esid = eaddr > > SID_SHIFT ;
u64 slb_esid = ( eaddr & ESID_MASK ) | SLB_ESID_V ;
u64 slb_vsid = SLB_VSID_USER ;
u64 gvsid ;
int slb_index ;
struct kvmppc_sid_map * map ;
slb_index = kvmppc_mmu_next_segment ( vcpu , eaddr & ESID_MASK ) ;
if ( vcpu - > arch . mmu . esid_to_vsid ( vcpu , esid , & gvsid ) ) {
/* Invalidate an entry */
get_paca ( ) - > kvm_slb [ slb_index ] . esid = 0 ;
return - ENOENT ;
}
map = find_sid_vsid ( vcpu , gvsid ) ;
if ( ! map )
map = create_sid_map ( vcpu , gvsid ) ;
map - > guest_esid = esid ;
slb_vsid | = ( map - > host_vsid < < 12 ) ;
slb_vsid & = ~ SLB_VSID_KP ;
slb_esid | = slb_index ;
get_paca ( ) - > kvm_slb [ slb_index ] . esid = slb_esid ;
get_paca ( ) - > kvm_slb [ slb_index ] . vsid = slb_vsid ;
dprintk_slb ( " slbmte %#llx, %#llx \n " , slb_vsid , slb_esid ) ;
return 0 ;
}
void kvmppc_mmu_flush_segments ( struct kvm_vcpu * vcpu )
{
get_paca ( ) - > kvm_slb_max = 1 ;
get_paca ( ) - > kvm_slb [ 0 ] . esid = 0 ;
}
void kvmppc_mmu_destroy ( struct kvm_vcpu * vcpu )
{
kvmppc_mmu_pte_flush ( vcpu , 0 , 0 ) ;
}