summaryrefslogtreecommitdiffstats
path: root/src/arch
diff options
context:
space:
mode:
authorMichael Brown2017-01-26 09:03:11 +0100
committerMichael Brown2017-01-26 09:19:26 +0100
commitf3ba0fb5fdd866961b5dfc0d227af60b25753f0e (patch)
treef2473a45a68f0e992b37798b6cef9cb31eb36b87 /src/arch
parent[time] Allow timer to be selected at runtime (diff)
downloadipxe-f3ba0fb5fdd866961b5dfc0d227af60b25753f0e.tar.gz
ipxe-f3ba0fb5fdd866961b5dfc0d227af60b25753f0e.tar.xz
ipxe-f3ba0fb5fdd866961b5dfc0d227af60b25753f0e.zip
[hyperv] Provide timer based on the 10MHz time reference count MSR
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>
Diffstat (limited to 'src/arch')
-rw-r--r--src/arch/x86/drivers/hyperv/hyperv.c113
-rw-r--r--src/arch/x86/drivers/hyperv/hyperv.h6
2 files changed, 108 insertions, 11 deletions
diff --git a/src/arch/x86/drivers/hyperv/hyperv.c b/src/arch/x86/drivers/hyperv/hyperv.c
index f73829bd..cc6e3868 100644
--- a/src/arch/x86/drivers/hyperv/hyperv.c
+++ b/src/arch/x86/drivers/hyperv/hyperv.c
@@ -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>
@@ -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
*
@@ -145,22 +152,19 @@ 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;
}
@@ -168,11 +172,26 @@ static int hv_check_hv ( struct hv_hypervisor *hv ) {
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 );
@@ -509,6 +528,10 @@ 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 ) {
@@ -516,9 +539,9 @@ static int hv_probe ( struct root_device *rootdev ) {
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,
@@ -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;
}
@@ -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 );
diff --git a/src/arch/x86/drivers/hyperv/hyperv.h b/src/arch/x86/drivers/hyperv/hyperv.h
index 0d09beb4..08031fc6 100644
--- a/src/arch/x86/drivers/hyperv/hyperv.h
+++ b/src/arch/x86/drivers/hyperv/hyperv.h
@@ -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
@@ -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