Browse Source

[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>
tags/v1.20.1
Michael Brown 7 years ago
parent
commit
f3ba0fb5fd
2 changed files with 108 additions and 11 deletions
  1. 102
    11
      src/arch/x86/drivers/hyperv/hyperv.c
  2. 6
    0
      src/arch/x86/drivers/hyperv/hyperv.h

+ 102
- 11
src/arch/x86/drivers/hyperv/hyperv.c View File

@@ -39,6 +39,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
39 39
 #include <pic8259.h>
40 40
 #include <ipxe/malloc.h>
41 41
 #include <ipxe/device.h>
42
+#include <ipxe/timer.h>
42 43
 #include <ipxe/cpuid.h>
43 44
 #include <ipxe/msr.h>
44 45
 #include <ipxe/hyperv.h>
@@ -51,6 +52,12 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
51 52
  */
52 53
 #define HV_MESSAGE_MAX_WAIT_MS 1000
53 54
 
55
+/** Hyper-V timer frequency (fixed 10Mhz) */
56
+#define HV_TIMER_HZ 10000000
57
+
58
+/** Hyper-V timer scale factor (used to avoid 64-bit division) */
59
+#define HV_TIMER_SHIFT 18
60
+
54 61
 /**
55 62
  * Convert a Hyper-V status code to an iPXE status code
56 63
  *
@@ -145,22 +152,19 @@ static void hv_free_message ( struct hv_hypervisor *hv ) {
145 152
 /**
146 153
  * Check whether or not we are running in Hyper-V
147 154
  *
148
- * @v hv		Hyper-V hypervisor
149 155
  * @ret rc		Return status code
150 156
  */
151
-static int hv_check_hv ( struct hv_hypervisor *hv ) {
157
+static int hv_check_hv ( void ) {
152 158
 	struct x86_features features;
153 159
 	uint32_t interface_id;
154 160
 	uint32_t discard_ebx;
155 161
 	uint32_t discard_ecx;
156 162
 	uint32_t discard_edx;
157
-	uint32_t available;
158
-	uint32_t permissions;
159 163
 
160 164
 	/* Check for presence of a hypervisor (not necessarily Hyper-V) */
161 165
 	x86_features ( &features );
162 166
 	if ( ! ( features.intel.ecx & CPUID_FEATURES_INTEL_ECX_HYPERVISOR ) ) {
163
-		DBGC ( hv, "HV %p not running in a hypervisor\n", hv );
167
+		DBGC ( HV_INTERFACE_ID, "HV not running in a hypervisor\n" );
164 168
 		return -ENODEV;
165 169
 	}
166 170
 
@@ -168,11 +172,26 @@ static int hv_check_hv ( struct hv_hypervisor *hv ) {
168 172
 	cpuid ( HV_CPUID_INTERFACE_ID, &interface_id, &discard_ebx,
169 173
 		&discard_ecx, &discard_edx );
170 174
 	if ( interface_id != HV_INTERFACE_ID ) {
171
-		DBGC ( hv, "HV %p not running in Hyper-V (interface ID "
172
-		       "%#08x)\n", hv, interface_id );
175
+		DBGC ( HV_INTERFACE_ID, "HV not running in Hyper-V (interface "
176
+		       "ID %#08x)\n", interface_id );
173 177
 		return -ENODEV;
174 178
 	}
175 179
 
180
+	return 0;
181
+}
182
+
183
+/**
184
+ * Check required features
185
+ *
186
+ * @v hv		Hyper-V hypervisor
187
+ * @ret rc		Return status code
188
+ */
189
+static int hv_check_features ( struct hv_hypervisor *hv ) {
190
+	uint32_t available;
191
+	uint32_t permissions;
192
+	uint32_t discard_ecx;
193
+	uint32_t discard_edx;
194
+
176 195
 	/* Check that required features and privileges are available */
177 196
 	cpuid ( HV_CPUID_FEATURES, &available, &permissions, &discard_ecx,
178 197
 		&discard_edx );
@@ -509,6 +528,10 @@ static int hv_probe ( struct root_device *rootdev ) {
509 528
 	struct hv_hypervisor *hv;
510 529
 	int rc;
511 530
 
531
+	/* Check we are running in Hyper-V */
532
+	if ( ( rc = hv_check_hv() ) != 0 )
533
+		goto err_check_hv;
534
+
512 535
 	/* Allocate and initialise structure */
513 536
 	hv = zalloc ( sizeof ( *hv ) );
514 537
 	if ( ! hv ) {
@@ -516,9 +539,9 @@ static int hv_probe ( struct root_device *rootdev ) {
516 539
 		goto err_alloc;
517 540
 	}
518 541
 
519
-	/* Check we are running in Hyper-V */
520
-	if ( ( rc = hv_check_hv ( hv ) ) != 0 )
521
-		goto err_check_hv;
542
+	/* Check features */
543
+	if ( ( rc = hv_check_features ( hv ) ) != 0 )
544
+		goto err_check_features;
522 545
 
523 546
 	/* Allocate pages */
524 547
 	if ( ( rc = hv_alloc_pages ( hv, &hv->hypercall, &hv->synic.message,
@@ -555,9 +578,10 @@ static int hv_probe ( struct root_device *rootdev ) {
555 578
 	hv_free_pages ( hv, hv->hypercall, hv->synic.message, hv->synic.event,
556 579
 			NULL );
557 580
  err_alloc_pages:
558
- err_check_hv:
581
+ err_check_features:
559 582
 	free ( hv );
560 583
  err_alloc:
584
+ err_check_hv:
561 585
 	return rc;
562 586
 }
563 587
 
@@ -590,6 +614,73 @@ struct root_device hv_root_device __root_device = {
590 614
 	.driver = &hv_root_driver,
591 615
 };
592 616
 
617
+/**
618
+ * Probe timer
619
+ *
620
+ * @ret rc		Return status code
621
+ */
622
+static int hv_timer_probe ( void ) {
623
+	uint32_t available;
624
+	uint32_t discard_ebx;
625
+	uint32_t discard_ecx;
626
+	uint32_t discard_edx;
627
+	int rc;
628
+
629
+	/* Check we are running in Hyper-V */
630
+	if ( ( rc = hv_check_hv() ) != 0 )
631
+		return rc;
632
+
633
+	/* Check for available reference counter */
634
+	cpuid ( HV_CPUID_FEATURES, &available, &discard_ebx, &discard_ecx,
635
+		&discard_edx );
636
+	if ( ! ( available & HV_FEATURES_AVAIL_TIME_REF_COUNT_MSR ) ) {
637
+		DBGC ( HV_INTERFACE_ID, "HV has no time reference counter\n" );
638
+		return -ENODEV;
639
+	}
640
+
641
+	return 0;
642
+}
643
+
644
+/**
645
+ * Get current system time in ticks
646
+ *
647
+ * @ret ticks		Current time, in ticks
648
+ */
649
+static unsigned long hv_currticks ( void ) {
650
+
651
+	/* Calculate time using a combination of bit shifts and
652
+	 * multiplication (to avoid a 64-bit division).
653
+	 */
654
+	return ( ( rdmsr ( HV_X64_MSR_TIME_REF_COUNT ) >> HV_TIMER_SHIFT ) *
655
+		 ( TICKS_PER_SEC / ( HV_TIMER_HZ >> HV_TIMER_SHIFT ) ) );
656
+}
657
+
658
+/**
659
+ * Delay for a fixed number of microseconds
660
+ *
661
+ * @v usecs		Number of microseconds for which to delay
662
+ */
663
+static void hv_udelay ( unsigned long usecs ) {
664
+	uint32_t start;
665
+	uint32_t elapsed;
666
+	uint32_t threshold;
667
+
668
+	/* Spin until specified number of 10MHz ticks have elapsed */
669
+	start = rdmsr ( HV_X64_MSR_TIME_REF_COUNT );
670
+	threshold = ( usecs * ( HV_TIMER_HZ / 1000000 ) );
671
+	do {
672
+		elapsed = ( rdmsr ( HV_X64_MSR_TIME_REF_COUNT ) - start );
673
+	} while ( elapsed < threshold );
674
+}
675
+
676
+/** Hyper-V timer */
677
+struct timer hv_timer __timer ( TIMER_PREFERRED ) = {
678
+	.name = "Hyper-V",
679
+	.probe = hv_timer_probe,
680
+	.currticks = hv_currticks,
681
+	.udelay = hv_udelay,
682
+};
683
+
593 684
 /* Drag in objects via hv_root_device */
594 685
 REQUIRING_SYMBOL ( hv_root_device );
595 686
 

+ 6
- 0
src/arch/x86/drivers/hyperv/hyperv.h View File

@@ -21,6 +21,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
21 21
 /** Get hypervisor features */
22 22
 #define HV_CPUID_FEATURES 0x40000003UL
23 23
 
24
+/** Time reference counter MSR is available */
25
+#define HV_FEATURES_AVAIL_TIME_REF_COUNT_MSR 0x00000002UL
26
+
24 27
 /** SynIC MSRs are available */
25 28
 #define HV_FEATURES_AVAIL_SYNIC_MSR 0x00000004UL
26 29
 
@@ -39,6 +42,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
39 42
 /** Hypercall page MSR */
40 43
 #define HV_X64_MSR_HYPERCALL 0x40000001UL
41 44
 
45
+/** Time reference MSR */
46
+#define HV_X64_MSR_TIME_REF_COUNT 0x40000020UL
47
+
42 48
 /** SynIC control MSR */
43 49
 #define HV_X64_MSR_SCONTROL 0x40000080UL
44 50
 

Loading…
Cancel
Save