diff --git a/sdk/xtdk/hltypes.h b/sdk/xtdk/hltypes.h index c107154..b38033e 100644 --- a/sdk/xtdk/hltypes.h +++ b/sdk/xtdk/hltypes.h @@ -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__ diff --git a/sdk/xtdk/ketypes.h b/sdk/xtdk/ketypes.h index 1c7d5ab..d9e36e6 100644 --- a/sdk/xtdk/ketypes.h +++ b/sdk/xtdk/ketypes.h @@ -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 { diff --git a/sdk/xtdk/xtstruct.h b/sdk/xtdk/xtstruct.h index 3929b09..a407660 100644 --- a/sdk/xtdk/xtstruct.h +++ b/sdk/xtdk/xtstruct.h @@ -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; diff --git a/xtoskrnl/CMakeLists.txt b/xtoskrnl/CMakeLists.txt index a0ead91..17b8691 100644 --- a/xtoskrnl/CMakeLists.txt +++ b/xtoskrnl/CMakeLists.txt @@ -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 diff --git a/xtoskrnl/hl/amd64/timer.cc b/xtoskrnl/hl/amd64/timer.cc new file mode 100644 index 0000000..371ca5a --- /dev/null +++ b/xtoskrnl/hl/amd64/timer.cc @@ -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 + */ + +#include + + +/* Include common Timer interface */ +#include ARCH_COMMON(timer.cc) diff --git a/xtoskrnl/hl/i686/timer.cc b/xtoskrnl/hl/i686/timer.cc new file mode 100644 index 0000000..c68f92b --- /dev/null +++ b/xtoskrnl/hl/i686/timer.cc @@ -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 + */ + +#include + + +/* Include common Timer interface */ +#include ARCH_COMMON(timer.cc) diff --git a/xtoskrnl/hl/x86/timer.cc b/xtoskrnl/hl/x86/timer.cc new file mode 100644 index 0000000..5d21bab --- /dev/null +++ b/xtoskrnl/hl/x86/timer.cc @@ -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 + */ + +#include + + +/** + * 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); +} diff --git a/xtoskrnl/includes/hl.hh b/xtoskrnl/includes/hl.hh index a3eda53..72ce751 100644 --- a/xtoskrnl/includes/hl.hh +++ b/xtoskrnl/includes/hl.hh @@ -21,6 +21,7 @@ #include #include #include +#include #endif /* __XTOSKRNL_HL_HH */ diff --git a/xtoskrnl/includes/hl/timer.hh b/xtoskrnl/includes/hl/timer.hh new file mode 100644 index 0000000..c6f94c3 --- /dev/null +++ b/xtoskrnl/includes/hl/timer.hh @@ -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 + */ + +#ifndef __XTOSKRNL_HL_TIMER_HH +#define __XTOSKRNL_HL_TIMER_HH + +#include + + +/* 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 */