nalika / rpms / grub2

Forked from rpms/grub2 2 years ago
Clone

Blame SOURCES/0056-Make-pmtimer-tsc-calibration-not-take-51-seconds-to-.patch

8e15ce
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
8e15ce
From: Peter Jones <pjones@redhat.com>
8e15ce
Date: Tue, 7 Nov 2017 17:12:17 -0500
8e15ce
Subject: [PATCH] Make pmtimer tsc calibration not take 51 seconds to fail.
8e15ce
8e15ce
On my laptop running at 2.4GHz, if I run a VM where tsc calibration
8e15ce
using pmtimer will fail presuming a broken pmtimer, it takes ~51 seconds
8e15ce
to do so (as measured with the stopwatch on my phone), with a tsc delta
8e15ce
of 0x1cd1c85300, or around 125 billion cycles.
8e15ce
8e15ce
If instead of trying to wait for 5-200ms to show up on the pmtimer, we try
8e15ce
to wait for 5-200us, it decides it's broken in ~0x2626aa0 TSCs, aka ~2.4
8e15ce
million cycles, or more or less instantly.
8e15ce
8e15ce
Additionally, this reading the pmtimer was returning 0xffffffff anyway,
8e15ce
and that's obviously an invalid return.  I've added a check for that and
8e15ce
0 so we don't bother waiting for the test if what we're seeing is dead
8e15ce
pins with no response at all.
8e15ce
8e15ce
If "debug" is includes "pmtimer", you will see one of the following
8e15ce
three outcomes.  If pmtimer gives all 0 or all 1 bits, you will see:
8e15ce
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 1
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 2
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 3
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 4
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 5
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 6
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 7
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 8
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 9
8e15ce
kern/i386/tsc_pmtimer.c:77: pmtimer: 0xffffff bad_reads: 10
8e15ce
kern/i386/tsc_pmtimer.c:78: timer is broken; giving up.
8e15ce
8e15ce
This outcome was tested using qemu+kvm with UEFI (OVMF) firmware and
8e15ce
these options: -machine pc-q35-2.10 -cpu Broadwell-noTSX
8e15ce
8e15ce
If pmtimer gives any other bit patterns but is not actually marching
8e15ce
forward fast enough to use for clock calibration, you will see:
8e15ce
8e15ce
kern/i386/tsc_pmtimer.c:121: pmtimer delta is 0x0 (1904 iterations)
8e15ce
kern/i386/tsc_pmtimer.c:124: tsc delta is implausible: 0x2626aa0
8e15ce
8e15ce
This outcome was tested using grub compiled with GRUB_PMTIMER_IGNORE_BAD_READS
8e15ce
defined (so as not to trip the bad read test) using qemu+kvm with UEFI
8e15ce
(OVMF) firmware, and these options: -machine pc-q35-2.10 -cpu Broadwell-noTSX
8e15ce
8e15ce
If pmtimer actually works, you'll see something like:
8e15ce
8e15ce
kern/i386/tsc_pmtimer.c:121: pmtimer delta is 0x0 (1904 iterations)
8e15ce
kern/i386/tsc_pmtimer.c:124: tsc delta is implausible: 0x2626aa0
8e15ce
8e15ce
This outcome was tested using qemu+kvm with UEFI (OVMF) firmware, and
8e15ce
these options: -machine pc-i440fx-2.4 -cpu Broadwell-noTSX
8e15ce
8e15ce
I've also tested this outcome on a real Intel Xeon E3-1275v3 on an Intel
8e15ce
Server Board S1200V3RPS using the SDV.RP.B8 "Release" build here:
8e15ce
https://firmware.intel.com/sites/default/files/UEFIDevKit_S1200RP_vB8.zip
8e15ce
8e15ce
Signed-off-by: Peter Jones <pjones@redhat.com>
8e15ce
---
8e15ce
 grub-core/kern/i386/tsc_pmtimer.c | 109 +++++++++++++++++++++++++++++++-------
8e15ce
 1 file changed, 89 insertions(+), 20 deletions(-)
8e15ce
8e15ce
diff --git a/grub-core/kern/i386/tsc_pmtimer.c b/grub-core/kern/i386/tsc_pmtimer.c
b35c50
index c9c3616997..ca15c3aacd 100644
8e15ce
--- a/grub-core/kern/i386/tsc_pmtimer.c
8e15ce
+++ b/grub-core/kern/i386/tsc_pmtimer.c
8e15ce
@@ -28,40 +28,101 @@
8e15ce
 #include <grub/acpi.h>
8e15ce
 #include <grub/cpu/io.h>
8e15ce
 
8e15ce
+/*
8e15ce
+ * Define GRUB_PMTIMER_IGNORE_BAD_READS if you're trying to test a timer that's
8e15ce
+ * present but doesn't keep time well.
8e15ce
+ */
8e15ce
+// #define GRUB_PMTIMER_IGNORE_BAD_READS
8e15ce
+
8e15ce
 grub_uint64_t
8e15ce
 grub_pmtimer_wait_count_tsc (grub_port_t pmtimer,
8e15ce
 			     grub_uint16_t num_pm_ticks)
8e15ce
 {
8e15ce
   grub_uint32_t start;
8e15ce
-  grub_uint32_t last;
8e15ce
-  grub_uint32_t cur, end;
8e15ce
+  grub_uint64_t cur, end;
8e15ce
   grub_uint64_t start_tsc;
8e15ce
   grub_uint64_t end_tsc;
8e15ce
-  int num_iter = 0;
8e15ce
+  unsigned int num_iter = 0;
8e15ce
+#ifndef GRUB_PMTIMER_IGNORE_BAD_READS
8e15ce
+  int bad_reads = 0;
8e15ce
+#endif
8e15ce
 
8e15ce
-  start = grub_inl (pmtimer) & 0xffffff;
8e15ce
-  last = start;
8e15ce
+  /*
8e15ce
+   * Some timers are 24-bit and some are 32-bit, but it doesn't make much
8e15ce
+   * difference to us.  Caring which one we have isn't really worth it since
8e15ce
+   * the low-order digits will give us enough data to calibrate TSC.  So just
8e15ce
+   * mask the top-order byte off.
8e15ce
+   */
8e15ce
+  cur = start = grub_inl (pmtimer) & 0xffffffUL;
8e15ce
   end = start + num_pm_ticks;
8e15ce
   start_tsc = grub_get_tsc ();
8e15ce
   while (1)
8e15ce
     {
8e15ce
-      cur = grub_inl (pmtimer) & 0xffffff;
8e15ce
-      if (cur < last)
8e15ce
-	cur |= 0x1000000;
8e15ce
-      num_iter++;
8e15ce
+      cur &= 0xffffffffff000000ULL;
8e15ce
+      cur |= grub_inl (pmtimer) & 0xffffffUL;
8e15ce
+
8e15ce
+      end_tsc = grub_get_tsc();
8e15ce
+
8e15ce
+#ifndef GRUB_PMTIMER_IGNORE_BAD_READS
8e15ce
+      /*
8e15ce
+       * If we get 10 reads in a row that are obviously dead pins, there's no
8e15ce
+       * reason to do this thousands of times.
8e15ce
+       */
8e15ce
+      if (cur == 0xffffffUL || cur == 0)
8e15ce
+	{
8e15ce
+	  bad_reads++;
8e15ce
+	  grub_dprintf ("pmtimer",
8e15ce
+			"pmtimer: 0x%"PRIxGRUB_UINT64_T" bad_reads: %d\n",
8e15ce
+			cur, bad_reads);
8e15ce
+	  grub_dprintf ("pmtimer", "timer is broken; giving up.\n");
8e15ce
+
8e15ce
+	  if (bad_reads == 10)
8e15ce
+	    return 0;
8e15ce
+	}
8e15ce
+#endif
8e15ce
+
8e15ce
+      if (cur < start)
8e15ce
+	cur += 0x1000000;
8e15ce
+
8e15ce
       if (cur >= end)
8e15ce
 	{
8e15ce
-	  end_tsc = grub_get_tsc ();
8e15ce
+	  grub_dprintf ("pmtimer", "pmtimer delta is 0x%"PRIxGRUB_UINT64_T"\n",
8e15ce
+			cur - start);
8e15ce
+	  grub_dprintf ("pmtimer", "tsc delta is 0x%"PRIxGRUB_UINT64_T"\n",
8e15ce
+			end_tsc - start_tsc);
8e15ce
 	  return end_tsc - start_tsc;
8e15ce
 	}
8e15ce
-      /* Check for broken PM timer.
8e15ce
-	 50000000 TSCs is between 5 ms (10GHz) and 200 ms (250 MHz)
8e15ce
-	 if after this time we still don't have 1 ms on pmtimer, then
8e15ce
-	 pmtimer is broken.
8e15ce
+
8e15ce
+      /*
8e15ce
+       * Check for broken PM timer.  1ms at 10GHz should be 1E+7 TSCs; at
8e15ce
+       * 250MHz it should be 2.5E6.  So if after 4E+7 TSCs on a 10GHz machine,
8e15ce
+       * we should have seen pmtimer show 4ms of change (i.e. cur =~
8e15ce
+       * start+14320); on a 250MHz machine that should be 16ms (start+57280).
8e15ce
+       * If after this a time we still don't have 1ms on pmtimer, then pmtimer
8e15ce
+       * is broken.
8e15ce
+       *
8e15ce
+       * Likewise, if our code is perfectly efficient and introduces no delays
8e15ce
+       * whatsoever, on a 10GHz system we should see a TSC delta of 3580 in
8e15ce
+       * ~3580 iterations.  On a 250MHz machine that should be ~900 iterations.
8e15ce
+       *
8e15ce
+       * With those factors in mind, there are two limits here.  There's a hard
8e15ce
+       * limit here at 8x our desired pm timer delta, picked as an arbitrarily
8e15ce
+       * large value that's still not a lot of time to humans, because if we
8e15ce
+       * get that far this is either an implausibly fast machine or the pmtimer
8e15ce
+       * is not running.  And there's another limit on 4x our 10GHz tsc delta
8e15ce
+       * without seeing cur converge on our target value.
8e15ce
        */
8e15ce
-      if ((num_iter & 0xffffff) == 0 && grub_get_tsc () - start_tsc > 5000000) {
8e15ce
-	return 0;
8e15ce
-      }
8e15ce
+      if ((++num_iter > (grub_uint32_t)num_pm_ticks << 3UL) ||
8e15ce
+	  end_tsc - start_tsc > 40000000)
8e15ce
+	{
8e15ce
+	  grub_dprintf ("pmtimer",
8e15ce
+			"pmtimer delta is 0x%"PRIxGRUB_UINT64_T" (%u iterations)\n",
8e15ce
+			cur - start, num_iter);
8e15ce
+	  grub_dprintf ("pmtimer",
8e15ce
+			"tsc delta is implausible: 0x%"PRIxGRUB_UINT64_T"\n",
8e15ce
+			end_tsc - start_tsc);
8e15ce
+	  return 0;
8e15ce
+	}
8e15ce
     }
8e15ce
 }
8e15ce
 
8e15ce
@@ -74,12 +135,20 @@ grub_tsc_calibrate_from_pmtimer (void)
8e15ce
 
8e15ce
   fadt = grub_acpi_find_fadt ();
8e15ce
   if (!fadt)
8e15ce
-    return 0;
8e15ce
+    {
8e15ce
+      grub_dprintf ("pmtimer", "No FADT found; not using pmtimer.\n");
8e15ce
+      return 0;
8e15ce
+    }
8e15ce
   pmtimer = fadt->pmtimer;
8e15ce
   if (!pmtimer)
8e15ce
-    return 0;
8e15ce
+    {
8e15ce
+      grub_dprintf ("pmtimer", "FADT does not specify pmtimer; skipping.\n");
8e15ce
+      return 0;
8e15ce
+    }
8e15ce
 
8e15ce
-  /* It's 3.579545 MHz clock. Wait 1 ms.  */
8e15ce
+  /*
8e15ce
+   * It's 3.579545 MHz clock. Wait 1 ms.
8e15ce
+   */
8e15ce
   tsc_diff = grub_pmtimer_wait_count_tsc (pmtimer, 3580);
8e15ce
   if (tsc_diff == 0)
8e15ce
     return 0;