]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[hyperv] Provide timer based on the 10MHz time reference count MSR
authorMichael Brown <mcb30@ipxe.org>
Thu, 26 Jan 2017 08:03:11 +0000 (08:03 +0000)
committerMichael Brown <mcb30@ipxe.org>
Thu, 26 Jan 2017 08:19:26 +0000 (08:19 +0000)
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>
src/arch/x86/drivers/hyperv/hyperv.c
src/arch/x86/drivers/hyperv/hyperv.h

index f73829bd598b14aeefc44ccec5f806fbb44eeb8f..cc6e38688d61b1396f38d9493e3b537dd1f7df68 100644 (file)
@@ -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 );
 
index 0d09beb44b6750b325e3c5ee7d27c9ffefb4b995..08031fc6d1bb3320a410931b1990b623fdcd6ff5 100644 (file)
@@ -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