diff options
Diffstat (limited to 'private/ntos/nthals/halws3/i386/w3clock.asm')
-rw-r--r-- | private/ntos/nthals/halws3/i386/w3clock.asm | 782 |
1 files changed, 782 insertions, 0 deletions
diff --git a/private/ntos/nthals/halws3/i386/w3clock.asm b/private/ntos/nthals/halws3/i386/w3clock.asm new file mode 100644 index 000000000..b45761307 --- /dev/null +++ b/private/ntos/nthals/halws3/i386/w3clock.asm @@ -0,0 +1,782 @@ + title "Interval Clock Interrupt" +;++ +; +; Copyright (c) 1989 Microsoft Corporation +; Copyright (c) 1993 Sequent Computer Systems, Inc. +; +; Module Name: +; +; w3clock.asm +; +; Abstract: +; +; This module implements the code necessary to field and process the +; interval clock interrupt. +; +; Author: +; +; Phil Hochstetler (phil@sequent.com) +; +; Environment: +; +; Kernel mode only. +; +; Revision History: +; +;-- + +.386p + .xlist +include hal386.inc +include callconv.inc +include i386\kimacro.inc +include mac386.inc +include i386\apic.inc +include i386\ixcmos.inc +include i386\w3.inc + .list + + EXTRNP _DbgBreakPoint,0,IMPORT + EXTRNP _KeUpdateSystemTime,0 + EXTRNP _KeUpdateRunTime,1,IMPORT + EXTRNP _KeProfileInterrupt,1,IMPORT + EXTRNP Kei386EoiHelper,0,IMPORT + EXTRNP _KeSetTimeIncrement,2,IMPORT + EXTRNP _HalEndSystemInterrupt,2 + EXTRNP _HalBeginSystemInterrupt,3 + extrn _HalpLocalUnitBase:DWORD + extrn _HalpW3PostRegisterImage:DWORD + +; +; Constants used to initialize timer 0 +; + +TIMER1_DATA_PORT0 EQU 40H ; Timer1, channel 0 data port +TIMER1_CONTROL_PORT0 EQU 43H ; Timer1, channel 0 control port +TIMER2_DATA_PORT0 EQU 48H ; Timer1, channel 0 data port +TIMER2_CONTROL_PORT0 EQU 4BH ; Timer1, channel 0 control port +TIMER1_IRQ EQU 0 ; Irq 0 for timer1 interrupt + +COMMAND_8254_COUNTER0 EQU 00H ; Select count 0 +COMMAND_8254_RW_16BIT EQU 30H ; Read/Write LSB firt then MSB +COMMAND_8254_MODE2 EQU 4 ; Use mode 2 +COMMAND_8254_BCD EQU 0 ; Binary count down +COMMAND_8254_LATCH_READ EQU 0 ; Latch read command + +PERFORMANCE_FREQUENCY EQU 1193182 + +; +; ==== Values used for System Clock ==== +; + +_DATA SEGMENT DWORD PUBLIC 'DATA' + +; +; 8254 spinlock. This must be acquired before touching the 8254 chip. +; + public _Halp8254Lock +_Halp8254Lock dd 0 + + + public HalpPerfCounterLow, HalpPerfCounterHigh + public HalpLastPerfCounterLow, HalpLastPerfCounterHigh +HalpPerfCounterLow dd 0 +HalpPerfCounterHigh dd 0 +HalpLastPerfCounterLow dd 0 +HalpLastPerfCounterHigh dd 0 + + + public HalpCurrentRollOver, HalpCurrentTimeIncrement +HalpCurrentRollOver dd 0 +HalpCurrentTimeIncrement dd 0 + +; +; 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 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). +; + + public HalpRollOverTable + + ; RollOver Time + ; Count Increment MS +HalpRollOverTable dd 1197, 10032 ; 1ms + dd 2394, 20064 ; 2 ms + dd 3591, 30096 ; 3 ms + dd 4767, 39952 ; 4 ms + dd 5964, 49984 ; 5 ms + dd 7161, 60016 ; 6 ms + dd 8358, 70048 ; 7 ms + dd 9555, 80080 ; 8 ms + dd 10731, 89936 ; 9 ms + dd 11949, 100144 ; 10 ms + dd 13125, 110000 ; 11 ms + dd 14322, 120032 ; 12 ms + dd 15519, 130064 ; 13 ms + dd 16695, 139920 ; 14 ms + dd 17892, 149952 ; 15 ms + +TimeIncr equ 4 +RollOver equ 0 + + public HalpLargestClockMS, HalpNextMSRate, HalpPendingMSRate +HalpLargestClockMS dd 15 ; Table goes to 15MS +HalpNextMSRate dd 0 +HalpPendingMSRate dd 0 + + +_DATA ends + + +INIT SEGMENT DWORD PUBLIC 'CODE' + ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING + + page ,132 + subttl "Initialize Clock" +;++ +; +; VOID +; HalpInitializeClock ( +; ) +; +; Routine Description: +; +; This routine initialize system time clock using 8254 timer1 counter 0 +; to generate an interrupt at every 10ms interval at EISA_CLOCK_VECTOR +; +; The Eisa clock interrupt handler then IPIs all processors at +; APIC_CLOCK_VECTOR +; +; See the definitions of TIME_INCREMENT and ROLLOVER_COUNT if clock rate +; needs to be changed. +; +; This routine assumes it runs during Phase 0 on P0. +; +; Arguments: +; +; None +; +; Return Value: +; +; None. +; +;-- +cPublicProc _HalpInitializeClock ,0 + + mov eax, PCR[PcPrcb] + cmp byte ptr [eax].PbCpuType, 4 ; 486 or better? + jc short @f ; no, skip + + mov HalpLargestClockMS, 10 ; Limit 486's to 10MS +@@: + mov eax, HalpLargestClockMS + mov ecx, HalpRollOverTable.TimeIncr + mov edx, HalpRollOverTable[eax*8-8].TimeIncr + mov eax, HalpRollOverTable[eax*8-8].RollOver + + mov HalpCurrentTimeIncrement, edx + +; +; (ecx) = Min time_incr +; (edx) = Max time_incr +; (eax) = max roll over count +; + + push eax + stdCall _KeSetTimeIncrement, <edx, ecx> + pop ecx + + pushfd ; save caller's eflag + cli ; make sure interrupts are disabled + +; +; Set clock rate +; (ecx) = RollOverCount +; + + mov al,COMMAND_8254_COUNTER0+COMMAND_8254_RW_16BIT+COMMAND_8254_MODE2 + out TIMER1_CONTROL_PORT0, al ;program count mode of timer 0 + IoDelay + mov al, cl + out TIMER1_DATA_PORT0, al ; program timer 0 LSB count + IoDelay + mov al,ch + out TIMER1_DATA_PORT0, al ; program timer 0 MSB count + + popfd ; restore caller's eflag + mov HalpCurrentRollOver, ecx ; Set RollOverCount & initialized + + stdRET _HalpInitializeClock + +stdENDP _HalpInitializeClock + +INIT ends + +_TEXT$03 SEGMENT DWORD PUBLIC 'CODE' + ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING + + page ,132 + subttl "Query Performance Counter" +;++ +; +; LARGE_INTEGER +; KeQueryPerformanceCounter ( +; OUT PLARGE_INTEGER PerformanceFrequency OPTIONAL +; ) +; +; Routine Description: +; +; This routine returns current 64-bit performance counter and, +; optionally, the Performance Frequency. +; +; Note this routine can NOT be called at Profiling interrupt +; service routine. Because this routine depends on IRR0 to determine +; the actual count. +; +; Also note that the performace counter returned by this routine +; is not necessary the value when this routine is just entered. +; The value returned is actually the counter value at any point +; between the routine is entered and is exited. +; +; Arguments: +; +; PerformanceFrequency [TOS+4] - optionally, supplies the address +; of a variable to receive the performance counter frequency. +; +; Return Value: +; +; Current value of the performance counter will be returned. +; +;-- + +; +; Parameter definitions +; + +KqpcFrequency EQU [esp+12] ; User supplied Performance Frequence + +cPublicProc _KeQueryPerformanceCounter ,1 +cPublicFpo 1, 0 +; +; First check to see if the performance counter has been initialized yet. +; Since the kernel debugger calls KeQueryPerformanceCounter to support the +; !timer command, we need to return something reasonable before Timer +; initialization has occured. Reading garbage off the Timer is not reasonable. +; + cmp HalpCurrentRollOver, 0 + je Kqpc50 + + push ebx + push esi + +Kqpc01: pushfd + cli +Kqpc20: +ifndef NT_UP + lea eax, _Halp8254Lock + ACQUIRE_SPINLOCK eax, Kqpc198 +endif + +; +; Fetch the base value. Note that interrupts are off. +; + +@@: + mov ebx, HalpPerfCounterLow + mov esi, HalpPerfCounterHigh ; [esi:ebx] = Performance counter + + cmp ebx, HalpPerfCounterLow + jne @b + +; +; Fetch the current counter value from the hardware +; + + mov al, COMMAND_8254_LATCH_READ+COMMAND_8254_COUNTER0 + ;Latch PIT Ctr 0 command. + out TIMER1_CONTROL_PORT0, al + IoDelay + in al, TIMER1_DATA_PORT0 ;Read PIT Ctr 0, LSByte. + movzx ecx,al ;Zero upper bytes of (ECX). + IoDelay + in al, TIMER1_DATA_PORT0 ;Read PIT Ctr 0, MSByte. + mov ch, al ;(CX) = PIT Ctr 0 count. + +ifndef NT_UP + lea eax, _Halp8254Lock + RELEASE_SPINLOCK eax +endif + + +; +; Now enable interrupts such that if timer interrupt is pending, it can +; be serviced and update the PerformanceCounter. Note that there could +; be a long time between the sti and cli because ANY interrupt could come +; in in between. +; + + popfd ; don't re-enable interrupts if + nop ; the caller had them off! + + jmp $+2 ; allow interrupt in case counter + ; has wrapped + + pushfd + cli + +; +; Fetch the base value again. +; + +@@: + mov eax, HalpPerfCounterLow + mov edx, HalpPerfCounterHigh ; [edx:eax] = new counter value + + cmp eax, HalpPerfCounterLow + jne @b +; +; Compare the two reads of Performance counter. If they are different, +; start over +; + + cmp eax, ebx + jne Kqpc20 + cmp edx, esi + jne Kqpc20 + + neg ecx ; PIT counts down from 0h + add ecx, HalpCurrentRollOver + jnc short Kqpc60 + +Kqpc30: + add eax, ecx + adc edx, 0 ; [edx:eax] = Final result + + cmp edx, HalpLastPerfCounterHigh + jc short Kqpc70 ; jmp if edx < lastperfcounterhigh + jne short Kqpc35 ; jmp if edx > lastperfcounterhigh + + cmp eax, HalpLastPerfCounterLow + jc short Kqpc70 ; jmp if eax < lastperfcounterlow + +Kqpc35: + mov HalpLastPerfCounterLow, eax + mov HalpLastPerfCounterHigh, edx + + popfd ; restore interrupt flag + + +; +; Return the freq. if caller wants it. +; + cmp dword ptr KqpcFrequency, 0 ; is it a NULL variable? + jz short Kqpc40 ; if z, yes, go exit + + mov ecx, KqpcFrequency ; (ecx)-> Frequency variable + mov DWORD PTR [ecx], PERFORMANCE_FREQUENCY ; Set frequency + mov DWORD PTR [ecx+4], 0 + +Kqpc40: + pop esi ; restore esi and ebx + pop ebx + stdRET _KeQueryPerformanceCounter + +Kqpc50: +; Initialization hasn't occured yet, so just return zeroes. + mov eax, 0 + mov edx, 0 + stdRET _KeQueryPerformanceCounter + +Kqpc60: +; +; The current count is larger then the HalpCurrentRollOver. The only way +; that could happen is if there is an interrupt in route to the processor +; but it was not processed while interrupts were enabled. +; + mov esi, [esp] ; (esi) = flags + mov ecx, HalpCurrentRollOver ; (ecx) = max possible value + popfd ; restore flags + + test esi, EFLAGS_INTERRUPT_MASK + jnz Kqpc01 ; ints are enabled, problem should go away + + pushfd ; fix stack + jmp short Kqpc30 ; ints are disabled, use max count (ecx) + +Kqpc70: +; +; The current count is smaller then the last returned count. The only way +; this should occur is if there is an interrupt in route to the processor +; which was not been processed. +; + + mov ebx, HalpLastPerfCounterLow + mov esi, HalpLastPerfCounterHigh + + mov ecx, ebx + or ecx, esi ; is last returned value 0? + jz short Kqpc35 ; Yes, then just return what we have + + ; sanity check - make sure count is not off by bogus amount + sub ebx, eax + sbb esi, edx + jnz short Kqpc75 ; off by bogus amount + cmp ebx, HalpCurrentRollOver + jg short Kqpc75 ; off by bogus amount + + sub eax, ebx + sbb edx, esi ; (edx:eax) = last returned count + + mov ecx, [esp] ; (ecx) = flags + popfd + + test ecx, EFLAGS_INTERRUPT_MASK + jnz Kqpc01 ; ints enabled, problem should go away + + pushfd ; fix stack + jmp Kqpc35 ; ints disabled, just return last count + +Kqpc75: + popfd + xor eax, eax ; reset bogus values + mov HalpLastPerfCounterLow, eax + mov HalpLastPerfCounterHigh, eax + jmp Kqpc01 ; go try again + +ifndef NT_UP +Kqpc198: popfd + SPIN_ON_SPINLOCK eax,<Kqpc01> +endif + +stdENDP _KeQueryPerformanceCounter + +;++ +; +; VOID +; HalCalibratePerformanceCounter ( +; IN volatile PLONG Number +; ) +; +; /*++ +; +; Routine Description: +; +; This routine calibrates the performance counter value for a +; multiprocessor system. The calibration can be done by zeroing +; the current performance counter, or by calculating a per-processor +; skewing between each processors counter. +; +; Arguments: +; +; Number - Supplies a pointer to count of the number of processors in +; the configuration. +; +; Return Value: +; +; None. +;-- +cPublicProc _HalCalibratePerformanceCounter,1 +cPublicFpo 1, 0 + + mov eax, [esp+4] ; ponter to Number +cPublicFpo 1, 1 + pushfd ; save previous interrupt state + cli ; disable interrupts (go to high_level) + + lock dec dword ptr [eax] ; count down + +@@: cmp dword ptr [eax], 0 ; wait for all processors to signal + jnz short @b + + ; + ; Nothing to calibrate on this apic based MP machine. There is only + ; a single 8254 device in use + ; + +cPublicFpo 1, 0 + popfd ; restore interrupt flag + stdRET _HalCalibratePerformanceCounter + +stdENDP _HalCalibratePerformanceCounter + + + + page ,132 + subttl "System Clock Interrupt" +;++ +; +; Routine Description: +; +; +; This routine is entered as the result of an interrupt generated by CLOCK2. +; 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: +; +; None +; Interrupt is disabled +; +; Return Value: +; +; Does not return, jumps directly to KeUpdateSystemTime, which returns +; +; Sets Irql = CLOCK2_LEVEL and dismisses the interrupt +; +;-- + + ENTER_DR_ASSIST Hci_a, Hci_t + +cPublicProc _HalpClockInterrupt ,0 + +; +; Save machine state in trap frame +; + + ENTER_INTERRUPT Hci_a, Hci_t + +; +; (esp) - base of trap frame +; +; dismiss interrupt and raise Irql +; + + push APIC_CLOCK_VECTOR + sub esp, 4 ; allocate space to save OldIrql + stdCall _HalBeginSystemInterrupt, <CLOCK2_LEVEL,APIC_CLOCK_VECTOR,esp> + or al,al ; check for spurious interrupt + jz Hci100 + +; +; Turn off (on) the processor active light to reflect whether +; we are (are not) currently executing the Idle Thread. +; + + mov edi, PCR[PcPrcb] ; (edi) -> Prcb + mov eax, [edi].PbCurrentThread + cmp eax, [edi].PbIdleThread ; running idle thread? + setnz al ; set al to desired light state + cmp al, byte ptr PCR[PcHal.ProcLightState] + jne Hci05 +Hci10: + +ifndef NT_UP + cmp byte ptr PCR[PcHal.PcrNumber], 0 ; Only P0 Will update System Time + je Do_P0Timer + + ; + ; All processors will update RunTime for current thread + ; + + sti + ; TOS const PreviousIrql + stdCall _KeUpdateRunTime,<dword ptr [esp]> + + INTERRUPT_EXIT ; lower irql to old value, iret + + ; + ; We don't return here + ; + + +Do_P0Timer: + +endif + +; +; Update front panel light show +; + mov eax, _HalpW3PostRegisterImage ; get current bits + and al, 01111111b ; clear disk error bit + out PostRegisterPort, al ; write PostCode Reg + +; +; Update performance counter +; + + mov eax, HalpCurrentRollOver + add HalpPerfCounterLow, eax ; update performace counter + adc HalpPerfCounterHigh, dword ptr 0 + + mov eax, HalpCurrentTimeIncrement +Hci30: + +; +; (esp) = OldIrql +; (esp+4) = Vector +; (esp+8) = base of trap frame +; ebp = trap frame +; eax = time increment +; + cmp HalpNextMSRate, 0 ; New clock rate desired? + jz _KeUpdateSystemTime@0 ; No, process tick + +; +; Time of clock frequency is being changed. See if the 8254 was +; was reprogrammed for a new rate during last tick +; + cmp HalpPendingMSRate, 0 ; Was a new rate set durning last + jnz short Hci50 ; tick? Yes, go update globals + +Hci40: +; (eax) = time increment for current tick + +; +; A new clock rate needs to be set. Setting the rate here will +; cause the tick after the next tick to be at the new rate. +; (the next tick is already in progress by the 8254 and will occur +; at the same rate as this tick) +; +Kci01: pushfd + cli +ifndef NT_UP + lea ecx, _Halp8254Lock + ACQUIRE_SPINLOCK ecx, Kci198 +endif + mov ebx, HalpNextMSRate + mov HalpPendingMSRate, ebx ; pending rate + + mov ecx, HalpRollOverTable[ebx*8-8].RollOver + +; +; Set clock rate +; (ecx) = RollOverCount +; + + push eax ; save current tick's rate + + + mov al,COMMAND_8254_COUNTER0+COMMAND_8254_RW_16BIT+COMMAND_8254_MODE2 + out TIMER1_CONTROL_PORT0, al ;program count mode of timer 0 + IoDelay + mov al, cl + out TIMER1_DATA_PORT0, al ; program timer 0 LSB count + IoDelay + mov al,ch + out TIMER1_DATA_PORT0, al ; program timer 0 MSB count + +ifndef NT_UP + lea eax, _Halp8254Lock + RELEASE_SPINLOCK eax +endif + + pop eax + popfd + jmp Hci30 ; dispatch this tick + +Hci50: +; +; The next tick will occur at the rate which was programmed during the last +; tick. Update globals for new rate which starts with the next tick. +; +; (eax) = time increment for current tick +; + mov ebx, HalpPendingMSRate + mov ecx, HalpRollOverTable[ebx*8-8].RollOver + mov edx, HalpRollOverTable[ebx*8-8].TimeIncr + + mov HalpCurrentRollOver, ecx + mov HalpCurrentTimeIncrement, edx ; next tick rate + mov HalpPendingMSRate, 0 ; no longer pending, clear it + + cmp ebx, HalpNextMSRate ; new rate == NextRate? + jne Hci40 ; no, go set new pending rate + + mov HalpNextMSRate, 0 ; we are at this rate, clear it + jmp Hci30 ; process this tick + +Hci100: + add esp, 8 ; spurious, no EndOfInterrupt + SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi + +ifndef NT_UP +Kci198: popfd + SPIN_ON_SPINLOCK ecx,<Kqpc01> +endif + +; +; Update the state of hardware lights and state variable +; +Hci05: + cmp al, 1 ; identify light going + je Hci06 ; on (or going off) + +; Turn light off that was on + + xor eax, eax + mov al, byte ptr PCR[PcHal.PcrNumber] ; get processor number + lock btr _HalpW3PostRegisterImage, eax ; clear the bit + mov byte ptr PCR[PcHal.ProcLightState], 0 ; update flag + jmp hci10 ; return + +; Turn light on that was off +Hci06: + xor eax, eax + mov al, byte ptr PCR[PcHal.PcrNumber] ; get processor number + lock bts _HalpW3PostRegisterImage, eax ; set the bit + mov byte ptr PCR[PcHal.ProcLightState], 1 ; update flag + jmp hci10 ; return + +stdENDP _HalpClockInterrupt + +;++ +; +; ULONG +; HalSetTimeIncrement ( +; 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. +;-- +cPublicProc _HalSetTimeIncrement,1 + + mov eax, [esp+4] ; desired setting + xor edx, edx + mov ecx, 10000 + div ecx ; round to MS + + cmp eax, HalpLargestClockMS ; MS > max? + jc short @f + mov eax, HalpLargestClockMS ; yes, use max +@@: + or eax, eax ; MS < min? + jnz short @f + inc eax ; yes, use min +@@: + mov HalpNextMSRate, eax + + mov eax, HalpRollOverTable[eax*8-8].TimeIncr + stdRET _HalSetTimeIncrement + +stdENDP _HalSetTimeIncrement +_TEXT$03 ends + + end |