xref: /reactos/hal/halx86/apic/rtctimer.c (revision 0c2cdcae)
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  *                  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 
22 static const UCHAR RtcMinimumClockRate = 6;  /* Minimum rate  6: 1024 Hz / 0.97 ms */
23 static const UCHAR RtcMaximumClockRate = 10; /* Maximum rate 10: 64 Hz / 15.6 ms */
24 static UCHAR HalpCurrentClockRate = 10;  /* Initial rate  10: 64 Hz / 15.6 ms */
25 static ULONG HalpCurrentTimeIncrement;
26 static ULONG HalpMinimumTimeIncrement;
27 static ULONG HalpMaximumTimeIncrement;
28 static ULONG HalpCurrentFractionalIncrement;
29 static ULONG HalpRunningFraction;
30 static BOOLEAN HalpSetClockRate;
31 static 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 */
56 FORCEINLINE
57 ULONG
58 RtcClockRateToPreciseIncrement(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 
67 VOID
68 RtcSetClockRate(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 
98 CODE_SEG("INIT")
99 VOID
100 NTAPI
101 HalpInitializeClock(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 
141 VOID
142 FASTCALL
143 HalpClockInterruptHandler(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         KiEoiHelper(TrapFrame);
160     }
161 
162     /* Read register C, so that the next interrupt can happen */
163     HalpReadCmos(RTC_REGISTER_C);
164 
165     /* Save increment */
166     LastIncrement = HalpCurrentTimeIncrement;
167 
168     /* Check if the running fraction has accounted for 100 ns */
169     HalpRunningFraction += HalpCurrentFractionalIncrement;
170     if (HalpRunningFraction >= 1000)
171     {
172         LastIncrement++;
173         HalpRunningFraction -= 1000;
174     }
175 
176     /* Check if someone changed the time rate */
177     if (HalpSetClockRate)
178     {
179         /* Set new clock rate */
180         RtcSetClockRate(HalpNextClockRate);
181 
182         /* We're done */
183         HalpSetClockRate = FALSE;
184     }
185 
186     /* Send the clock IPI to all other CPUs */
187     HalpBroadcastClockIpi(CLOCK_IPI_VECTOR);
188 
189     /* Update the system time -- on x86 the kernel will exit this trap  */
190     KeUpdateSystemTime(TrapFrame, LastIncrement, Irql);
191 }
192 
193 VOID
194 FASTCALL
195 HalpClockIpiHandler(IN PKTRAP_FRAME TrapFrame)
196 {
197     KIRQL Irql;
198 
199     /* Enter trap */
200     KiEnterInterruptTrap(TrapFrame);
201 #ifdef _M_AMD64
202     /* This is for debugging */
203     TrapFrame->ErrorCode = 0xc10c4;
204 #endif
205 
206     /* Start the interrupt */
207     if (!HalBeginSystemInterrupt(CLOCK_LEVEL, CLOCK_IPI_VECTOR, &Irql))
208     {
209         /* Spurious, just end the interrupt */
210         KiEoiHelper(TrapFrame);
211     }
212 
213     /* Call the kernel to update runtimes */
214     KeUpdateRunTime(TrapFrame, Irql);
215 
216     /* End the interrupt */
217     KiEndInterrupt(Irql, TrapFrame);
218 }
219 
220 ULONG
221 NTAPI
222 HalSetTimeIncrement(IN ULONG Increment)
223 {
224     UCHAR Rate;
225     ULONG NextIncrement;
226 
227     /* Lookup largest value below given Increment */
228     for (Rate = RtcMinimumClockRate; Rate < RtcMaximumClockRate; Rate++)
229     {
230         /* Check if this is the largest rate possible */
231         NextIncrement = RtcClockRateToPreciseIncrement(Rate + 1) / 1000;
232         if (NextIncrement > Increment)
233             break;
234     }
235 
236     /* Set the rate and tell HAL we want to change it */
237     HalpNextClockRate = Rate;
238     HalpSetClockRate = TRUE;
239 
240     /* Return the real increment */
241     return RtcClockRateToPreciseIncrement(Rate) / 1000;
242 }
243