reactos/hal/halx86/apic/rtctimer.c
Timo Kreuzer 3e1454c739 [HAL/APIC] Make the real time clock more precise
The interval time is now calculated in 0.1ns precision and a running fraction on the 100ns interval is updated on each clock interrupt.
Also adjust minimum, maximum and default clock rate to what Windows uses.
2021-07-19 23:05:03 +02:00

211 lines
6.1 KiB
C

/*
* PROJECT: ReactOS HAL
* LICENSE: GNU GPL - See COPYING in the top level directory
* FILE: hal/halx86/apic/rtctimer.c
* PURPOSE: HAL APIC Management and Control Code
* PROGRAMMERS: Timo Kreuzer (timo.kreuzer@reactos.org)
* REFERENCES: https://wiki.osdev.org/RTC
* https://forum.osdev.org/viewtopic.php?f=13&t=20825&start=0
* http://www.bioscentral.com/misc/cmosmap.htm
*/
/* INCLUDES *******************************************************************/
#include <hal.h>
#include "apicp.h"
#define NDEBUG
#include <debug.h>
/* GLOBALS ********************************************************************/
static const UCHAR RtcMinimumClockRate = 6; /* Minimum rate 6: 1024 Hz / 0.97 ms */
static const UCHAR RtcMaximumClockRate = 10; /* Maximum rate 10: 64 Hz / 15.6 ms */
static UCHAR HalpCurrentClockRate = 10; /* Initial rate 10: 64 Hz / 15.6 ms */
static ULONG HalpCurrentTimeIncrement;
static ULONG HalpMinimumTimeIncrement;
static ULONG HalpMaximumTimeIncrement;
static ULONG HalpCurrentFractionalIncrement;
static ULONG HalpRunningFraction;
static BOOLEAN HalpSetClockRate;
static UCHAR HalpNextClockRate;
/*!
\brief Converts the CMOS RTC rate into the time increment in 100ns intervals.
Rate Frequency Interval (ms) Precise increment (0.1ns)
------------------------------------------------------
0 disabled
1 32768 0.03052 305,175
2 16384 0.06103 610,351
3 8192 0.12207 1,220,703
4 4096 0.24414 2,441,406
5 2048 0.48828 4,882,812
6 1024 0.97656 9,765,625 <- minimum
7 512 1.95313 19,531,250
8 256 3.90625 39,062,500
9 128 7.8125 78,125,000
10 64 15.6250 156,250,000 <- maximum / default
11 32 31.25 312,500,000
12 16 62.5 625,000,000
13 8 125 1,250,000,000
14 4 250 2,500,000,000
15 2 500 5,000,000,000
*/
FORCEINLINE
ULONG
RtcClockRateToPreciseIncrement(UCHAR Rate)
{
/* Calculate frequency */
ULONG Frequency = 32768 >> (Rate - 1);
/* Calculate interval in 0.1ns interval: Interval = (1 / Frequency) * 10,000,000,000 */
return 10000000000ULL / Frequency;
}
VOID
RtcSetClockRate(UCHAR ClockRate)
{
UCHAR RegisterA;
ULONG PreciseIncrement;
/* Update the global values */
HalpCurrentClockRate = ClockRate;
PreciseIncrement = RtcClockRateToPreciseIncrement(ClockRate);
HalpCurrentTimeIncrement = PreciseIncrement / 1000;
HalpCurrentFractionalIncrement = PreciseIncrement % 1000;
/* Acquire CMOS lock */
HalpAcquireCmosSpinLock();
// TODO: disable NMI
/* Read value of register A */
RegisterA = HalpReadCmos(RTC_REGISTER_A);
/* Change lower 4 bits to new rate */
RegisterA &= 0xF0;
RegisterA |= ClockRate;
/* Write the new value */
HalpWriteCmos(RTC_REGISTER_A, RegisterA);
/* Release CMOS lock */
HalpReleaseCmosSpinLock();
}
CODE_SEG("INIT")
VOID
NTAPI
HalpInitializeClock(VOID)
{
ULONG_PTR EFlags;
UCHAR RegisterB;
/* Save EFlags and disable interrupts */
EFlags = __readeflags();
_disable();
// TODO: disable NMI
/* Acquire CMOS lock */
HalpAcquireCmosSpinLock();
/* Enable the periodic interrupt in the CMOS */
RegisterB = HalpReadCmos(RTC_REGISTER_B);
HalpWriteCmos(RTC_REGISTER_B, RegisterB | RTC_REG_B_PI);
/* Release CMOS lock */
HalpReleaseCmosSpinLock();
/* Set initial rate */
RtcSetClockRate(HalpCurrentClockRate);
/* Restore interrupt state */
__writeeflags(EFlags);
/* Calculate minumum and maximum increment */
HalpMinimumTimeIncrement = RtcClockRateToPreciseIncrement(RtcMinimumClockRate) / 1000;
HalpMaximumTimeIncrement = RtcClockRateToPreciseIncrement(RtcMaximumClockRate) / 1000;
/* Notify the kernel about the maximum and minimum increment */
KeSetTimeIncrement(HalpMaximumTimeIncrement, HalpMinimumTimeIncrement);
/* Enable the timer interrupt */
HalEnableSystemInterrupt(APIC_CLOCK_VECTOR, CLOCK_LEVEL, Latched);
DPRINT1("Clock initialized\n");
}
VOID
FASTCALL
HalpClockInterruptHandler(IN PKTRAP_FRAME TrapFrame)
{
ULONG LastIncrement;
KIRQL Irql;
/* Enter trap */
KiEnterInterruptTrap(TrapFrame);
#ifdef _M_AMD64
/* This is for debugging */
TrapFrame->ErrorCode = 0xc10c4;
#endif
/* Start the interrupt */
if (!HalBeginSystemInterrupt(CLOCK_LEVEL, APIC_CLOCK_VECTOR, &Irql))
{
/* Spurious, just end the interrupt */
KiEoiHelper(TrapFrame);
}
/* Read register C, so that the next interrupt can happen */
HalpReadCmos(RTC_REGISTER_C);
/* Save increment */
LastIncrement = HalpCurrentTimeIncrement;
/* Check if the running fraction has accounted for 100 ns */
HalpRunningFraction += HalpCurrentFractionalIncrement;
if (HalpRunningFraction >= 1000)
{
LastIncrement++;
HalpRunningFraction -= 1000;
}
/* Check if someone changed the time rate */
if (HalpSetClockRate)
{
/* Set new clock rate */
RtcSetClockRate(HalpNextClockRate);
/* We're done */
HalpSetClockRate = FALSE;
}
/* Update the system time -- on x86 the kernel will exit this trap */
KeUpdateSystemTime(TrapFrame, LastIncrement, Irql);
}
ULONG
NTAPI
HalSetTimeIncrement(IN ULONG Increment)
{
UCHAR Rate;
ULONG CurrentIncrement;
/* Lookup largest value below given Increment */
for (Rate = RtcMinimumClockRate; Rate <= RtcMaximumClockRate; Rate++)
{
/* Check if this is the largest rate possible */
CurrentIncrement = RtcClockRateToPreciseIncrement(Rate + 1) / 1000;
if (Increment > CurrentIncrement) break;
}
/* Set the rate and tell HAL we want to change it */
HalpNextClockRate = Rate;
HalpSetClockRate = TRUE;
/* Return the real increment */
return RtcClockRateToPreciseIncrement(Rate) / 1000;
}