Files
Dmitry Borisov 82ef7e5d04 [HALPC98] Improve TSC calibration routine (#8860)
Do not rely on a hardcoded ASM instruction offset.

Addendum to commit 45e6d49a29.
CORE-17977
2026-05-06 16:20:17 +03:00

151 lines
4.4 KiB
C

/*
* PROJECT: NEC PC-98 series HAL
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: TSC calibration for the busy-wait loop routine
* COPYRIGHT: Copyright 2011 Timo Kreuzer <[email protected]>
* Copyright 2026 Dmitry Borisov <[email protected]>
*/
/* INCLUDES ******************************************************************/
#include <hal.h>
#define NDEBUG
#include <debug.h>
#include "delay.h"
/* GLOBALS *******************************************************************/
#define SAMPLE_FREQUENCY 1024 // 0.977 ms
VOID
__cdecl
HalpTscCalibrationISR(VOID);
extern volatile ULONG TscCalibrationPhase;
extern ULONG64 TscCalibrationArray[NUM_SAMPLES];
extern UCHAR HalpStallExecutionSerialize;
/* FUNCTIONS *****************************************************************/
static
CODE_SEG("INIT")
VOID
HalpPrepareStallExecution(VOID)
{
PUCHAR Instruction = &HalpStallExecutionSerialize;
PKPRCB Prcb = KeGetCurrentPrcb();
/* xor eax, eax; cpuid */
ASSERT((Instruction[1] == 0xC0) && // The byte [0] has different encodings
(Instruction[2] == 0x0F) &&
(Instruction[3] == 0xA2));
/*
* Starting with the Pentium Pro processor it is necessary to force
* the in-order execution of the RDTSC instruction using a serializing instruction.
* For more details, please refer to Section 3.1 of
* Intel "Using the RDTSC Instruction for Performance Monitoring".
*
* Patch the KeStallExecutionProcessor function to remove the serializing instruction
* for the Pentium and Pentium MMX processors because the CPUID instruction is slow.
*/
if ((Prcb->CpuType < 6) && !strcmp(Prcb->VendorString, "GenuineIntel"))
{
/* Replace "xor eax, eax; cpuid" with "lea esi, [esi+0]" */
Instruction[0] = 0x8D;
Instruction[1] = 0x74;
Instruction[2] = 0x26;
Instruction[3] = 0x00;
KeSweepICache(Instruction, 4);
}
}
static
CODE_SEG("INIT")
ULONG64
HalpDoLinearRegression(
_In_ ULONG XMax,
_In_reads_(XMax + 1) const ULONG64* ArrayY)
{
ULONG X, SumXX;
ULONG64 SumXY;
/* Calculate the sum of the squares of X */
SumXX = (XMax * (XMax + 1) * (2 * XMax + 1)) / 6;
/* Calculate the sum of the differences to the first value weighted by X */
for (SumXY = 0, X = 1; X <= XMax; X++)
{
SumXY += X * (ArrayY[X] - ArrayY[0]);
}
/* Account for sample frequency */
SumXY *= SAMPLE_FREQUENCY;
/* Return the quotient of the sums */
return (SumXY + (SumXX / 2)) / SumXX;
}
CODE_SEG("INIT")
VOID
NTAPI
HalpCalibrateStallExecution(VOID)
{
ULONG_PTR Flags;
PVOID PreviousHandler;
TIMER_CONTROL_PORT_REGISTER TimerControl;
ULONG TimerFrequency;
USHORT Period;
ULONG64 CpuClockFrequency;
/* Check if the CPU supports RDTSC */
if (!(KeGetCurrentPrcb()->FeatureBits & KF_RDTSC))
{
KeBugCheck(HAL_INITIALIZATION_FAILED);
}
Flags = __readeflags();
_disable();
PreviousHandler = KeQueryInterruptHandler(PIC_TIMER_IRQ);
KeRegisterInterruptHandler(PRIMARY_VECTOR_BASE + PIC_TIMER_IRQ, HalpTscCalibrationISR);
/* Program the PIT for binary mode */
TimerControl.BcdMode = FALSE;
TimerControl.OperatingMode = PitOperatingMode2;
TimerControl.Channel = PitChannel0;
TimerControl.AccessMode = PitAccessModeLowHigh;
if (__inbyte(0x42) & 0x20)
TimerFrequency = TIMER_FREQUENCY_1;
else
TimerFrequency = TIMER_FREQUENCY_2;
Period = (TimerFrequency + (SAMPLE_FREQUENCY / 2)) / SAMPLE_FREQUENCY;
__outbyte(TIMER_CONTROL_PORT, TimerControl.Bits);
__outbyte(TIMER_CHANNEL0_DATA_PORT, Period & 0xFF);
__outbyte(TIMER_CHANNEL0_DATA_PORT, Period >> 8);
HalEnableSystemInterrupt(PRIMARY_VECTOR_BASE + PIC_TIMER_IRQ, CLOCK2_LEVEL, Latched);
/* Collect the sample data */
_enable();
while (TscCalibrationPhase != (NUM_SAMPLES + 1))
NOTHING;
_disable();
HalDisableSystemInterrupt(PRIMARY_VECTOR_BASE + PIC_TIMER_IRQ, CLOCK2_LEVEL);
KeRegisterInterruptHandler(PRIMARY_VECTOR_BASE + PIC_TIMER_IRQ, PreviousHandler);
/* Calculate an average, using simplified linear regression */
CpuClockFrequency = HalpDoLinearRegression(NUM_SAMPLES - 1, TscCalibrationArray);
KeGetPcr()->StallScaleFactor = (ULONG)(CpuClockFrequency / 1000000);
HalpPrepareStallExecution();
__writeeflags(Flags);
}