summaryrefslogtreecommitdiffstats
path: root/private/ntos/nthals/halx86/i386/ixclock.asm
diff options
context:
space:
mode:
authorAdam <you@example.com>2020-05-17 05:51:50 +0200
committerAdam <you@example.com>2020-05-17 05:51:50 +0200
commite611b132f9b8abe35b362e5870b74bce94a1e58e (patch)
treea5781d2ec0e085eeca33cf350cf878f2efea6fe5 /private/ntos/nthals/halx86/i386/ixclock.asm
downloadNT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.gz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.bz2
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.lz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.xz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.zst
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.zip
Diffstat (limited to 'private/ntos/nthals/halx86/i386/ixclock.asm')
-rw-r--r--private/ntos/nthals/halx86/i386/ixclock.asm760
1 files changed, 760 insertions, 0 deletions
diff --git a/private/ntos/nthals/halx86/i386/ixclock.asm b/private/ntos/nthals/halx86/i386/ixclock.asm
new file mode 100644
index 000000000..6abb583cb
--- /dev/null
+++ b/private/ntos/nthals/halx86/i386/ixclock.asm
@@ -0,0 +1,760 @@
+
+ title "Interval Clock Interrupt"
+;++
+;
+; Copyright (c) 1989 Microsoft Corporation
+;
+; Module Name:
+;
+; ixclock.asm
+;
+; Abstract:
+;
+; This module implements the code necessary to field and process the
+; interval clock interrupt.
+;
+; 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.
+;
+;--
+
+.386p
+ .xlist
+include hal386.inc
+include callconv.inc ; calling convention macros
+include i386\ix8259.inc
+include i386\kimacro.inc
+include mac386.inc
+include i386\ixcmos.inc
+ .list
+
+ EXTRNP _KeUpdateSystemTime,0
+ EXTRNP Kei386EoiHelper,0,IMPORT
+ EXTRNP _KeSetTimeIncrement,2,IMPORT
+ EXTRNP _HalEndSystemInterrupt,2
+ EXTRNP _HalBeginSystemInterrupt,3
+ EXTRNP _HalpReleaseCmosSpinLock ,0
+ EXTRNP _HalpMcaQueueDpc, 0
+
+;
+; 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'
+
+;
+; The following array stores the per microsecond loop count for each
+; central processor.
+;
+
+;
+; 8254 spinlock.
+;
+ 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
+
+ public _HalpClockWork, _HalpClockSetMSRate, _HalpClockMcaQueueDpc
+_HalpClockWork label dword
+ _HalpClockSetMSRate db 0
+ _HalpClockMcaQueueDpc db 0
+ _bReserved1 db 0
+ _bReserved2 db 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 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).
+;
+
+ public HalpRollOverTable
+
+ ; RollOver Time
+ ; Count Increment MS
+HalpRollOverTable dd 1197, 10032 ; 1 ms
+ 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 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.
+;
+;--
+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
+;
+; 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 8254
+; initialization has occured. Reading garbage off the 8254 is not reasonable.
+;
+ cmp HalpCurrentRollOver, 0
+ je Kqpc50
+
+ push ebx
+ push esi
+
+Kqpc01: pushfd
+ cli
+Kqpc20:
+
+;
+; Fetch the base value. Note that interrupts are off.
+;
+
+ mov ebx, HalpPerfCounterLow
+ mov esi, HalpPerfCounterHigh ; [esi:ebx] = Performance counter
+
+;
+; 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.
+ IODelay
+ movzx ecx,al ;Zero upper bytes of (ECX).
+ in al, TIMER1_DATA_PORT0 ;Read PIT Ctr 0, MSByte.
+ mov ch, al ;(CX) = PIT Ctr 0 count.
+
+;
+; 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!
+ ; (kernel debugger calls this function
+ ; with interrupts disabled)
+
+ jmp $+2 ; allow interrupt in case counter
+ ; has wrapped
+
+ pushfd
+ cli
+
+;
+; Fetch the base value again.
+;
+; Note: it's possible that the counter wrapped before we read the value
+; and that the timer tick interrupt did not occur during while interrupts
+; where enabled. (ie, there's a delay between when the device raises the
+; interrupt and when the processor see it).
+;
+;
+; note *2 -
+
+
+ mov eax, HalpPerfCounterLow
+ mov edx, HalpPerfCounterHigh ; [edx:eax] = new counter value
+
+;
+; Compare the two reads of Performance counter. If they are different,
+; start over
+;
+
+ cmp eax, ebx
+ jne short Kqpc20
+ cmp edx, esi
+ jne short 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
+
+stdENDP _KeQueryPerformanceCounter
+
+;++
+;
+; VOID
+; HalCalibratePerformanceCounter (
+; IN volatile PLONG Number
+; )
+;
+; /*++
+;
+; 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.
+;
+; Return Value:
+;
+; None.
+;--
+cPublicProc _HalCalibratePerformanceCounter,1
+ mov eax, [esp+4] ; ponter to Number
+ 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 a UP machine...
+ ;
+
+ 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 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:
+;
+; 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
+;
+
+ifdef MCA
+
+;
+; Special hack for MCA machines
+;
+
+ in al, 61h
+ jmp $+2
+ or al, 80h
+ out 61h, al
+ jmp $+2
+
+endif ; MCA
+
+
+;
+; Dismiss interrupt and raise irq level to clock2 level
+;
+
+Hci10:
+ push CLOCK_VECTOR
+ sub esp, 4 ; allocate space to save OldIrql
+ stdCall _HalBeginSystemInterrupt, <CLOCK2_LEVEL, CLOCK_VECTOR, esp>
+
+ or al,al ; check for spurious interrupt
+ jz Hci100
+
+;
+; Update performance counter
+;
+
+ xor ebx, ebx
+ mov eax, HalpCurrentRollOver
+ add HalpPerfCounterLow, eax ; update performace counter
+ adc HalpPerfCounterHigh, ebx
+
+;
+; Check for any more work
+;
+ mov eax, HalpCurrentTimeIncrement
+
+ cmp _HalpClockWork, ebx ; Any clock interrupt work desired?
+ jz _KeUpdateSystemTime@0 ; No, process tick
+
+ cmp _HalpClockMcaQueueDpc, bl
+ je short Hci20
+
+ mov _HalpClockMcaQueueDpc, bl
+
+;
+; Queue MCA Dpc
+;
+
+ push eax
+ stdCall _HalpMcaQueueDpc ; Queue MCA Dpc
+ pop eax
+
+
+Hci20:
+;
+; (esp) = OldIrql
+; (esp+4) = Vector
+; (esp+8) = base of trap frame
+; ebp = trap frame
+; eax = time increment
+; ebx = 0
+;
+ cmp _HalpClockSetMSRate, bl ; 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, ebx ; 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)
+;
+ 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
+
+ pop eax
+
+;
+; (esp) = OldIrql
+; (esp+4) = Vector
+; (esp+8) = base of trap frame
+; ebp = trap frame
+; eax = time increment
+;
+ jmp _KeUpdateSystemTime@0 ; 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 short Hci40 ; no, go set new pending rate
+
+ mov _HalpClockSetMSRate, 0 ; all done setting new rate
+ jmp _KeUpdateSystemTime@0 ; dispatch this tick
+
+Hci100:
+ add esp, 8 ; spurious, no EndOfInterrupt
+ SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi
+
+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 _HalpClockSetMSRate, 1 ; New clock rate desired.
+
+ mov eax, HalpRollOverTable[eax*8-8].TimeIncr
+ stdRET _HalSetTimeIncrement
+
+stdENDP _HalSetTimeIncrement
+_TEXT$03 ends
+
+ end