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