title "Initialize Stall Execution for the Corollary MP machines" ;++ ; ;Copyright (c) 1992, 1993, 1994 Corollary Inc ; ;Module Name: ; ; cb1stall.asm ; ;Abstract: ; ; This module contains various stall initialization, clock and performance ; counter routines. ; ;Author: ; ; Landy Wang (landy@corollary.com) 05-Oct-1992 ; ;Revision History: ; ;-- .386p .xlist include hal386.inc include i386\kimacro.inc include callconv.inc ; calling convention macros include cbus.inc include mac386.inc include i386\ix8259.inc EXTRNP _HalBeginSystemInterrupt,3 EXTRNP _HalEndSystemInterrupt,2 .list ; ; the CLKIN pin of the 82489DX is clocking at 32MHz (32000000) on the bridge, ; and 33-1/3 (333333333) MHz on the additional processor cards. ; ; divide this by 1,000,000 to get clocking in a microsecond --> ~33. ; 33 * 16 microseconds == 528 == 0x210 ; CLKIN_ONE_SECOND EQU 32000000 ; in APIC CLKIN units ; CLKIN_ONE_SECOND EQU 33333333 ; in APIC CLKIN units CLKIN_SIXTEEN_MS EQU 210h ; 16 microseconds (CLKIN) CLKIN_SIXTEEN_MS_SHIFT EQU 4 ; shift 16 microseconds to 1ms CLKIN_ENABLE_ONESHOT EQU 0h ; enable one-shot CLKIN interrupt CLKIN_ENABLE_PERIODIC EQU 20000h ; enable periodic CLKIN interrupts CLKIN_DISABLE EQU 10000h ; mask off CLKIN interrupts APIC_TIMER_MILLISECOND EQU 32000 ; timer units to == 1 millisecond APIC_TIMER_MICROSECOND EQU 33 ; timer units to == 1 microsecond TIMER_VECTOR_ENTRY EQU 320h ; timer vector table entry 0 address INITIAL_COUNT_REG EQU 380h ; poke here to set the initial count CURRENT_COUNT_REG EQU 390h ; current counter countdown is here D_INT032 EQU 8E00h ; access word for 386 ring 0 int gate CBUS1_PERF_TASKPRI EQU 0DDh ; vector generated by 8254 timer ; ; The default interval between clock interrupts is ; 10 milliseconds == 10000 microseconds == 100000 (in 100 ns units). ; Remember the NT executive expects this value to be in ; 100 nanosecond units (not microseconds), so multiply ; accordingly when referencing KeTimeIncrement. ; ; the maxclock rates below are not the slowest that the hardware can support - ; they are the slowest we want NT to restore it whenever it is rolled back. ; MAXCLOCK_RATE_IN_MS EQU 10 ; specify in milliseconds ; ; We will support clock interrupt generation with a minimum of 300 ; nanosecond separations to as much as 10 milliseconds apart. ; MINCLOCK_DELTA_IN_100NS EQU 3 ; specify in 100-nanosecond units MAXCLOCK_DELTA_IN_100NS EQU 100000 ; specify in 100-nanosecond units ; ; "Convert" the interval to rollover count for 8254 Timer1 device. ; timer1 counts down a 16 bit value at a rate of 1.193M counts-per-sec. ; So that's what we'll use to get the finest granularity. Note that ; this is solely for the performance counter and has NOTHING to do ; with the system clock, which is driven directly from the APIC. ; We'd like for the interrupt rate to be even lower (ie: once per ; second rather than approximately 17 times per second, but the 8254 ; only gives us two bytes to feed in the interrupt counter. ; ROLLOVER_COUNT EQU 0ffffH ; must fit in the 8254's two bytes PERFORMANCE_FREQUENCY EQU 1193000 TIMER1_DATA_PORT0 EQU 40H ; Timer1, channel 0 data port TIMER1_CONTROL_PORT0 EQU 43H ; Timer1, channel 0 control port 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 _DATA SEGMENT DWORD PUBLIC 'DATA' ; ; default clock rate is 10 milliseconds ; CbusClockRateInMillis dd 10 ; in milliseconds Cbus1PerfCounterInit dd 0 Cbus1PerfCounterLow dd 0 Cbus1PerfCounterHigh dd 0 Cbus1LastReadPerfLow dd 0 Cbus1LastReadPerfHigh dd 0 Cbus18254Late dd 0 Cbus1CurrentTimeIncrement dd 0 if DBG Cbus18254LateCount dd 0 Cbus1Queries dd 0 endif _DATA ends INIT SEGMENT PARA PUBLIC 'CODE' ; Start 32 bit code ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING page ,132 subttl "Initialize Stall Execution Counter" ;++ ; ; VOID ; Cbus1InitializeStall ( ; IN CCHAR ProcessorNumber ; ) ; ; Routine Description: ; ; This routine initializes the per Microsecond counter for ; KeStallExecutionProcessor. ; ; All Corollary processors have their own local APIC and ; their own local timers. each processor independently ; calibrates himself here, thus supporting processors of ; varying speeds. ; ; Arguments: ; ; ProcessorNumber - Processor Number ; ; Return Value: ; ; None. ; ;-- cPublicProc _Cbus1InitializeStall ,1 cPublicFpo 1,2 push ebp ; save ebp mov ebp, esp sub esp, 8 ; save room for idtr pushfd ; save caller's eflag cli ; disable interrupts ; ; save the current CbusClockVector IDT entry, as we are ; going to temporarily repoint it at a private handler. ; sidt fword ptr [ebp-8] ; get IDTR (base & limit) mov ecx, [ebp-6] ; get IDTR base value mov eax, [_CbusClockVector] shl eax, 3 ; 8 bytes per IDT entry add ecx, eax ; now at the correct IDT RTC entry push dword ptr [ecx] ; (TOS) = original desc of IRQ 8 push dword ptr [ecx + 4] ; each descriptor has 8 bytes push ecx ; (TOS) -> &IDT[CbusClockVector] mov eax, offset FLAT:TimerExpired mov word ptr [ecx], ax ; Lower half of handler addr mov word ptr [ecx+2], KGDT_R0_CODE ; set up selector mov word ptr [ecx+4], D_INT032 ; 386 interrupt gate shr eax, 16 ; (ax)=higher half of handler addr mov word ptr [ecx+6], ax mov eax, [_CbusClockVector] ; we expect this vector to... or eax, CLKIN_ENABLE_ONESHOT ; use one-shot CLKIN to interrupt us ; get the base of APIC space, so we can then access ; the addr of hardware interrupt command register below mov ecx, [_CbusLocalApic] ; the count register appears to decrement approx 0x30 per ; asm instruction on a 486/33, just fyi. so load it up with ; a "real high value" so we don't get an interrupt whilst setting ; up the local time vector entry, etc, and then at the last ; possible moment, fill it in with the desired starting value. mov dword ptr INITIAL_COUNT_REG[ecx], CLKIN_ONE_SECOND ; initialize the local timer vector table entry with ; appropriate Vector, Timer Base, Timer Mode and Mask. mov TIMER_VECTOR_ENTRY[ecx], eax ; poke initial count reg to interrupt us in 16 microseconds mov dword ptr INITIAL_COUNT_REG[ecx], CLKIN_SIXTEEN_MS xor eax, eax ; initialize our register counter ALIGN 16 sti ; enable the interrupt jmp kise10 ALIGN 16 kise10: sub eax, 1 ; increment the loopcount jnz short kise10 if DBG stdCall _DbgBreakPoint ; Counter overflowed! endif jmp short kise10 TimerExpired: ; take the timer expiration interrupt here if DBG cmp eax, 0 jnz short kise30 stdCall _DbgBreakPoint ; Counter was never bumped! ; never return kise30: endif neg eax shr eax, CLKIN_SIXTEEN_MS_SHIFT ; convert to microseconds mov dword ptr PCR[PcStallScaleFactor], eax mov eax, [_CbusLocalApic] ; mask off local timer vector table entry now that we're done with it. mov dword ptr TIMER_VECTOR_ENTRY[eax], CLKIN_DISABLE ; ; Dismiss the interrupt AFTER disabling the timer entry so ; we don't get an extra interrupt later that was already pending. ; mov eax, _CbusClockVector ; mark interrupting vector CBUS_EOI eax, ecx ; destroy eax & ecx add esp, 12 ; unload flags, cs, ip pop ecx ; (ecx) -> &IDT[CbusClockVector] pop [ecx+4] ; restore higher half of RTC desc pop [ecx] ; restore lower half of RTC desc popfd ; restore caller's eflags mov esp, ebp pop ebp ; restore ebp stdRET _Cbus1InitializeStall stdENDP _Cbus1InitializeStall page ,132 subttl "Cbus1 Initialize Performance Counter" ;++ ; ; VOID ; Cbus1InitializePerf ( ; ) ; ; Routine Description: ; ; Initialize the 8254 to interrupt the minimum number of times ; per second on the boot processor only to support the performance counter. ; ; Arguments: ; ; None ; ; Return Value: ; ; None. ; ;-- cPublicProc _Cbus1InitializePerf ,0 ; ; Since ke\i386\allproc.c no longer boots all the available ; processors in the machine, the first processor to boot must ; initialize the 8254 and take the extra 8254 ticks. ; mov eax, PCR[PcHal.PcrNumber] cmp eax, 0 jne short @f pushfd ; save caller's eflag cli ; make sure interrupts are disabled ; ; Initialize the APIC so that 8254 interrupts go only to the boot ; processor - there is no need for all of them to get this interrupt ; when all it is doing is updating a global counter. ; ; stdCall _HalEnableSystemInterrupt, ; no need to enable - it's done by our caller ; ; Set clock rate at the 8254 ; 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 ecx, ROLLOVER_COUNT 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 Cbus1PerfCounterInit, 1 align 4 @@: stdRET _Cbus1InitializePerf stdENDP _Cbus1InitializePerf INIT ends _TEXT SEGMENT DWORD PUBLIC 'CODE' ; Start 32 bit code ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING page ,132 subttl "Cbus1 Initialize Clock" ;++ ; ; VOID ; Cbus1InitializeClock ( ; ) ; ; Routine Description: ; ; Initializes the clock and the kernel variable for the amount of ; time between timer ticks. The kernel needs this information by the end ; of phase 0. ; ; Arguments: ; ; None ; ; Return Value: ; ; None. ; ;-- cPublicProc _Cbus1InitializeClock,0 ; ; Fill in value with the time between clock ticks, ; remember the NT executive expects this value to be in ; 100 nanosecond units, not microseconds, so multiply accordingly. ; mov eax, CbusClockRateInMillis ; current rate in milliseconds mov ecx, 10000 ; converting back to 100 ns mul ecx ; eax == 100ns unit value ; ; The Cbus1CurrentTimeIncrement value is used by the clock ; interrupt routine to pass to the kernel, so set it now. ; mov Cbus1CurrentTimeIncrement, eax stdCall _Cbus1ProgramClock stdCall _KeSetTimeIncrement, stdRET _Cbus1InitializeClock stdENDP _Cbus1InitializeClock ;++ ; ; VOID ; Cbus1ProgramClock ( ; ) ; ; Routine Description: ; ; This routine initializes the system time clock for each calling ; processor to generate periodic interrupts at the specified rate using ; this processor's local APIC timer. Thus, each processor must call ; this routine to set or change his clock rate. During startup, each ; processor will call this routine to set his own clock rate. Later, ; when the system is running, the HalSetTimerResolution API is only ; supposed to change the clock rate for the boot processor - this is ; for multimedia apps that want timeouts serviced at a better than ; 5 millisecond granularity. ; ; Arguments: ; ; None ; ; Return Value: ; ; None. ; ;-- cPublicProc _Cbus1ProgramClock ,0 mov ecx, CbusClockRateInMillis ; new rate in milliseconds mov eax, APIC_TIMER_MILLISECOND ; counter per microsecond mul ecx ; eax == new APIC countdown val mov edx, [_CbusClockVector] ; we expect this vector to... or edx, CLKIN_ENABLE_PERIODIC ; use CLKIN to interrupt us mov ecx, [_CbusLocalApic] pushfd cli ; as a frame of reference, the count register decrements ; approximately 0x30 per asm instruction on a 486/33. ; initialize the local timer vector table entry with ; appropriate Vector, Timer Base, Timer Mode and Mask. mov TIMER_VECTOR_ENTRY[ecx], edx ; poke initial count reg to start the periodic clock timer interrupts. ; the IDT entry is valid & enabled on entry to this routine. mov dword ptr INITIAL_COUNT_REG[ecx], eax mov eax, CbusClockRateInMillis ; new rate in milliseconds mov ecx, 10000 ; converting back to 100 ns xor edx, edx mul ecx ; eax == 100ns unit value ; ; store it here so the clock ISR can tell it to the kernel ; mov Cbus1CurrentTimeIncrement, eax popfd stdRET _Cbus1ProgramClock stdENDP _Cbus1ProgramClock page ,132 subttl "Query Performance Counter" ;++ ; ; LARGE_INTEGER ; Cbus1QueryPerformanceCounter ( ; OUT PLARGE_INTEGER PerformanceFrequency OPTIONAL ; ) ; ; Routine Description: ; ; This routine returns the current 64-bit performance counter. ; The Performance Frequency is also returned if asked for. ; ; Also note that the performance counter returned by this routine ; is not necessarily the value exactly when this routine was entered. ; The value returned is actually the counter value at any point ; between the routine's entrance and exit times. ; ; This routine is not susceptible to the 2 problems that plague most ; multiprocessor HALs. Their problems are as follows: ; ; a) If the boot processor (or whoever updates the global performance ; counters) misses an interrupt, the counter rolls over undetected, ; and the performance counter returned can actually roll backwards! ; ; b) If you are on an additional processor and for some reason, ; the boot processor is holding off a pending clock interrupt for ; the entire time the additional processor is executing in ; KeQueryPerfomanceCounter. The boot processor hasn't necessarily ; lost a clock interrupt, it's just that he hasn't dropped IRQL ; enough to get it yet, and there isn't any good way for the ; additional processor to force him to. Since the boot processor ; is the only one maintaining the PerfCounter[High,Low], this can ; result in processors returning potentially non-uniform snapshots, ; which can even roll backwards! ; ; Both of these problems have been solved in the Corollary HAL by ; using separate clocks for the system timer and the performance counter. ; Ie: the APIC timer is used for the system timer and interrupts each ; processor every 15 milliseconds. The 8254 interrupts ONLY the boot ; processor once every second. Thus, case a) above cannot happen ; unless the boot processor disables interrupts for more than one second. ; If this happens, the entire system will be badly broken. case b) ; is also bypassed by using a global lock in the performance counter ; interrupt handler. Since the interrupt only occurs once per second ; and only on one processor, the global lock will not hinder performance. ; This allows us to take globally synchronized performance counter ; snapshots and also detect in software if case b) is happening, and ; handle it in software. Because the 8254 timer register is only 16 bits ; wide, the actual 8254 synchronization interrupt will occur approximately ; seventeen times per second instead of once. This is still at least 4 ; times better than the system timer (approximately 70 times per second). ; ; 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+4] ; User supplied Performance Frequency cPublicProc _Cbus1QueryPerformanceCounter ,1 ; ; First check to see if the performance counter has been ; initialized yet. Since the kernel debugger calls ; KeQueryPerformanceCounter to support the !timer command ; _VERY_ early on, we need to return something reasonable ; even though timer initialization hasn't occured yet. ; cmp Cbus1PerfCounterInit, 0 jne short @f ; ok, perf counter has been initialized ; ; Initialization hasn't occured yet, so just return zeros. ; mov eax, 0 mov edx, 0 jmp retfreq ; retfreq too far away for short jmp align 4 @@: if DBG inc dword ptr [Cbus1Queries] endif push ebx push esi ; ; all interrupts must be disabled prior to lock acquisition to ; prevent deadlock. ; pushfd cli lea esi, _Halp8254Lock align 4 Kqpc00: ACQUIRE_SPINLOCK esi, Kqpc01 ; ; get the global timer counters which are incremented ; one processor only. ; mov esi, Cbus1PerfCounterLow mov ebx, Cbus1PerfCounterHigh ; [ebx:esi] = 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. neg ecx ; PIT counts down to 0h add ecx, ROLLOVER_COUNT add esi, ecx adc ebx, 0 ; [ebx:esi] = Final result ; ; Check whether case b) could be happening right now - this is ; possible no matter which processor is currently calling us. ; cmp ebx, dword ptr [Cbus1LastReadPerfHigh] jl short caseb cmp esi, dword ptr [Cbus1LastReadPerfLow] jl short caseb jmp short notpending align 4 caseb: ; ; Detected case b) happening RIGHT NOW! ; Update the Cbus1 performance counter NOW, and ; set a flag so the interrupt handler will NOT. ; This case actually happens fairly frequently, ; ie: at least every few seconds on an idle ; uniprocessor, so this code is quite useful. ; add Cbus1PerfCounterLow, ROLLOVER_COUNT ; update performance counter adc Cbus1PerfCounterHigh, 0 inc dword ptr [Cbus18254Late] if DBG inc dword ptr [Cbus18254LateCount] endif align 4 notpending: ; ; save last performance counter read so future callers can compare ; mov Cbus1LastReadPerfLow, esi mov Cbus1LastReadPerfHigh, ebx mov edx, ebx ; save return value mov eax, esi ; save return value lea esi, _Halp8254Lock RELEASE_SPINLOCK esi popfd pop esi pop ebx ; ; if asked to, return the frequency in units/second ; align 4 retfreq: or dword ptr KqpcFrequency, 0 ; is it NULL? jz short @f ; if z, yes, go exit mov ecx, KqpcFrequency ; frequency pointer mov dword ptr [ecx], PERFORMANCE_FREQUENCY ; set frequency mov dword ptr [ecx+4], 0 ; currently < 4Gig! align 4 @@: stdRET _Cbus1QueryPerformanceCounter align 4 Kqpc01: SPIN_ON_SPINLOCK esi, stdENDP _Cbus1QueryPerformanceCounter page ,132 subttl "Cbus1 Perf Interrupt" ;++ ; ; VOID ; Cbus1PerfInterrupt( ; VOID ; ); ; ; Routine Description: ; ; This routine is the interrupt handler for the Cbus1 performance ; counter interrupt at a priority just below that of normal clocks. ; Its function is to update the global performance counter so that ; KeQueryPerformanceCounter can return meaningful values. ; ; This routine is executed only by one processor at a rate of seventeen ; times per second, as there is no need for all processors to update ; the same global counter. The only reason the rate is so high is ; because the interrupt interval must fit into a 16 bit register in the ; 8254. Otherwise, it would have been more like once per second. ; ; Since this routine is entered directly via an interrupt gate, interrupt ; protection via cli is not necessary. ; ; Arguments: ; ; None ; ; Return Value: ; ; None. ; ;-- ENTER_DR_ASSIST hipi_a, hipi_t cPublicProc _Cbus1PerfInterrupt ,0 ; ; Save machine state on trap frame ; ENTER_INTERRUPT hipi_a, hipi_t ; keep it simple, just issue the EOI right now. ; no changing of taskpri/irql is needed here. ; Thus, the EOI serves as the HalEndSystemInterrupt. mov eax, CBUS1_PERF_TASKPRI ; mark interrupting vector CBUS_EOI eax, ecx ; destroy eax & ecx ; ; (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 lea esi, _Halp8254Lock align 4 Kcpi00: ACQUIRE_SPINLOCK esi, Kcpi01 ; ; Update Cbus1 performance counter if a performance counter query ; hasn't done so already. ; cmp dword ptr [Cbus18254Late], 0 jne short @f add Cbus1PerfCounterLow, ROLLOVER_COUNT ; update performance counter adc Cbus1PerfCounterHigh, 0 jmp short noroll align 4 @@: dec dword ptr [Cbus18254Late] align 4 noroll: RELEASE_SPINLOCK esi ; ; Call this directly instead of through INTERRUPT_EXIT ; because the HalEndSystemInterrupt has already been done, ; and must only be done ONCE per interrupt. ; SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi align 4 Kcpi01: SPIN_ON_SPINLOCK esi, stdENDP _Cbus1PerfInterrupt ;++ ; ; ULONG ; Cbus1SetTimeIncrement ( ; IN ULONG DesiredIncrement ; ) ; ; /*++ ; ; Routine Description: ; ; This routine initializes the system time clock to generate an ; interrupt at every DesiredIncrement interval. ; ; Arguments: ; ; DesiredIncrement - desired interval between every timer tick in ; 100ns units. ; ; Return Value: ; ; The *REAL* time increment set - this can be different from what he ; requested due to hardware limitations - currently to keep the math ; simple, we limit the interval to between 1 and 32000 milliseconds, ; on millisecond boundaries (ie 1.3 milliseconds is rounded to 1 ; millisecond). ;-- cPublicProc _Cbus1SetTimeIncrement,1 mov eax, [esp+4] ; caller's desired setting xor edx, edx mov ecx, 10000 div ecx ; round to milliseconds cmp eax, MAXCLOCK_RATE_IN_MS ; desired > max? jc short @f mov eax, MAXCLOCK_RATE_IN_MS ; yes, use max @@: or eax, eax ; MS < min? jnz short @f inc eax ; yes, use min @@: mov CbusClockRateInMillis, eax ; set new rate in milliseconds ; ; inform this processor's local APIC hardware of the change. ; and then tell all the other processors to update theirs too... ; stdCall _Cbus1ProgramClock mov eax, Cbus1CurrentTimeIncrement stdRET _Cbus1SetTimeIncrement stdENDP _Cbus1SetTimeIncrement 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. ; ; Note spurious interrupts are always sent to the spurious interrupt IDT ; entry, and hence, no need to check for one here. ; ; See also Cbus1ClockInterruptPx() - it's used by the non-boot processors, ; since additional processors don't need to bump any global counters. ; ; Arguments: ; ; None ; Interrupt is disabled ; ; Return Value: ; ; must set up eax with the increment value, also leave ebp pointing at ; base of trap frame. ; Does not return, jumps directly to KeUpdateSystemTime, which returns ; ; Sets Irql = CLOCK2_LEVEL and dismisses the interrupt ; ;-- ENTER_DR_ASSIST Hck_a, Hck_t cPublicProc _Cbus1ClockInterrupt ,0 ; ; Save machine state in trap frame ; (esp) - base of trap frame ; ENTER_INTERRUPT Hck_a, Hck_t ; ; Dismiss interrupt and raise irq level to clock2 level ; push _CbusClockVector sub esp, 4 ; allocate space to save OldIrql stdCall _HalBeginSystemInterrupt, POKE_LEDS eax, edx ; ; (esp) = OldIrql ; (esp+4) = Vector ; (esp+8) = base of trap frame ; (ebp) = address of trap frame ; (eax) = time increment ; mov eax, Cbus1CurrentTimeIncrement jmp _KeUpdateSystemTime@0 stdENDP _Cbus1ClockInterrupt _TEXT ends end