/*++ Copyright (c) 2000 Microsoft Corporation Module Name: ixclock.c Abstract: This module implements the code necessary to field and process the interval clock interrupts. Author: Shie-Lin Tzong (shielint) 12-Jan-1990 Environment: Kernel mode only. Revision History: bryanwi 20-Sep-90 Add KiSetProfileInterval, KiStartProfileInterrupt, KiStopProfileInterrupt procedures. KiProfileInterrupt ISR. KiProfileList, KiProfileLock are delcared here. shielint 10-Dec-90 Add performance counter support. Move system clock to irq8, ie we now use RTC to generate system clock. Performance count and Profile use timer 1 counter 0. The interval of the irq0 interrupt can be changed by KiSetProfileInterval. Performance counter does not care about the interval of the interrupt as long as it knows the rollover count. Note: Currently I implemented 1 performance counter for the whole i386 NT. John Vert (jvert) 11-Jul-1991 Moved from ke\i386 to hal\i386. Removed non-HAL stuff shie-lin tzong (shielint) 13-March-92 Move System clock back to irq0 and use RTC (irq8) to generate profile interrupt. Performance counter and system clock use time1 counter 0 of 8254. Landy Wang (corollary!landy) 04-Dec-92 Move much code into separate modules for easy inclusion by various HAL builds. Forrest Foltz (forrestf) 24-Oct-2000 Ported from ixclock.asm to ixclock.c --*/ #include "halcmn.h" #define COUNTER_TICKS_AVG_SHIFT 4 #define COUNTER_TICKS_FOR_AVG 16 #define FRAME_COPY_SIZE 64 // // Convert the interval to rollover count for 8254 Timer1 device. // Timer1 counts down a 16 bit value at a rate of 1.193181667M counts-per-sec. // (The main crystal freq is 14.31818, and this is a divide by 12) // // The best fit value closest to 10ms is 10.0144012689ms: // ROLLOVER_COUNT 11949 // TIME_INCREMENT 100144 // Calculated error is -.0109472 s/day // // // The following table contains 8254 values timer values to use at // any given ms setting from 1ms - 15ms. All values work out to the // same error per day (-.0109472 s/day). // typedef struct _TIMER_TABLE_ENTRY { USHORT RolloverCount; ULONG TimeIncrement; } TIMER_TABLE_ENTRY, *PTIMER_TABLE_ENTRY; TIMER_TABLE_ENTRY HalpTimerTable[] = { { 0, 0 }, // dummy entry to zero-base array { 1197, 10032 }, // 1ms { 2394, 20064 }, // 2ms { 3591, 30096 }, // 3ms { 4767, 39952 }, // 4ms { 5964, 49984 }, // 5ms { 7161, 60016 }, // 6ms { 8358, 70048 }, // 7ms { 9555, 80080 }, // 8ms { 10731, 89936 }, // 9ms { 11949, 100144 } // 10ms }; #define MIN_TIMER_INCREMENT 1 #define MAX_TIMER_INCREMENT \ (sizeof(HalpTimerTable) / sizeof(TIMER_TABLE_ENTRY) - 1) #define HalpMinimumTimerTableEntry (&HalpTimerTable[MIN_TIMER_INCREMENT]) #define HalpMaximumTimerTableEntry (&HalpTimerTable[MAX_TIMER_INCREMENT]) // // External function prototypes // VOID HalpMcaQueueDpc( VOID ); // // External declarations // extern ULONG HalpTimerWatchdogEnabled; // // Globals // ULONG64 HalpWatchdogCount; ULONG64 HalpWatchdogTsc; ULONG64 HalpTimeBias; BOOLEAN HalpClockMcaQueueDpc; PTIMER_TABLE_ENTRY HalpNextMSRate = NULL; ULONG HalpCurrentMSRateTableIndex; #define HalpCurrentMSRate (&HalpTimerTable[HalpCurrentMSRateTableIndex+1]) // // Forward function declarations // VOID HalpSetMSRate( IN PTIMER_TABLE_ENTRY TableEntry ); // // Inline functions // __forceinline VOID HalpCalibrateWatchdog( VOID ) { if (HalpTimerWatchdogEnabled == 0) { return; } // // Initializethe timer latency watchdog // HalpWatchdogTsc = ReadTimeStampCounter(); HalpWatchdogCount = 0; } __forceinline VOID HalpCheckWatchdog( VOID ) { if (HalpTimerWatchdogEnabled == 0) { return; } AMD64_IMPLEMENT; } // // Module functions // VOID HalpInitializeClock ( VOID ) /*++ Routine Description: This routine initialize system time clock using 8254 timer1 counter 0 to generate an interrupt at every 15ms interval at 8259 irq0. See the definitions of TIME_INCREMENT and ROLLOVER_COUNT if clock rate needs to be changed. Arguments: None Return Value: None --*/ { ULONG maxTimeIncrement; ULONG minTimeIncrement; // // Indicate to the kernel the minimum and maximum tick increments // minTimeIncrement = HalpMinimumTimerTableEntry->TimeIncrement; maxTimeIncrement = HalpMaximumTimerTableEntry->TimeIncrement; KeSetTimeIncrement(minTimeIncrement,maxTimeIncrement); // // Set the initial clock rate to the slowest permissible // HalpSetMSRate(HalpMaximumTimerTableEntry); } VOID HalpAcpiTimerCalibratePerfCount ( IN PLONG volatile Number, IN ULONG64 NewCount ) /*++ Routine Description: This routine resets the performance counter value for the current processor to zero. The reset is done such that the resulting value is closely synchronized with other processors in the configuration. Arguments: Number - Supplies a pointer to count of the number of processors in the configuration. NewCount - Supplies the value to synchronize the counter too Return Value: None. --*/ { PKPCR pcr; ULONG64 perfCount; pcr = KeGetPcr(); if (pcr->Number == 0) { // // Only execute on the boot processor. // perfCount = QueryTimer().QuadPart; // // Compute how far the current count is from the target count, // and adjust TimerInfo.Bias accordingly. // HalpTimeBias = NewCount - perfCount; } // // Wait for all processors to reach this point // InterlockedDecrement(Number); while (*Number != 0) { PAUSE_PROCESSOR } } VOID HalpSetMSRate( IN PTIMER_TABLE_ENTRY TableEntry ) /*++ Routine Description This routine programs the 8254 with a new rollover count. Artuments TableEntry - Supplies a pointer to an entry within HalpTimerTable. Return value: None. --*/ { USHORT rollover; ULONG interruptsEnabled; // // Program the 8254 to generate the new timer interrupt rate. // rollover = TableEntry->RolloverCount; interruptsEnabled = HalpDisableInterrupts(); WRITE_PORT_UCHAR(TIMER1_CONTROL_PORT, TIMER_COMMAND_COUNTER0 + TIMER_COMMAND_RW_16BIT + TIMER_COMMAND_MODE2); IO_DELAY(); WRITE_PORT_USHORT_PAIR (TIMER1_DATA_PORT0, TIMER1_DATA_PORT0, rollover); IO_DELAY(); HalpRestoreInterrupts(interruptsEnabled); // // Recalibrate the timer watchdog // HalpCalibrateWatchdog(); // // Update the global representing the timer rate // HalpCurrentMSRateTableIndex = (ULONG)(TableEntry - HalpTimerTable) - 1; } BOOLEAN HalpClockInterrupt ( IN PKINTERRUPT Interrupt, IN PVOID ServiceContext ) /*++ Routine Description This routine is entered as the result of an interrupt generated by CLOCK. Its function is to dismiss the interrupt, raise system Irql to CLOCK2_LEVEL, update performance counter and transfer control to the standard system routine to update the system time and the execution time of the current thread and process. Arguments: Interrupt - Supplies a pointer to the interrupt object ServiceContext - Not used Return value: --*/ { ULONG timeIncrement; // // Capture the time increment for the now-occuring tick. This is // done here because HalpCurrentMSRate will change if a rate change // is pending. // timeIncrement = HalpCurrentMSRate->TimeIncrement; // // Give the watchdog timer a chance to do its work // HalpCheckWatchdog(); // // Check whether an MCA dpc should be queued // if (HalpClockMcaQueueDpc != FALSE) { HalpClockMcaQueueDpc = FALSE; HalpMcaQueueDpc(); } // // Check whether the clock interrupt frequency should be changed. // if (HalpNextMSRate != NULL) { HalpNextMSRate = NULL; HalpSetMSRate(HalpNextMSRate); } // // Indicate to the kernel that a clock tick has occured. // KeUpdateSystemTime(Interrupt->TrapFrame,timeIncrement); return TRUE; } ULONG HalpAcpiTimerSetTimeIncrement ( IN ULONG DesiredIncrement ) /*++ Routine Description: This routine initialize system time clock to generate an interrupt at every DesiredIncrement interval. Arguments: DesiredIncrement - desired interval between every timer tick (in 100ns unit.) Return Value: The *REAL* time increment set. --*/ { ULONG incMs; PTIMER_TABLE_ENTRY tableEntry; // // Convert the desired time inecrement to milliseconds // incMs = DesiredIncrement / 10000; // // Place the value within the range supported by this hal // if (incMs > MAX_TIMER_INCREMENT) { incMs = MAX_TIMER_INCREMENT; } else if (incMs < MIN_TIMER_INCREMENT) { incMs = MIN_TIMER_INCREMENT; } // // If the new rate is different than the current rate, indicate via // a non-null HalpNextMSRate that a new timer rate is expected. This // will be done at the next timer ISR. // tableEntry = &HalpTimerTable[incMs]; if (tableEntry != HalpCurrentMSRate) { HalpNextMSRate = tableEntry; } // // Finally, return the timer increment that will actually be used. // return tableEntry->TimeIncrement; }