File: tsc_pmtimer.c

package info (click to toggle)
grub2 2.14~git20250718.0e36779-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 60,688 kB
  • sloc: ansic: 541,811; asm: 68,074; sh: 9,803; cpp: 2,095; makefile: 1,895; python: 1,518; sed: 446; lex: 393; yacc: 268; awk: 85; lisp: 54; perl: 31
file content (157 lines) | stat: -rw-r--r-- 5,308 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* kern/i386/tsc.c - x86 TSC time source implementation
 * Requires Pentium or better x86 CPU that supports the RDTSC instruction.
 * This module uses the PIT to calibrate the TSC to
 * real time.
 *
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2008  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/types.h>
#include <grub/time.h>
#include <grub/misc.h>
#include <grub/i386/tsc.h>
#include <grub/i386/pmtimer.h>
#include <grub/acpi.h>
#include <grub/cpu/io.h>

grub_uint64_t
grub_pmtimer_wait_count_tsc (grub_port_t pmtimer,
			     grub_uint16_t num_pm_ticks)
{
  grub_uint32_t start;
  grub_uint64_t cur, end;
  grub_uint64_t start_tsc;
  grub_uint64_t end_tsc;
  grub_uint32_t num_iter = 0;
  int bad_reads = 0;

  /*
   * Some timers are 24-bit and some are 32-bit, but it doesn't make much
   * difference to us. Caring which one we have isn't really worth it since
   * the low-order digits will give us enough data to calibrate TSC. So just
   * mask the top-order byte off.
   */
  cur = start = grub_inl (pmtimer) & 0x00ffffffUL;
  end = start + num_pm_ticks;
  start_tsc = grub_get_tsc ();
  while (1)
    {
      cur &= 0xffffffffff000000ULL;

      /* Only take the low-order 24-bit for the reason explained above. */
      cur |= grub_inl (pmtimer) & 0x00ffffffUL;

      end_tsc = grub_get_tsc();

      /*
       * If we get 10 reads in a row that are obviously dead pins, there's no
       * reason to do this thousands of times.
       */
      if (cur == 0xffffffUL || cur == 0)
	{
	  bad_reads++;
	  grub_dprintf ("pmtimer",
			"pmtimer: 0x%"PRIxGRUB_UINT64_T" bad_reads: %d\n",
			cur, bad_reads);

	  if (bad_reads == 10)
            {
              grub_dprintf ("pmtimer", "timer is broken; giving up.\n");
              return 0;
            }
	}

      if (cur < start)
	cur += 0x1000000;

      if (cur >= end)
	{
	  grub_dprintf ("pmtimer", "pmtimer delta is 0x%"PRIxGRUB_UINT64_T"\n",
			cur - start);
	  grub_dprintf ("pmtimer", "tsc delta is 0x%"PRIxGRUB_UINT64_T"\n",
			end_tsc - start_tsc);
	  return end_tsc - start_tsc;
	}

      /*
       * Check for broken PM timer. 1ms at 10GHz should be 1E+7 TSCs; at
       * 250MHz it should be 2.5E5. So if after 4E+7 TSCs on a 10GHz machine,
       * we should have seen pmtimer show 4ms of change (i.e. cur =~ start + 14320);
       * on a 250MHz machine that should be 160ms (start + 572800). If after
       * this a time we still don't have 1ms on pmtimer, then pmtimer is broken.
       *
       * Likewise, if our code is perfectly efficient and introduces no delays
       * whatsoever, on a 10GHz system we should see a TSC delta of 3580 in
       * ~3580 iterations. On a 250MHz machine that should be ~900 iterations.
       *
       * With those factors in mind, there are two limits here. There's a hard
       * limit here at 8x our desired pm timer delta. This limit was picked as
       * an arbitrarily large value that's still not a lot of time to humans,
       * because if we get that far this is either an implausibly fast machine
       * or the pmtimer is not running. And there is another limit on a 4 ms TSC
       * delta on a 10 GHz clock, without seeing cur converge on our target value.
       */
      if ((++num_iter > (grub_uint32_t) num_pm_ticks << 3UL) || end_tsc - start_tsc > 40000000)
	{
	  grub_dprintf ("pmtimer",
			"pmtimer delta is 0x%"PRIxGRUB_UINT64_T" (%"PRIxGRUB_UINT32_T" iterations)\n",
			cur - start, num_iter);
	  grub_dprintf ("pmtimer",
			"tsc delta is implausible: 0x%"PRIxGRUB_UINT64_T"\n",
			end_tsc - start_tsc);
	  return 0;
	}
    }
}

int
grub_tsc_calibrate_from_pmtimer (void)
{
  struct grub_acpi_fadt *fadt;
  grub_port_t pmtimer;
  grub_uint64_t tsc_diff;

  fadt = grub_acpi_find_fadt ();
  if (!fadt)
    {
      grub_dprintf ("pmtimer", "No FADT found; not using pmtimer.\n");
      return 0;
    }
  pmtimer = fadt->pmtimer;
  if (!pmtimer)
    {
      grub_dprintf ("pmtimer", "FADT does not specify pmtimer; skipping.\n");
      return 0;
    }

  /* It's 3.579545 MHz clock. Wait 1 ms.  */
  tsc_diff = grub_pmtimer_wait_count_tsc (pmtimer, 3580);
  if (tsc_diff == 0)
    return 0;
  grub_tsc_rate = grub_divmod64 ((1ULL << 32), tsc_diff, 0);
  /*
   * Specifically, when the tsc_diff (end_tsc - start_tsc) is greater than (1ULL << 32),
   * the result of grub_divmod64() becomes zero, causing grub_tsc_rate to always be zero.
   * As a result, grub_tsc_get_time_ms() consistently returns zero, and the GRUB menu
   * countdown gets stuck. To resolve this, we return 0 to proceed to the next calibration
   * function when grub_tsc_rate is zero.
   */
  if (grub_tsc_rate == 0)
    return 0;

  return 1;
}