Implement APIC timer initialization and calibration
This commit is contained in:
@@ -179,6 +179,10 @@
|
||||
#define COMPORT_REG_MSR 0x06 /* Modem Status Register */
|
||||
#define COMPORT_REG_SR 0x07 /* Scratch Register */
|
||||
|
||||
/* Minimum and maximum profile intervals */
|
||||
#define MIN_PROFILE_INTERVAL 1000
|
||||
#define MAX_PROFILE_INTERVAL 10000000
|
||||
|
||||
|
||||
/* C/C++ specific code */
|
||||
#ifndef __XTOS_ASSEMBLER__
|
||||
|
||||
@@ -137,6 +137,37 @@ typedef enum _KPROCESS_STATE
|
||||
ProcessOutSwap
|
||||
} KPROCESS_STATE, *PKPROCESS_STATE;
|
||||
|
||||
/* Kernel profiling sources */
|
||||
typedef enum _KPROFILE_SOURCE
|
||||
{
|
||||
ProfileTime,
|
||||
ProfileAlignmentFixup,
|
||||
ProfileTotalIssues,
|
||||
ProfilePipelineDry,
|
||||
ProfileLoadInstructions,
|
||||
ProfilePipelineFrozen,
|
||||
ProfileBranchInstructions,
|
||||
ProfileTotalNonissues,
|
||||
ProfileDcacheMisses,
|
||||
ProfileIcacheMisses,
|
||||
ProfileCacheMisses,
|
||||
ProfileBranchMispredictions,
|
||||
ProfileStoreInstructions,
|
||||
ProfileFpInstructions,
|
||||
ProfileIntegerInstructions,
|
||||
Profile2Issue,
|
||||
Profile3Issue,
|
||||
Profile4Issue,
|
||||
ProfileSpecialInstructions,
|
||||
ProfileTotalCycles,
|
||||
ProfileIcacheIssues,
|
||||
ProfileDcacheAccesses,
|
||||
ProfileMemoryBarrierCycles,
|
||||
ProfileLoadLinkedIssues,
|
||||
ProfileXtKernel,
|
||||
ProfileMaximum
|
||||
} KPROFILE_SOURCE, *PKPROFILE_SOURCE;
|
||||
|
||||
/* Thread state */
|
||||
typedef enum _KTHREAD_STATE
|
||||
{
|
||||
|
||||
@@ -47,6 +47,7 @@ typedef enum _KDPC_IMPORTANCE KDPC_IMPORTANCE, *PKDPC_IMPORTANCE;
|
||||
typedef enum _KEVENT_TYPE KEVENT_TYPE, *PKEVENT_TYPE;
|
||||
typedef enum _KOBJECTS KOBJECTS, *PKOBJECTS;
|
||||
typedef enum _KPROCESS_STATE KPROCESS_STATE, *PKPROCESS_STATE;
|
||||
typedef enum _KPROFILE_SOURCE KPROFILE_SOURCE, *PKPROFILE_SOURCE;
|
||||
typedef enum _KTHREAD_STATE KTHREAD_STATE, *PKTHREAD_STATE;
|
||||
typedef enum _KTIMER_TYPE KTIMER_TYPE, *PKTIMER_TYPE;
|
||||
typedef enum _KUBSAN_DATA_TYPE KUBSAN_DATA_TYPE, *PKUBSAN_DATA_TYPE;
|
||||
|
||||
@@ -21,6 +21,7 @@ list(APPEND XTOSKRNL_SOURCE
|
||||
${XTOSKRNL_SOURCE_DIR}/hl/${ARCH}/ioport.cc
|
||||
${XTOSKRNL_SOURCE_DIR}/hl/${ARCH}/irq.cc
|
||||
${XTOSKRNL_SOURCE_DIR}/hl/${ARCH}/runlevel.cc
|
||||
${XTOSKRNL_SOURCE_DIR}/hl/${ARCH}/timer.cc
|
||||
${XTOSKRNL_SOURCE_DIR}/hl/acpi.cc
|
||||
${XTOSKRNL_SOURCE_DIR}/hl/cport.cc
|
||||
${XTOSKRNL_SOURCE_DIR}/hl/data.cc
|
||||
|
||||
13
xtoskrnl/hl/amd64/timer.cc
Normal file
13
xtoskrnl/hl/amd64/timer.cc
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* PROJECT: ExectOS
|
||||
* COPYRIGHT: See COPYING.md in the top level directory
|
||||
* FILE: xtoskrnl/hl/amd64/timer.cc
|
||||
* DESCRIPTION: APIC Timer for AMD64 support
|
||||
* DEVELOPERS: Aiken Harris <harraiken91@gmail.com>
|
||||
*/
|
||||
|
||||
#include <xtos.hh>
|
||||
|
||||
|
||||
/* Include common Timer interface */
|
||||
#include ARCH_COMMON(timer.cc)
|
||||
13
xtoskrnl/hl/i686/timer.cc
Normal file
13
xtoskrnl/hl/i686/timer.cc
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* PROJECT: ExectOS
|
||||
* COPYRIGHT: See COPYING.md in the top level directory
|
||||
* FILE: xtoskrnl/hl/i686/timer.cc
|
||||
* DESCRIPTION: APIC Timer for i686 support
|
||||
* DEVELOPERS: Aiken Harris <harraiken91@gmail.com>
|
||||
*/
|
||||
|
||||
#include <xtos.hh>
|
||||
|
||||
|
||||
/* Include common Timer interface */
|
||||
#include ARCH_COMMON(timer.cc)
|
||||
350
xtoskrnl/hl/x86/timer.cc
Normal file
350
xtoskrnl/hl/x86/timer.cc
Normal file
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* PROJECT: ExectOS
|
||||
* COPYRIGHT: See COPYING.md in the top level directory
|
||||
* FILE: xtoskrnl/hl/x86/timer.cc
|
||||
* DESCRIPTION: APIC Timer support for x86 (i686/AMD64) support
|
||||
* DEVELOPERS: Aiken Harris <harraiken91@gmail.com>
|
||||
*/
|
||||
|
||||
#include <xtos.hh>
|
||||
|
||||
|
||||
/**
|
||||
* Calibrates the Local APIC timer frequency.
|
||||
*
|
||||
* @return This routine returns a status code.
|
||||
*
|
||||
* @since XT 1.0
|
||||
*/
|
||||
XTAPI
|
||||
XTSTATUS
|
||||
HL::Timer::CalibrateApicTimer()
|
||||
{
|
||||
ULONG CurrentCount, Frequency, InitialCount;
|
||||
XTSTATUS Status;
|
||||
|
||||
/* Get APIC timer frequency from the Core Crystal Clock */
|
||||
Status = GetApicTimerFrequency(&Frequency);
|
||||
if(Status != STATUS_SUCCESS || !Frequency)
|
||||
{
|
||||
/* CCC unavailable, fallback to PIT calibration */
|
||||
InitialCount = 0xFFFFFFFF;
|
||||
|
||||
/* Load the initial count into the APIC Timer and begin the countdown */
|
||||
HL::Pic::WriteApicRegister(APIC_TICR, InitialCount);
|
||||
|
||||
/* Wait for 10ms */
|
||||
StallExecution(10000);
|
||||
|
||||
/* Read current tick count from APIC timer and clear APIC timer */
|
||||
CurrentCount = HL::Pic::ReadApicRegister(APIC_TCCR);
|
||||
HL::Pic::WriteApicRegister(APIC_TICR, 0);
|
||||
|
||||
/* Calculate APIC timer frequency based on ticks passed */
|
||||
Frequency = (InitialCount - CurrentCount) * 100;
|
||||
|
||||
/* Verify APIC timer frequency */
|
||||
if(Frequency == 0)
|
||||
{
|
||||
/* Unable to calibrate APIC timer, return error */
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Save APIC timer frequency */
|
||||
TimerFrequency = Frequency;
|
||||
|
||||
/* Print APIC timer frequency and return success */
|
||||
DebugPrint(L"APIC Timer calibrated: %u Ticks/s\n", TimerFrequency);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the APIC timer frequency from the Core Crystal Clock.
|
||||
*
|
||||
* @param Frequency
|
||||
* Supplies a pointer to a variable that will receive the nominal APIC timer frequency in Hz.
|
||||
*
|
||||
* @return This routine returns a status code.
|
||||
*
|
||||
* @since XT 1.0
|
||||
*/
|
||||
XTAPI
|
||||
XTSTATUS
|
||||
HL::Timer::GetApicTimerFrequency(OUT PULONG Frequency)
|
||||
{
|
||||
CPUID_REGISTERS CpuRegisters;
|
||||
|
||||
/* Verify input parameter */
|
||||
if(!Frequency)
|
||||
{
|
||||
/* Invalid parameter passed */
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/* Initialize output parameter to 0 */
|
||||
*Frequency = 0;
|
||||
|
||||
/* Get maximum supported standard CPUID leaf */
|
||||
CpuRegisters.Leaf = 0;
|
||||
CpuRegisters.SubLeaf = 0;
|
||||
CpuRegisters.Eax = 0;
|
||||
CpuRegisters.Ebx = 0;
|
||||
CpuRegisters.Ecx = 0;
|
||||
CpuRegisters.Edx = 0;
|
||||
AR::CpuFunc::CpuId(&CpuRegisters);
|
||||
|
||||
/* Check if leaf 0x15 is supported by the CPU */
|
||||
if(CpuRegisters.Eax < 0x15)
|
||||
{
|
||||
/* Processor is too old, return error */
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* Query Time Stamp Counter and Core Crystal Clock information */
|
||||
CpuRegisters.Leaf = 0x15;
|
||||
CpuRegisters.SubLeaf = 0;
|
||||
CpuRegisters.Eax = 0;
|
||||
CpuRegisters.Ebx = 0;
|
||||
CpuRegisters.Ecx = 0;
|
||||
CpuRegisters.Edx = 0;
|
||||
AR::CpuFunc::CpuId(&CpuRegisters);
|
||||
|
||||
/* Check if the leaf is properly enumerated */
|
||||
if(CpuRegisters.Eax == 0 || CpuRegisters.Ebx == 0)
|
||||
{
|
||||
/* Intel SDM: EAX or EBX is 0, the leaf is not properly enumerated, return error */
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* Check if ECX contains the nominal frequency of the core crystal clock */
|
||||
if(CpuRegisters.Ecx == 0)
|
||||
{
|
||||
/* Hardware did not provide the exact frequency, return error */
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* Save the base frequency for the APIC Timer and return success */
|
||||
*Frequency = CpuRegisters.Ecx;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and calibrates the Local APIC Timer.
|
||||
*
|
||||
* @return This routine does not return any value.
|
||||
*
|
||||
* @since XT 1.0
|
||||
*/
|
||||
XTAPI
|
||||
VOID
|
||||
HL::Timer::InitializeApicTimer(VOID)
|
||||
{
|
||||
XTSTATUS Status;
|
||||
|
||||
/* Set APIC timer to divide by 1 */
|
||||
HL::Pic::WriteApicRegister(APIC_TDCR, TIMER_DivideBy1);
|
||||
|
||||
/* Calibrate the APIC timer */
|
||||
Status = CalibrateApicTimer();
|
||||
if(Status != STATUS_SUCCESS)
|
||||
{
|
||||
/* System cannot operate without a calibrated system timer, raise kernel panic */
|
||||
KE::Crash::Panic(0);
|
||||
}
|
||||
|
||||
/* Set the default system profile interval */
|
||||
HL::Timer::SetProfileInterval(1000);
|
||||
|
||||
/* Program the APIC timer for periodic mode */
|
||||
StopProfileInterrupt(ProfileXtKernel);
|
||||
|
||||
// StartProfileInterrupt(ProfileXtKernel);
|
||||
// AR::CpuFunc::SetInterruptFlag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stalls the CPU execution for a specified duration (maximum 3 seconds) using the legacy PIT timer.
|
||||
*
|
||||
* @param MicroSeconds
|
||||
* Specifies the number of microseconds to stall execution.
|
||||
*
|
||||
* @return This routine does not return any value.
|
||||
*
|
||||
* @since XT 1.0
|
||||
*/
|
||||
XTAPI
|
||||
VOID
|
||||
HL::Timer::PitStallExecution(IN ULONG MicroSeconds)
|
||||
{
|
||||
USHORT CurrentCount, PreviousCount;
|
||||
ULONG TargetTicks, TickCounter;
|
||||
|
||||
/* Validate input parameter */
|
||||
if(MicroSeconds == 0)
|
||||
{
|
||||
/* Nothing to do */
|
||||
return;
|
||||
}
|
||||
else if(MicroSeconds > 3000000)
|
||||
{
|
||||
/* Cap execution stall to 3 seconds */
|
||||
MicroSeconds = 3000000;
|
||||
}
|
||||
|
||||
/* Convert us to PIT ticks and initialize tick counter */
|
||||
TargetTicks = (MicroSeconds * 1193) / 1000;
|
||||
TickCounter = 0;
|
||||
|
||||
/* Configure PIT Channel 0: Read/Write LSB then MSB, Mode 0 (Interrupt on Terminal Count) */
|
||||
HL::IoPort::WritePort8(PIT_COMMAND_PORT, 0x30);
|
||||
|
||||
/* Initialize the PIT counter with the maximum possible value (0xFFFF) */
|
||||
HL::IoPort::WritePort8(PIT_DATA_PORT0, 0xFF);
|
||||
HL::IoPort::WritePort8(PIT_DATA_PORT0, 0xFF);
|
||||
|
||||
/* Latch and read the initial counter value */
|
||||
HL::IoPort::WritePort8(PIT_COMMAND_PORT, 0x00);
|
||||
PreviousCount = HL::IoPort::ReadPort8(PIT_DATA_PORT0);
|
||||
PreviousCount |= (HL::IoPort::ReadPort8(PIT_DATA_PORT0) << 8);
|
||||
|
||||
/* Poll the PIT */
|
||||
while(TickCounter < TargetTicks)
|
||||
{
|
||||
/* Latch the current counter value without stopping the timer */
|
||||
HL::IoPort::WritePort8(PIT_COMMAND_PORT, 0x00);
|
||||
CurrentCount = HL::IoPort::ReadPort8(PIT_DATA_PORT0);
|
||||
CurrentCount |= (HL::IoPort::ReadPort8(PIT_DATA_PORT0) << 8);
|
||||
|
||||
/* Calculate elapsed ticks since the last read */
|
||||
TickCounter += (PreviousCount - CurrentCount) & 0xFFFF;
|
||||
|
||||
/* Update the tracking variable */
|
||||
PreviousCount = CurrentCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the profile interrupt interval. The interval may be bounded by hardware capabilities.
|
||||
*
|
||||
* @param Interval
|
||||
* Supplies the requested profile interval in 100-nanosecond units.
|
||||
*
|
||||
* @return This routine returns the actual profile interval that was set.
|
||||
*
|
||||
* @since XT 1.0
|
||||
*/
|
||||
XTAPI
|
||||
ULONG_PTR
|
||||
HL::Timer::SetProfileInterval(IN ULONG_PTR Interval)
|
||||
{
|
||||
/* Validate and bound the requested profile interval against hardware limits */
|
||||
if(Interval < MIN_PROFILE_INTERVAL)
|
||||
{
|
||||
/* Enforce the minimum profile interval limit */
|
||||
Interval = MIN_PROFILE_INTERVAL;
|
||||
}
|
||||
else if(Interval > MAX_PROFILE_INTERVAL)
|
||||
{
|
||||
/* Enforce the maximum profile interval limit */
|
||||
Interval = MAX_PROFILE_INTERVAL;
|
||||
}
|
||||
|
||||
/* Calculate the number of APIC timer ticks required for the requested interval */
|
||||
ProfilingInterval = (TimerFrequency / 10000) * (Interval / 1000);
|
||||
|
||||
/* Update the APIC Timer Initial Count Register (TICR) to apply the new interval immediately */
|
||||
HL::Pic::WriteApicRegister(APIC_TICR, ProfilingInterval);
|
||||
|
||||
/* Return the actual interval */
|
||||
return Interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stalls the CPU execution for a specified duration.
|
||||
*
|
||||
* @param MicroSeconds
|
||||
* Supplies the number of microseconds to stall execution.
|
||||
*
|
||||
* @return This routine does not return any value.
|
||||
*
|
||||
* @since XT 1.0
|
||||
*/
|
||||
XTAPI
|
||||
VOID
|
||||
HL::Timer::StallExecution(IN ULONG MicroSeconds)
|
||||
{
|
||||
UNIMPLEMENTED;
|
||||
|
||||
/* ACPI PM Timer not supported, fall back to PIT */
|
||||
PitStallExecution(MicroSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the profile interrupt for the specified profile source.
|
||||
*
|
||||
* @param ProfileSource
|
||||
* Supplies the source of the profile interrupt to start.
|
||||
*
|
||||
* @return This routine does not return any value.
|
||||
*
|
||||
* @since XT 1.0
|
||||
*/
|
||||
XTAPI
|
||||
VOID
|
||||
HL::Timer::StartProfileInterrupt(IN KPROFILE_SOURCE ProfileSource)
|
||||
{
|
||||
APIC_LVT_REGISTER LvtRegister;
|
||||
|
||||
/* Handle only ProfileTime and ProfileXtKernel */
|
||||
if(ProfileSource != ProfileTime && ProfileSource != ProfileXtKernel)
|
||||
{
|
||||
/* Invalid profile source, do nothing */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set the interval */
|
||||
HL::Pic::WriteApicRegister(APIC_TICR, ProfilingInterval);
|
||||
|
||||
/* Unmask interrupt */
|
||||
LvtRegister.Long = 0;
|
||||
LvtRegister.Mask = 0;
|
||||
LvtRegister.DeliveryMode = APIC_DM_FIXED;
|
||||
LvtRegister.TimerMode = 1;
|
||||
LvtRegister.TriggerMode = APIC_TGM_EDGE;
|
||||
LvtRegister.Vector = APIC_VECTOR_PROFILE;
|
||||
HL::Pic::WriteApicRegister(APIC_TMRLVTR, LvtRegister.Long);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the profile interrupt for the specified profile source.
|
||||
*
|
||||
* @param ProfileSource
|
||||
* Supplies the source of the profile interrupt to stop.
|
||||
*
|
||||
* @return This routine does not return any value.
|
||||
*
|
||||
* @since XT 1.0
|
||||
*/
|
||||
XTAPI
|
||||
VOID
|
||||
HL::Timer::StopProfileInterrupt(IN KPROFILE_SOURCE ProfileSource)
|
||||
{
|
||||
APIC_LVT_REGISTER LvtRegister;
|
||||
|
||||
/* Handle only ProfileTime and ProfileXtKernel */
|
||||
if(ProfileSource != ProfileTime && ProfileSource != ProfileXtKernel)
|
||||
{
|
||||
/* Invalid profile source, do nothing */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Mask interrupt */
|
||||
LvtRegister.Long = 0;
|
||||
LvtRegister.Mask = 1;
|
||||
LvtRegister.DeliveryMode = APIC_DM_FIXED;
|
||||
LvtRegister.TimerMode = 1;
|
||||
LvtRegister.TriggerMode = APIC_TGM_EDGE;
|
||||
LvtRegister.Vector = APIC_VECTOR_PROFILE;
|
||||
HL::Pic::WriteApicRegister(APIC_TMRLVTR, LvtRegister.Long);
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <hl/irq.hh>
|
||||
#include <hl/pic.hh>
|
||||
#include <hl/runlevel.hh>
|
||||
#include <hl/timer.hh>
|
||||
|
||||
|
||||
#endif /* __XTOSKRNL_HL_HH */
|
||||
|
||||
38
xtoskrnl/includes/hl/timer.hh
Normal file
38
xtoskrnl/includes/hl/timer.hh
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* PROJECT: ExectOS
|
||||
* COPYRIGHT: See COPYING.md in the top level directory
|
||||
* FILE: xtoskrnl/includes/hl/timer.hh
|
||||
* DESCRIPTION: ACPI Timer support
|
||||
* DEVELOPERS: Aiken Harris <harraiken91@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef __XTOSKRNL_HL_TIMER_HH
|
||||
#define __XTOSKRNL_HL_TIMER_HH
|
||||
|
||||
#include <xtos.hh>
|
||||
|
||||
|
||||
/* Hardware Layer */
|
||||
namespace HL
|
||||
{
|
||||
class Timer
|
||||
{
|
||||
private:
|
||||
STATIC ULONG ProfilingInterval;
|
||||
STATIC ULONG TimerFrequency;
|
||||
|
||||
public:
|
||||
STATIC XTAPI VOID InitializeApicTimer(VOID);
|
||||
STATIC XTAPI ULONG_PTR SetProfileInterval(IN ULONG_PTR Interval);
|
||||
STATIC XTAPI VOID StartProfileInterrupt(IN KPROFILE_SOURCE ProfileSource);
|
||||
STATIC XTAPI VOID StopProfileInterrupt(IN KPROFILE_SOURCE ProfileSource);
|
||||
|
||||
private:
|
||||
STATIC XTAPI XTSTATUS CalibrateApicTimer();
|
||||
STATIC XTAPI XTSTATUS GetApicTimerFrequency(OUT PULONG Frequency);
|
||||
STATIC XTAPI VOID PitStallExecution(IN ULONG Us);
|
||||
STATIC XTAPI VOID StallExecution(IN ULONG MicroSeconds);
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* __XTOSKRNL_HL_TIMER_HH */
|
||||
Reference in New Issue
Block a user