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