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