Reactos
1/*
2 * PROJECT: ReactOS HAL
3 * LICENSE: GNU GPL - See COPYING in the top level directory
4 * FILE: hal/halx86/apic/rtctimer.c
5 * PURPOSE: HAL APIC Management and Control Code
6 * PROGRAMMERS: Timo Kreuzer (timo.kreuzer@reactos.org)
7 * REFERENCES: https://wiki.osdev.org/RTC
8 * https://forum.osdev.org/viewtopic.php?f=13&t=20825&start=0
9 * https://web.archive.org/web/20240119203005/http://www.bioscentral.com/misc/cmosmap.htm
10 */
11
12/* INCLUDES *******************************************************************/
13
14#include <hal.h>
15#include "apicp.h"
16#include <smp.h>
17#define NDEBUG
18#include <debug.h>
19
20/* GLOBALS ********************************************************************/
21
22static const UCHAR RtcMinimumClockRate = 6; /* Minimum rate 6: 1024 Hz / 0.97 ms */
23static const UCHAR RtcMaximumClockRate = 10; /* Maximum rate 10: 64 Hz / 15.6 ms */
24static UCHAR HalpCurrentClockRate = 10; /* Initial rate 10: 64 Hz / 15.6 ms */
25static ULONG HalpCurrentTimeIncrement;
26static ULONG HalpMinimumTimeIncrement;
27static ULONG HalpMaximumTimeIncrement;
28static ULONG HalpCurrentFractionalIncrement;
29static ULONG HalpRunningFraction;
30static BOOLEAN HalpSetClockRate;
31static UCHAR HalpNextClockRate;
32
33/*!
34 \brief Converts the CMOS RTC rate into the time increment in 0.1ns intervals.
35
36 Rate Frequency Interval (ms) Precise increment (0.1ns)
37 ------------------------------------------------------
38 0 disabled
39 1 32768 0.03052 305,175
40 2 16384 0.06103 610,351
41 3 8192 0.12207 1,220,703
42 4 4096 0.24414 2,441,406
43 5 2048 0.48828 4,882,812
44 6 1024 0.97656 9,765,625 <- minimum
45 7 512 1.95313 19,531,250
46 8 256 3.90625 39,062,500
47 9 128 7.8125 78,125,000
48 10 64 15.6250 156,250,000 <- maximum / default
49 11 32 31.25 312,500,000
50 12 16 62.5 625,000,000
51 13 8 125 1,250,000,000
52 14 4 250 2,500,000,000
53 15 2 500 5,000,000,000
54
55*/
56FORCEINLINE
57ULONG
58RtcClockRateToPreciseIncrement(UCHAR Rate)
59{
60 /* Calculate frequency */
61 ULONG Frequency = 32768 >> (Rate - 1);
62
63 /* Calculate interval in 0.1ns interval: Interval = (1 / Frequency) * 10,000,000,000 */
64 return 10000000000ULL / Frequency;
65}
66
67VOID
68RtcSetClockRate(UCHAR ClockRate)
69{
70 UCHAR RegisterA;
71 ULONG PreciseIncrement;
72
73 /* Update the global values */
74 HalpCurrentClockRate = ClockRate;
75 PreciseIncrement = RtcClockRateToPreciseIncrement(ClockRate);
76 HalpCurrentTimeIncrement = PreciseIncrement / 1000;
77 HalpCurrentFractionalIncrement = PreciseIncrement % 1000;
78
79 /* Acquire CMOS lock */
80 HalpAcquireCmosSpinLock();
81
82 // TODO: disable NMI
83
84 /* Read value of register A */
85 RegisterA = HalpReadCmos(RTC_REGISTER_A);
86
87 /* Change lower 4 bits to new rate */
88 RegisterA &= 0xF0;
89 RegisterA |= ClockRate;
90
91 /* Write the new value */
92 HalpWriteCmos(RTC_REGISTER_A, RegisterA);
93
94 /* Release CMOS lock */
95 HalpReleaseCmosSpinLock();
96}
97
98CODE_SEG("INIT")
99VOID
100NTAPI
101HalpInitializeClock(VOID)
102{
103 ULONG_PTR EFlags;
104 UCHAR RegisterB;
105
106 /* Save EFlags and disable interrupts */
107 EFlags = __readeflags();
108 _disable();
109
110 // TODO: disable NMI
111
112 /* Acquire CMOS lock */
113 HalpAcquireCmosSpinLock();
114
115 /* Enable the periodic interrupt in the CMOS */
116 RegisterB = HalpReadCmos(RTC_REGISTER_B);
117 HalpWriteCmos(RTC_REGISTER_B, RegisterB | RTC_REG_B_PI);
118
119 /* Release CMOS lock */
120 HalpReleaseCmosSpinLock();
121
122 /* Set initial rate */
123 RtcSetClockRate(HalpCurrentClockRate);
124
125 /* Restore interrupt state */
126 __writeeflags(EFlags);
127
128 /* Calculate minumum and maximum increment */
129 HalpMinimumTimeIncrement = RtcClockRateToPreciseIncrement(RtcMinimumClockRate) / 1000;
130 HalpMaximumTimeIncrement = RtcClockRateToPreciseIncrement(RtcMaximumClockRate) / 1000;
131
132 /* Notify the kernel about the maximum and minimum increment */
133 KeSetTimeIncrement(HalpMaximumTimeIncrement, HalpMinimumTimeIncrement);
134
135 /* Enable the timer interrupt */
136 HalEnableSystemInterrupt(APIC_CLOCK_VECTOR, CLOCK_LEVEL, Latched);
137
138 DPRINT1("Clock initialized\n");
139}
140
141VOID
142FASTCALL
143HalpClockInterruptHandler(IN PKTRAP_FRAME TrapFrame)
144{
145 ULONG LastIncrement;
146 KIRQL Irql;
147
148 /* Enter trap */
149 KiEnterInterruptTrap(TrapFrame);
150#ifdef _M_AMD64
151 /* This is for debugging */
152 TrapFrame->ErrorCode = 0xc10c4;
153#endif
154
155 /* Start the interrupt */
156 if (!HalBeginSystemInterrupt(CLOCK_LEVEL, APIC_CLOCK_VECTOR, &Irql))
157 {
158 /* Spurious, just end the interrupt */
159#ifdef _M_IX86
160 KiEoiHelper(TrapFrame);
161#endif
162 return;
163 }
164
165 /* Read register C, so that the next interrupt can happen */
166 HalpReadCmos(RTC_REGISTER_C);
167
168 /* Save increment */
169 LastIncrement = HalpCurrentTimeIncrement;
170
171 /* Check if the running fraction has accounted for 100 ns */
172 HalpRunningFraction += HalpCurrentFractionalIncrement;
173 if (HalpRunningFraction >= 1000)
174 {
175 LastIncrement++;
176 HalpRunningFraction -= 1000;
177 }
178
179 /* Check if someone changed the time rate */
180 if (HalpSetClockRate)
181 {
182 /* Set new clock rate */
183 RtcSetClockRate(HalpNextClockRate);
184
185 /* We're done */
186 HalpSetClockRate = FALSE;
187 }
188
189 /* Send the clock IPI to all other CPUs */
190 HalpBroadcastClockIpi(CLOCK_IPI_VECTOR);
191
192 /* Update the system time -- on x86 the kernel will exit this trap */
193 KeUpdateSystemTime(TrapFrame, LastIncrement, Irql);
194
195 /* End the interrupt */
196 KiEndInterrupt(Irql, TrapFrame);
197}
198
199VOID
200FASTCALL
201HalpClockIpiHandler(IN PKTRAP_FRAME TrapFrame)
202{
203 KIRQL Irql;
204
205 /* Enter trap */
206 KiEnterInterruptTrap(TrapFrame);
207#ifdef _M_AMD64
208 /* This is for debugging */
209 TrapFrame->ErrorCode = 0xc10c4;
210#endif
211
212 /* Start the interrupt */
213 if (!HalBeginSystemInterrupt(CLOCK_LEVEL, CLOCK_IPI_VECTOR, &Irql))
214 {
215 /* Spurious, just end the interrupt */
216#ifdef _M_IX86
217 KiEoiHelper(TrapFrame);
218#endif
219 return;
220 }
221
222 /* Call the kernel to update runtimes */
223 KeUpdateRunTime(TrapFrame, Irql);
224
225 /* End the interrupt */
226 KiEndInterrupt(Irql, TrapFrame);
227}
228
229ULONG
230NTAPI
231HalSetTimeIncrement(IN ULONG Increment)
232{
233 UCHAR Rate;
234 ULONG NextIncrement;
235
236 /* Lookup largest value below given Increment */
237 for (Rate = RtcMinimumClockRate; Rate < RtcMaximumClockRate; Rate++)
238 {
239 /* Check if this is the largest rate possible */
240 NextIncrement = RtcClockRateToPreciseIncrement(Rate + 1) / 1000;
241 if (NextIncrement > Increment)
242 break;
243 }
244
245 /* Set the rate and tell HAL we want to change it */
246 HalpNextClockRate = Rate;
247 HalpSetClockRate = TRUE;
248
249 /* Return the real increment */
250 return RtcClockRateToPreciseIncrement(Rate) / 1000;
251}