2015-07-03 15:01:34 +03:00
/*
* KVM Microsoft Hyper - V emulation
*
* derived from arch / x86 / kvm / x86 . c
*
* Copyright ( C ) 2006 Qumranet , Inc .
* Copyright ( C ) 2008 Qumranet , Inc .
* Copyright IBM Corporation , 2008
* Copyright 2010 Red Hat , Inc . and / or its affiliates .
* Copyright ( C ) 2015 Andrey Smetanin < asmetanin @ virtuozzo . com >
*
* Authors :
* Avi Kivity < avi @ qumranet . com >
* Yaniv Kamay < yaniv @ qumranet . com >
* Amit Shah < amit . shah @ qumranet . com >
* Ben - Ami Yassour < benami @ il . ibm . com >
* Andrey Smetanin < asmetanin @ virtuozzo . com >
*
* This work is licensed under the terms of the GNU GPL , version 2. See
* the COPYING file in the top - level directory .
*
*/
# include "x86.h"
# include "lapic.h"
# include "hyperv.h"
# include <linux/kvm_host.h>
# include <trace/events/kvm.h>
# include "trace.h"
static bool kvm_hv_msr_partition_wide ( u32 msr )
{
bool r = false ;
switch ( msr ) {
case HV_X64_MSR_GUEST_OS_ID :
case HV_X64_MSR_HYPERCALL :
case HV_X64_MSR_REFERENCE_TSC :
case HV_X64_MSR_TIME_REF_COUNT :
2015-07-03 15:01:37 +03:00
case HV_X64_MSR_CRASH_CTL :
case HV_X64_MSR_CRASH_P0 . . . HV_X64_MSR_CRASH_P4 :
2015-09-16 12:29:48 +03:00
case HV_X64_MSR_RESET :
2015-07-03 15:01:34 +03:00
r = true ;
break ;
}
return r ;
}
2015-07-03 15:01:37 +03:00
static int kvm_hv_msr_get_crash_data ( struct kvm_vcpu * vcpu ,
u32 index , u64 * pdata )
{
struct kvm_hv * hv = & vcpu - > kvm - > arch . hyperv ;
if ( WARN_ON_ONCE ( index > = ARRAY_SIZE ( hv - > hv_crash_param ) ) )
return - EINVAL ;
* pdata = hv - > hv_crash_param [ index ] ;
return 0 ;
}
static int kvm_hv_msr_get_crash_ctl ( struct kvm_vcpu * vcpu , u64 * pdata )
{
struct kvm_hv * hv = & vcpu - > kvm - > arch . hyperv ;
* pdata = hv - > hv_crash_ctl ;
return 0 ;
}
static int kvm_hv_msr_set_crash_ctl ( struct kvm_vcpu * vcpu , u64 data , bool host )
{
struct kvm_hv * hv = & vcpu - > kvm - > arch . hyperv ;
if ( host )
hv - > hv_crash_ctl = data & HV_X64_MSR_CRASH_CTL_NOTIFY ;
if ( ! host & & ( data & HV_X64_MSR_CRASH_CTL_NOTIFY ) ) {
vcpu_debug ( vcpu , " hv crash (0x%llx 0x%llx 0x%llx 0x%llx 0x%llx) \n " ,
hv - > hv_crash_param [ 0 ] ,
hv - > hv_crash_param [ 1 ] ,
hv - > hv_crash_param [ 2 ] ,
hv - > hv_crash_param [ 3 ] ,
hv - > hv_crash_param [ 4 ] ) ;
/* Send notification about crash to user space */
kvm_make_request ( KVM_REQ_HV_CRASH , vcpu ) ;
}
return 0 ;
}
static int kvm_hv_msr_set_crash_data ( struct kvm_vcpu * vcpu ,
u32 index , u64 data )
{
struct kvm_hv * hv = & vcpu - > kvm - > arch . hyperv ;
if ( WARN_ON_ONCE ( index > = ARRAY_SIZE ( hv - > hv_crash_param ) ) )
return - EINVAL ;
hv - > hv_crash_param [ index ] = data ;
return 0 ;
}
static int kvm_hv_set_msr_pw ( struct kvm_vcpu * vcpu , u32 msr , u64 data ,
bool host )
2015-07-03 15:01:34 +03:00
{
struct kvm * kvm = vcpu - > kvm ;
struct kvm_hv * hv = & kvm - > arch . hyperv ;
switch ( msr ) {
case HV_X64_MSR_GUEST_OS_ID :
hv - > hv_guest_os_id = data ;
/* setting guest os id to zero disables hypercall page */
if ( ! hv - > hv_guest_os_id )
hv - > hv_hypercall & = ~ HV_X64_MSR_HYPERCALL_ENABLE ;
break ;
case HV_X64_MSR_HYPERCALL : {
u64 gfn ;
unsigned long addr ;
u8 instructions [ 4 ] ;
/* if guest os id is not set hypercall should remain disabled */
if ( ! hv - > hv_guest_os_id )
break ;
if ( ! ( data & HV_X64_MSR_HYPERCALL_ENABLE ) ) {
hv - > hv_hypercall = data ;
break ;
}
gfn = data > > HV_X64_MSR_HYPERCALL_PAGE_ADDRESS_SHIFT ;
addr = gfn_to_hva ( kvm , gfn ) ;
if ( kvm_is_error_hva ( addr ) )
return 1 ;
kvm_x86_ops - > patch_hypercall ( vcpu , instructions ) ;
( ( unsigned char * ) instructions ) [ 3 ] = 0xc3 ; /* ret */
if ( __copy_to_user ( ( void __user * ) addr , instructions , 4 ) )
return 1 ;
hv - > hv_hypercall = data ;
mark_page_dirty ( kvm , gfn ) ;
break ;
}
case HV_X64_MSR_REFERENCE_TSC : {
u64 gfn ;
HV_REFERENCE_TSC_PAGE tsc_ref ;
memset ( & tsc_ref , 0 , sizeof ( tsc_ref ) ) ;
hv - > hv_tsc_page = data ;
if ( ! ( data & HV_X64_MSR_TSC_REFERENCE_ENABLE ) )
break ;
gfn = data > > HV_X64_MSR_TSC_REFERENCE_ADDRESS_SHIFT ;
if ( kvm_write_guest (
kvm ,
gfn < < HV_X64_MSR_TSC_REFERENCE_ADDRESS_SHIFT ,
& tsc_ref , sizeof ( tsc_ref ) ) )
return 1 ;
mark_page_dirty ( kvm , gfn ) ;
break ;
}
2015-07-03 15:01:37 +03:00
case HV_X64_MSR_CRASH_P0 . . . HV_X64_MSR_CRASH_P4 :
return kvm_hv_msr_set_crash_data ( vcpu ,
msr - HV_X64_MSR_CRASH_P0 ,
data ) ;
case HV_X64_MSR_CRASH_CTL :
return kvm_hv_msr_set_crash_ctl ( vcpu , data , host ) ;
2015-09-16 12:29:48 +03:00
case HV_X64_MSR_RESET :
if ( data = = 1 ) {
vcpu_debug ( vcpu , " hyper-v reset requested \n " ) ;
kvm_make_request ( KVM_REQ_HV_RESET , vcpu ) ;
}
break ;
2015-07-03 15:01:34 +03:00
default :
vcpu_unimpl ( vcpu , " Hyper-V uhandled wrmsr: 0x%x data 0x%llx \n " ,
msr , data ) ;
return 1 ;
}
return 0 ;
}
static int kvm_hv_set_msr ( struct kvm_vcpu * vcpu , u32 msr , u64 data )
{
struct kvm_vcpu_hv * hv = & vcpu - > arch . hyperv ;
switch ( msr ) {
case HV_X64_MSR_APIC_ASSIST_PAGE : {
u64 gfn ;
unsigned long addr ;
if ( ! ( data & HV_X64_MSR_APIC_ASSIST_PAGE_ENABLE ) ) {
hv - > hv_vapic = data ;
if ( kvm_lapic_enable_pv_eoi ( vcpu , 0 ) )
return 1 ;
break ;
}
gfn = data > > HV_X64_MSR_APIC_ASSIST_PAGE_ADDRESS_SHIFT ;
addr = kvm_vcpu_gfn_to_hva ( vcpu , gfn ) ;
if ( kvm_is_error_hva ( addr ) )
return 1 ;
if ( __clear_user ( ( void __user * ) addr , PAGE_SIZE ) )
return 1 ;
hv - > hv_vapic = data ;
kvm_vcpu_mark_page_dirty ( vcpu , gfn ) ;
if ( kvm_lapic_enable_pv_eoi ( vcpu ,
gfn_to_gpa ( gfn ) | KVM_MSR_ENABLED ) )
return 1 ;
break ;
}
case HV_X64_MSR_EOI :
return kvm_hv_vapic_msr_write ( vcpu , APIC_EOI , data ) ;
case HV_X64_MSR_ICR :
return kvm_hv_vapic_msr_write ( vcpu , APIC_ICR , data ) ;
case HV_X64_MSR_TPR :
return kvm_hv_vapic_msr_write ( vcpu , APIC_TASKPRI , data ) ;
default :
vcpu_unimpl ( vcpu , " Hyper-V uhandled wrmsr: 0x%x data 0x%llx \n " ,
msr , data ) ;
return 1 ;
}
return 0 ;
}
static int kvm_hv_get_msr_pw ( struct kvm_vcpu * vcpu , u32 msr , u64 * pdata )
{
u64 data = 0 ;
struct kvm * kvm = vcpu - > kvm ;
struct kvm_hv * hv = & kvm - > arch . hyperv ;
switch ( msr ) {
case HV_X64_MSR_GUEST_OS_ID :
data = hv - > hv_guest_os_id ;
break ;
case HV_X64_MSR_HYPERCALL :
data = hv - > hv_hypercall ;
break ;
case HV_X64_MSR_TIME_REF_COUNT : {
data =
div_u64 ( get_kernel_ns ( ) + kvm - > arch . kvmclock_offset , 100 ) ;
break ;
}
case HV_X64_MSR_REFERENCE_TSC :
data = hv - > hv_tsc_page ;
break ;
2015-07-03 15:01:37 +03:00
case HV_X64_MSR_CRASH_P0 . . . HV_X64_MSR_CRASH_P4 :
return kvm_hv_msr_get_crash_data ( vcpu ,
msr - HV_X64_MSR_CRASH_P0 ,
pdata ) ;
case HV_X64_MSR_CRASH_CTL :
return kvm_hv_msr_get_crash_ctl ( vcpu , pdata ) ;
2015-09-16 12:29:48 +03:00
case HV_X64_MSR_RESET :
data = 0 ;
break ;
2015-07-03 15:01:34 +03:00
default :
vcpu_unimpl ( vcpu , " Hyper-V unhandled rdmsr: 0x%x \n " , msr ) ;
return 1 ;
}
* pdata = data ;
return 0 ;
}
static int kvm_hv_get_msr ( struct kvm_vcpu * vcpu , u32 msr , u64 * pdata )
{
u64 data = 0 ;
struct kvm_vcpu_hv * hv = & vcpu - > arch . hyperv ;
switch ( msr ) {
case HV_X64_MSR_VP_INDEX : {
int r ;
struct kvm_vcpu * v ;
kvm_for_each_vcpu ( r , v , vcpu - > kvm ) {
if ( v = = vcpu ) {
data = r ;
break ;
}
}
break ;
}
case HV_X64_MSR_EOI :
return kvm_hv_vapic_msr_read ( vcpu , APIC_EOI , pdata ) ;
case HV_X64_MSR_ICR :
return kvm_hv_vapic_msr_read ( vcpu , APIC_ICR , pdata ) ;
case HV_X64_MSR_TPR :
return kvm_hv_vapic_msr_read ( vcpu , APIC_TASKPRI , pdata ) ;
case HV_X64_MSR_APIC_ASSIST_PAGE :
data = hv - > hv_vapic ;
break ;
default :
vcpu_unimpl ( vcpu , " Hyper-V unhandled rdmsr: 0x%x \n " , msr ) ;
return 1 ;
}
* pdata = data ;
return 0 ;
}
2015-07-03 15:01:37 +03:00
int kvm_hv_set_msr_common ( struct kvm_vcpu * vcpu , u32 msr , u64 data , bool host )
2015-07-03 15:01:34 +03:00
{
if ( kvm_hv_msr_partition_wide ( msr ) ) {
int r ;
mutex_lock ( & vcpu - > kvm - > lock ) ;
2015-07-03 15:01:37 +03:00
r = kvm_hv_set_msr_pw ( vcpu , msr , data , host ) ;
2015-07-03 15:01:34 +03:00
mutex_unlock ( & vcpu - > kvm - > lock ) ;
return r ;
} else
return kvm_hv_set_msr ( vcpu , msr , data ) ;
}
int kvm_hv_get_msr_common ( struct kvm_vcpu * vcpu , u32 msr , u64 * pdata )
{
if ( kvm_hv_msr_partition_wide ( msr ) ) {
int r ;
mutex_lock ( & vcpu - > kvm - > lock ) ;
r = kvm_hv_get_msr_pw ( vcpu , msr , pdata ) ;
mutex_unlock ( & vcpu - > kvm - > lock ) ;
return r ;
} else
return kvm_hv_get_msr ( vcpu , msr , pdata ) ;
}
bool kvm_hv_hypercall_enabled ( struct kvm * kvm )
{
return kvm - > arch . hyperv . hv_hypercall & HV_X64_MSR_HYPERCALL_ENABLE ;
}
int kvm_hv_hypercall ( struct kvm_vcpu * vcpu )
{
u64 param , ingpa , outgpa , ret ;
uint16_t code , rep_idx , rep_cnt , res = HV_STATUS_SUCCESS , rep_done = 0 ;
bool fast , longmode ;
/*
* hypercall generates UD from non zero cpl and real mode
* per HYPER - V spec
*/
if ( kvm_x86_ops - > get_cpl ( vcpu ) ! = 0 | | ! is_protmode ( vcpu ) ) {
kvm_queue_exception ( vcpu , UD_VECTOR ) ;
return 0 ;
}
longmode = is_64_bit_mode ( vcpu ) ;
if ( ! longmode ) {
param = ( ( u64 ) kvm_register_read ( vcpu , VCPU_REGS_RDX ) < < 32 ) |
( kvm_register_read ( vcpu , VCPU_REGS_RAX ) & 0xffffffff ) ;
ingpa = ( ( u64 ) kvm_register_read ( vcpu , VCPU_REGS_RBX ) < < 32 ) |
( kvm_register_read ( vcpu , VCPU_REGS_RCX ) & 0xffffffff ) ;
outgpa = ( ( u64 ) kvm_register_read ( vcpu , VCPU_REGS_RDI ) < < 32 ) |
( kvm_register_read ( vcpu , VCPU_REGS_RSI ) & 0xffffffff ) ;
}
# ifdef CONFIG_X86_64
else {
param = kvm_register_read ( vcpu , VCPU_REGS_RCX ) ;
ingpa = kvm_register_read ( vcpu , VCPU_REGS_RDX ) ;
outgpa = kvm_register_read ( vcpu , VCPU_REGS_R8 ) ;
}
# endif
code = param & 0xffff ;
fast = ( param > > 16 ) & 0x1 ;
rep_cnt = ( param > > 32 ) & 0xfff ;
rep_idx = ( param > > 48 ) & 0xfff ;
trace_kvm_hv_hypercall ( code , fast , rep_cnt , rep_idx , ingpa , outgpa ) ;
switch ( code ) {
case HV_X64_HV_NOTIFY_LONG_SPIN_WAIT :
kvm_vcpu_on_spin ( vcpu ) ;
break ;
default :
res = HV_STATUS_INVALID_HYPERCALL_CODE ;
break ;
}
ret = res | ( ( ( u64 ) rep_done & 0xfff ) < < 32 ) ;
if ( longmode ) {
kvm_register_write ( vcpu , VCPU_REGS_RAX , ret ) ;
} else {
kvm_register_write ( vcpu , VCPU_REGS_RDX , ret > > 32 ) ;
kvm_register_write ( vcpu , VCPU_REGS_RAX , ret & 0xffffffff ) ;
}
return 1 ;
}