Skip to content

Commit

Permalink
[hyperv] Provide timer based on the 10MHz time reference count MSR
Browse files Browse the repository at this point in the history
When running on AMD platforms, the legacy hardware emulation is
extremely unreliable.  In particular, the IRQ0 timer interrupt is
likely to simply stop working, resulting in a total failure of any
code that relies on timers (such as DHCP retransmission attempts).

Work around this by using the 10MHz time counter provided by Hyper-V
via an MSR.  (This timer can be tested in KVM via the command-line
option "-cpu host,hv_time".)

Signed-off-by: Michael Brown <mcb30@ipxe.org>
  • Loading branch information
mcb30 committed Jan 26, 2017
1 parent 302f1ee commit f3ba0fb
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 11 deletions.
113 changes: 102 additions & 11 deletions src/arch/x86/drivers/hyperv/hyperv.c
Expand Up @@ -39,6 +39,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <pic8259.h>
#include <ipxe/malloc.h>
#include <ipxe/device.h>
#include <ipxe/timer.h>
#include <ipxe/cpuid.h>
#include <ipxe/msr.h>
#include <ipxe/hyperv.h>
Expand All @@ -51,6 +52,12 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*/
#define HV_MESSAGE_MAX_WAIT_MS 1000

/** Hyper-V timer frequency (fixed 10Mhz) */
#define HV_TIMER_HZ 10000000

/** Hyper-V timer scale factor (used to avoid 64-bit division) */
#define HV_TIMER_SHIFT 18

/**
* Convert a Hyper-V status code to an iPXE status code
*
Expand Down Expand Up @@ -145,34 +152,46 @@ static void hv_free_message ( struct hv_hypervisor *hv ) {
/**
* Check whether or not we are running in Hyper-V
*
* @v hv Hyper-V hypervisor
* @ret rc Return status code
*/
static int hv_check_hv ( struct hv_hypervisor *hv ) {
static int hv_check_hv ( void ) {
struct x86_features features;
uint32_t interface_id;
uint32_t discard_ebx;
uint32_t discard_ecx;
uint32_t discard_edx;
uint32_t available;
uint32_t permissions;

/* Check for presence of a hypervisor (not necessarily Hyper-V) */
x86_features ( &features );
if ( ! ( features.intel.ecx & CPUID_FEATURES_INTEL_ECX_HYPERVISOR ) ) {
DBGC ( hv, "HV %p not running in a hypervisor\n", hv );
DBGC ( HV_INTERFACE_ID, "HV not running in a hypervisor\n" );
return -ENODEV;
}

/* Check that hypervisor is Hyper-V */
cpuid ( HV_CPUID_INTERFACE_ID, &interface_id, &discard_ebx,
&discard_ecx, &discard_edx );
if ( interface_id != HV_INTERFACE_ID ) {
DBGC ( hv, "HV %p not running in Hyper-V (interface ID "
"%#08x)\n", hv, interface_id );
DBGC ( HV_INTERFACE_ID, "HV not running in Hyper-V (interface "
"ID %#08x)\n", interface_id );
return -ENODEV;
}

return 0;
}

/**
* Check required features
*
* @v hv Hyper-V hypervisor
* @ret rc Return status code
*/
static int hv_check_features ( struct hv_hypervisor *hv ) {
uint32_t available;
uint32_t permissions;
uint32_t discard_ecx;
uint32_t discard_edx;

/* Check that required features and privileges are available */
cpuid ( HV_CPUID_FEATURES, &available, &permissions, &discard_ecx,
&discard_edx );
Expand Down Expand Up @@ -509,16 +528,20 @@ static int hv_probe ( struct root_device *rootdev ) {
struct hv_hypervisor *hv;
int rc;

/* Check we are running in Hyper-V */
if ( ( rc = hv_check_hv() ) != 0 )
goto err_check_hv;

/* Allocate and initialise structure */
hv = zalloc ( sizeof ( *hv ) );
if ( ! hv ) {
rc = -ENOMEM;
goto err_alloc;
}

/* Check we are running in Hyper-V */
if ( ( rc = hv_check_hv ( hv ) ) != 0 )
goto err_check_hv;
/* Check features */
if ( ( rc = hv_check_features ( hv ) ) != 0 )
goto err_check_features;

/* Allocate pages */
if ( ( rc = hv_alloc_pages ( hv, &hv->hypercall, &hv->synic.message,
Expand Down Expand Up @@ -555,9 +578,10 @@ static int hv_probe ( struct root_device *rootdev ) {
hv_free_pages ( hv, hv->hypercall, hv->synic.message, hv->synic.event,
NULL );
err_alloc_pages:
err_check_hv:
err_check_features:
free ( hv );
err_alloc:
err_check_hv:
return rc;
}

Expand Down Expand Up @@ -590,6 +614,73 @@ struct root_device hv_root_device __root_device = {
.driver = &hv_root_driver,
};

/**
* Probe timer
*
* @ret rc Return status code
*/
static int hv_timer_probe ( void ) {
uint32_t available;
uint32_t discard_ebx;
uint32_t discard_ecx;
uint32_t discard_edx;
int rc;

/* Check we are running in Hyper-V */
if ( ( rc = hv_check_hv() ) != 0 )
return rc;

/* Check for available reference counter */
cpuid ( HV_CPUID_FEATURES, &available, &discard_ebx, &discard_ecx,
&discard_edx );
if ( ! ( available & HV_FEATURES_AVAIL_TIME_REF_COUNT_MSR ) ) {
DBGC ( HV_INTERFACE_ID, "HV has no time reference counter\n" );
return -ENODEV;
}

return 0;
}

/**
* Get current system time in ticks
*
* @ret ticks Current time, in ticks
*/
static unsigned long hv_currticks ( void ) {

/* Calculate time using a combination of bit shifts and
* multiplication (to avoid a 64-bit division).
*/
return ( ( rdmsr ( HV_X64_MSR_TIME_REF_COUNT ) >> HV_TIMER_SHIFT ) *
( TICKS_PER_SEC / ( HV_TIMER_HZ >> HV_TIMER_SHIFT ) ) );
}

/**
* Delay for a fixed number of microseconds
*
* @v usecs Number of microseconds for which to delay
*/
static void hv_udelay ( unsigned long usecs ) {
uint32_t start;
uint32_t elapsed;
uint32_t threshold;

/* Spin until specified number of 10MHz ticks have elapsed */
start = rdmsr ( HV_X64_MSR_TIME_REF_COUNT );
threshold = ( usecs * ( HV_TIMER_HZ / 1000000 ) );
do {
elapsed = ( rdmsr ( HV_X64_MSR_TIME_REF_COUNT ) - start );
} while ( elapsed < threshold );
}

/** Hyper-V timer */
struct timer hv_timer __timer ( TIMER_PREFERRED ) = {
.name = "Hyper-V",
.probe = hv_timer_probe,
.currticks = hv_currticks,
.udelay = hv_udelay,
};

/* Drag in objects via hv_root_device */
REQUIRING_SYMBOL ( hv_root_device );

Expand Down
6 changes: 6 additions & 0 deletions src/arch/x86/drivers/hyperv/hyperv.h
Expand Up @@ -21,6 +21,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** Get hypervisor features */
#define HV_CPUID_FEATURES 0x40000003UL

/** Time reference counter MSR is available */
#define HV_FEATURES_AVAIL_TIME_REF_COUNT_MSR 0x00000002UL

/** SynIC MSRs are available */
#define HV_FEATURES_AVAIL_SYNIC_MSR 0x00000004UL

Expand All @@ -39,6 +42,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** Hypercall page MSR */
#define HV_X64_MSR_HYPERCALL 0x40000001UL

/** Time reference MSR */
#define HV_X64_MSR_TIME_REF_COUNT 0x40000020UL

/** SynIC control MSR */
#define HV_X64_MSR_SCONTROL 0x40000080UL

Expand Down

0 comments on commit f3ba0fb

Please sign in to comment.