2013-01-21 03:28:13 +04:00
/*
* Copyright ( C ) 2012 - ARM Ltd
* Author : Marc Zyngier < marc . zyngier @ arm . 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 , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/kvm_host.h>
# include <linux/wait.h>
2013-10-18 21:19:03 +04:00
# include <asm/cputype.h>
2013-01-21 03:28:13 +04:00
# include <asm/kvm_emulate.h>
# include <asm/kvm_psci.h>
/*
* This is an implementation of the Power State Coordination Interface
* as described in ARM document number ARM DEN 0022 A .
*/
static void kvm_psci_vcpu_off ( struct kvm_vcpu * vcpu )
{
vcpu - > arch . pause = true ;
}
static unsigned long kvm_psci_vcpu_on ( struct kvm_vcpu * source_vcpu )
{
struct kvm * kvm = source_vcpu - > kvm ;
2013-10-18 21:19:03 +04:00
struct kvm_vcpu * vcpu = NULL , * tmp ;
2013-01-21 03:28:13 +04:00
wait_queue_head_t * wq ;
unsigned long cpu_id ;
2013-10-18 21:19:03 +04:00
unsigned long mpidr ;
2013-01-21 03:28:13 +04:00
phys_addr_t target_pc ;
2013-10-18 21:19:03 +04:00
int i ;
2013-01-21 03:28:13 +04:00
cpu_id = * vcpu_reg ( source_vcpu , 1 ) ;
if ( vcpu_mode_is_32bit ( source_vcpu ) )
cpu_id & = ~ ( ( u32 ) 0 ) ;
2013-10-18 21:19:03 +04:00
kvm_for_each_vcpu ( i , tmp , kvm ) {
mpidr = kvm_vcpu_get_mpidr ( tmp ) ;
if ( ( mpidr & MPIDR_HWID_BITMASK ) = = ( cpu_id & MPIDR_HWID_BITMASK ) ) {
vcpu = tmp ;
break ;
}
}
if ( ! vcpu )
2013-01-21 03:28:13 +04:00
return KVM_PSCI_RET_INVAL ;
target_pc = * vcpu_reg ( source_vcpu , 2 ) ;
wq = kvm_arch_vcpu_wq ( vcpu ) ;
if ( ! waitqueue_active ( wq ) )
return KVM_PSCI_RET_INVAL ;
kvm_reset_vcpu ( vcpu ) ;
/* Gracefully handle Thumb2 entry point */
if ( vcpu_mode_is_32bit ( vcpu ) & & ( target_pc & 1 ) ) {
target_pc & = ~ ( ( phys_addr_t ) 1 ) ;
vcpu_set_thumb ( vcpu ) ;
}
2013-11-05 18:12:15 +04:00
/* Propagate caller endianness */
if ( kvm_vcpu_is_be ( source_vcpu ) )
kvm_vcpu_set_be ( vcpu ) ;
2013-01-21 03:28:13 +04:00
* vcpu_pc ( vcpu ) = target_pc ;
vcpu - > arch . pause = false ;
smp_mb ( ) ; /* Make sure the above is visible */
wake_up_interruptible ( wq ) ;
return KVM_PSCI_RET_SUCCESS ;
}
/**
* kvm_psci_call - handle PSCI call if r0 value is in range
* @ vcpu : Pointer to the VCPU struct
*
2013-05-01 20:49:28 +04:00
* Handle PSCI calls from guests through traps from HVC instructions .
2013-01-21 03:28:13 +04:00
* The calling convention is similar to SMC calls to the secure world where
* the function number is placed in r0 and this function returns true if the
* function number specified in r0 is withing the PSCI range , and false
* otherwise .
*/
bool kvm_psci_call ( struct kvm_vcpu * vcpu )
{
unsigned long psci_fn = * vcpu_reg ( vcpu , 0 ) & ~ ( ( u32 ) 0 ) ;
unsigned long val ;
switch ( psci_fn ) {
case KVM_PSCI_FN_CPU_OFF :
kvm_psci_vcpu_off ( vcpu ) ;
val = KVM_PSCI_RET_SUCCESS ;
break ;
case KVM_PSCI_FN_CPU_ON :
val = kvm_psci_vcpu_on ( vcpu ) ;
break ;
case KVM_PSCI_FN_CPU_SUSPEND :
case KVM_PSCI_FN_MIGRATE :
val = KVM_PSCI_RET_NI ;
break ;
default :
return false ;
}
* vcpu_reg ( vcpu , 0 ) = val ;
return true ;
}