// TITLE("Interval and Profile Clock Interrupts") //++ // // Copyright (c) 1990 Microsoft Corporation // Copyright (c) 1992 Digital Equipment Corporation // // Module Name: // // clock.s // // Abstract: // // This module implements the code necessary to field and process the // interval and profile clock interrupts. // // Author: // // David N. Cutler (davec) 27-Mar-1990 // Joe Notarangelo 06-Apr-1992 // // Environment: // // Kernel mode only. // // Revision History: // //-- #include "ksalpha.h" #if DBG // // KiDpcTimeout - This is the number of clock ticks that a single DPC can // consume. When a DPC crosses this threshold, a BreakPoint is issued // .globl KiDpcTimeout KiDpcTimeout: .long 110 // // KiDpcTimeoutMsg - This is the message that gets displayed if the DPC // has exceeded KiDpcTimeout // .globl KiDpcTimeoutMsg KiDpcTimeoutMsg: .ascii "\n*** DPC routine > 1 sec --- This is not a break in KeUpdateRunTime\n" // // KiDpcTimeoutMsgLength - This is the length of the timeout message, // including the trailing NULL // .globl KiDpcTimeoutMsgLength KiDpcTimeoutMsgLength: .long 69 #endif //++ // // VOID // KeUpdateSystemTime ( // IN ULONG TimeIncrement // ) // // Routine Description: // // This routine is entered as the result of an interrupt generated by the // interval timer. Its function is to update the system time and check to // determine if a timer has expired. // // N.B. This routine is executed on a single processor in a multiprocess // system. The remainder of the processors only execute the quantum end // and runtime update code. // // Arguments: // // Time Increment (a0) - Supplies the time increment in 100ns units. // // Return Value: // // None. // //-- LEAF_ENTRY(KeUpdateSystemTime) // // Update the interrupt time. // lda a2, KiTickOffset // get tick offset value ldl a3, 0(a2) // LDIP t8, SharedUserData // get shared user data address ldl t9, UsInterruptTime + 0(t8) // get low interrupt time ldl t10, UsInterruptTime + 4(t8) // get high interrupt time addl a0, t9, t9 // add time increment value cmpult t9, a0, t11 // compute carry addl t11, t10, t10 // add carry to high part stl t10, UsInterruptTime + 8(t8) // store high interrupt time stl t9, UsInterruptTime + 0(t8) // store low interrupt time #if !defined(NT_UP) mb // #endif stl t10, UsInterruptTime + 4(t8) // store high interrupt time zapnot t9, 15, t9 // set t9 = full 64 bits of sll t10, 32, t10 // interrupt time bis t9, t10, t9 // subq a3, a0, a3 // subtract time increment lda v0, KeTickCount // get tick count value ldq t6, 0(v0) // lda t0, KiTimerTableListHead // get base address of timer table stl a3, 0(a2) // store tick offset value bgt a3, 10f // if gt, tick not completed ldl a4, KeMaximumIncrement // get maximum increment value // // Update system time. // lda t1, KeTimeAdjustment // get time adjustment value ldl t1, 0(t1) // ldl t3, UsSystemTime + 0(t8) // get low system time ldl t4, UsSystemTime + 4(t8) // get high system time addl t1, t3, t3 // add time increment value cmpult t3, t1, t5 // compute carry addl t5, t4, t4 // add carry to high part stl t4, UsSystemTime + 8(t8) // store high system time stl t3, UsSystemTime + 0(t8) // store low system time #if !defined(NT_UP) mb // #endif stl t4, UsSystemTime + 4(t8) // store high system time // // Update the tick count. // addq t6, 1, t1 // increment tick count value stq t1, 0(v0) // store tick count value stl t1, UsTickCountLow(t8) // store low tick count value // // Compute next tick offset value. // addq a3, a4, a4 // add maximum increment to residue stl a4, 0(a2) // store tick offset value // // Check to determine if a timer has expired at the current hand value. // and t6, TIMER_TABLE_SIZE - 1, v0 // reduce to table table index #if defined(_AXP64_) sll v0, 4, t2 // compute timer table listhead address addq t2, t0, t2 // #else s8addl v0, t0, t2 // compute timer table listhead address #endif LDP t3, LsFlink(t2) // get address of first timer in list cmpeq t2, t3, t4 // compare fist with listhead address bne t4, 5f // if ne, no timer active // // Get the expiration time from the timer object. // // N.B. The offset to the timer list entry must be subtracted out of the // displacement calculation. // ldq t4,TiDueTime - TiTimerListEntry(t3) // get due time cmpule t4, t9, t5 // is expiration time <= system time bne t5, 20f // if ne, timer has expired // // Check to determine if a timer has expired at the next hand value. // 5: addq t6, 1, t6 // advance hand value to next entry 10: and t6, TIMER_TABLE_SIZE - 1, v0 // reduce to table table index #if defined(_AXP64_) sll v0, 4, t2 // compute timer table listhead address addq t2, t0, t2 // #else s8addl v0, t0, t2 // compute timer table listhead address #endif LDP t3, LsFlink(t2) // get address of first timer in list cmpeq t2, t3, t4 // compare fist with listhead address bne t4, 40f // if ne, no timer active // // Get the expiration time from the timer object. // ldq t4, TiDueTime - TiTimerListEntry(t3) // get due time cmpule t4, t9, t5 // is expiration time <= system time beq t5, 40f // if eq, timer has not expired // // Put timer expiration DPC in the system DPC list and initiate a dispatch // interrupt on the current processor. // 20: lda t2, KiTimerExpireDpc // get expiration DPC address DISABLE_INTERRUPTS // disable interrupts GET_PROCESSOR_CONTROL_BLOCK_BASE // get current prcb address lda t3, PbDpcListHead(v0) // get DPC listhead address lda t1, PbDpcLock(v0) // get address of spin lock #if !defined(NT_UP) 30: LDP_L t4, 0(t1) // get current lock value bis t1, zero, t5 // set ownership value bne t4, 50f // if ne, spin lock owned STP_C t5, 0(t1) // set spin lock owned beq t5, 50f // if eq, store conditional failed mb // synchronize memory access #endif LDP t4, DpLock(t2) // get DPC inserted state bne t4, 35f // if ne, DPC entry already inserted LDP t4, LsBlink(t3) // get address of last entry in list STP t1, DpLock(t2) // set DPC inserted state STP t6, DpSystemArgument1(t2) // set timer table hand value ADDP t2, DpDpcListEntry, t2 // compute address of DPC list entry STP t2, LsBlink(t3) // set address of new last entry STP t2, LsFlink(t4) // set next link in old last entry STP t3, LsFlink(t2) // set address of next entry STP t4, LsBlink(t2) // set address of previous entry ldl t5, PbDpcQueueDepth(v0) // get current DPC queue depth addl t5, 1, t7 // increment DPC queue depth stl t7, PbDpcQueueDepth(v0) // set updated DPC queue depth // // N.B. Since an interrupt must be active, simply set the software interrupt // request bit in the PRCB to request a dispatch interrupt directly from // the interrupt exception handler. // ldil t11, DISPATCH_INTERRUPT // get interrupt request level stl t11, PbSoftwareInterrupts(v0) // set interrupt request level 35: // #if !defined(NT_UP) mb // synchronize memory access STP zero, 0(t1) // set spin lock not owned #endif ENABLE_INTERRUPTS // enable interrupts // // Check to determine is a full tick has expired. // 40: ble a3, KeUpdateRunTime // if le, full tick expiration ret zero, (ra) // return // // Attempt to acquire the dpc lock failed. // #if !defined(NT_UP) 50: LDP t4, 0(t1) // get lock value beq t4, 30b // if eq, lock available br zero, 50b // retry #endif .end KeUpdateSystemTime //++ // // VOID // KeUpdateRunTime ( // VOID // ) // // Routine Description: // // This routine is entered as the result of an interrupt generated by the // interval timer. Its function is to update the runtime of the current // thread, update the runtime of the current thread's process, and decrement // the current thread's quantum. // // N.B. This routine is executed on all processors in a multiprocess system. // // Arguments: // // None // // Return Value: // // None. // //-- LEAF_ENTRY(KeUpdateRunTime) GET_CURRENT_THREAD // get current thread address bis v0, zero, t0 // save current thread address GET_PROCESSOR_CONTROL_BLOCK_BASE // get current prcb address bis v0, zero, t5 // save current prcb address LDP a0, PbInterruptTrapFrame(v0) // get trap frame address // // Update the current DPC rate. // // A running average of the DPC rate is used. The number of DPCs requested // in the previous tick is added to the current DPC rate and divided by two. // This becomes the new DPC rate. // ldl t1, PbDpcCount(t5) // get current DPC count ldl t6, PbLastDpcCount(t5) // get last DPC count subl t1, t6, t7 // compute difference ldl t2, PbDpcRequestRate(t5) // get old DPC request rate addl t7, t2, t3 // compute average srl t3, 1, t4 // stl t4, PbDpcRequestRate(t5) // store new DPC request rate stl t1, PbLastDpcCount(t5) // update last DPC count LDP t2, ThApcState + AsProcess(t0) // get current process address ldl t3, TrPsr(a0) // get saved processor status and t3, PSR_MODE_MASK, t6 // isolate previous mode bne t6, 30f // if ne, previous mode was user // // If a DPC is active, then increment the time spent executing DPC routines. // Otherwise, if the old IRQL is greater than DPC level, then increment the // time spent executing interrupt service routines. Otherwise, increment // the time spent in kernel mode for the current thread. // srl t3, PSR_IRQL, t6 // isolate previous IRQL ldl v0, PbDpcRoutineActive(t5) // get DPC active flag subl t6, DISPATCH_LEVEL, t6 // previous Irql - DPC level blt t6, 20f // if lt, charge against thread lda t8, PbInterruptTime(t5) // compute interrupt time address bgt t6, 10f // if gt, increment interrupt time lda t8, PbDpcTime(t5) // compute DPC time address beq v0, 20f // if eq, not executing DPC #if DBG // // On a checked build, increment the DebugDpcTime count and see if this // has exceeded the value of KiDpcTimeout. If it has, then we need to // print a message and issue a breakpoint (if possible) // ldl t9, PbDebugDpcTime(t5) // load current time in DPC addl t9, 1, t9 // another tick occured ldl t10, KiDpcTimeout // What is the timeout value? cmpule t9, t10, t11 // T11=1 if tick <= timeout bne t11, 5f // if ne, then time is okay lda a0, KiDpcTimeoutMsg // load the timeout message address ldl a1, KiDpcTimeoutMsgLength // load the timeout message length BREAK_DEBUG_PRINT // Print the message BREAK_DEBUG_STOP // Enter the debugger bis zero, zero, t9 // Clear the time in DPC 5: stl t9, PbDebugDpcTime(t5) // store current time in DPC #endif // // Update the time spent executing DPC or interrupt level // // t8 = address of time to increment // 10: ldl t11, 0(t8) // get processor time addl t11, 1, t11 // increment processor time stl t11, 0(t8) // update processor time lda t6, PbKernelTime(t5) // compute address of kernel time br zero, 45f // update kernel time // // Update the time spent in kernel mode for the current thread and the current // thread's process. // 20: ldl t11, ThKernelTime(t0) // get kernel time addl t11, 1, t11 // increment kernel time stl t11, ThKernelTime(t0) // store updated kernel time lda t2, PrKernelTime(t2) // compute process kernel time address lda t6, PbKernelTime(t5) // compute processor kernel time addr br zero, 40f // join comon code // // Update the time spend in user mode for the current thread and the current // thread's process. // 30: ldl t11, ThUserTime(t0) // get user time addl t11, 1, t11 // increment user time stl t11, ThUserTime(t0) // store updated user time lda t2, PrUserTime(t2) // compute process user time address lda t6, PbUserTime(t5) // compute processor user time address // // Update the time spent in kernel/user mode for the current thread's process. // 40: // #if !defined(NT_UP) ldl_l t11, 0(t2) // get process time addl t11, 1, t11 // increment process time stl_c t11, 0(t2) // store updated process time beq t11, 41f // if eq, store conditional failed mb // synchronize subsequent reads #else ldl t11,0(t2) // get process time addl t11, 1, t11 // increment process time stl t11,0(t2) // store updated process time #endif // // A DPC is not active. If there are DPCs in the DPC queue and a DPC // interrupt has not been requested, request a dispatch interrupt in // order to initiate the batch processing of the pending DPCs in the // DPC queue. // // N.B. Since an interrupt must be active, the software interrupt request // bit in the PRCB can be set to request a dispatch interrupt directly from // the interrupt exception handler. // // Pushing DPCs from the clock interrupt indicates that the current maximum // DPC queue depth is too high. If the DPC rate does not exceed the ideal // rate, decrement the maximum DPC queue depth and // reset the threshold to its original value. // ldl t1, PbDpcQueueDepth(t5) // get current queue depth beq t1, 45f // skip if queue is empty ldl t2, PbDpcInterruptRequested(t5) // get dpc interrupt request flag bne t2, 45f // skip if flag is set ldil a0, DISPATCH_INTERRUPT // set software interrupt request stl a0, PbSoftwareInterrupts(t5) // ldl t3, PbMaximumDpcQueueDepth(t5) // get current DPC queue depth subl t3, 1, t4 // decrement ldl t2, PbDpcRequestRate(t5) // get old DPC request rate ldl t1, KiIdealDpcRate // get ideal DPC rate cmpult t2, t1, t2 // compare current with ideal ldl t1, KiAdjustDpcThreshold // get system threshold default stl t1, PbAdjustDpcThreshold(t5) // reset processor threshold default beq t4, 50f // if queue depth==0, skip decrement beq t2, 50f // if rate not lt ideal rate, skip decrement stl t4, PbMaximumDpcQueueDepth(t5) // set current DPC queue depth br zero, 50f // rejoin common code // // There is no need to push a DPC from the clock interrupt. This indicates that // the current maximum DPC queue depth may be too low. Decrement the threshold // indicator, and if the new threshold is zero, and the current maximum queue // depth is less than the maximum, increment the maximum DPC queue // depth. // 45: ldl t1, PbAdjustDpcThreshold(t5) // get current threshold subl t1, 1, t2 // decrement threshold stl t2, PbAdjustDpcThreshold(t5) // update current threshold bne t2, 50f // if threshold nez, skip ldl t1, KiAdjustDpcThreshold // get system threshold default stl t1, PbAdjustDpcThreshold(t5) // reset processor threshold default ldl t3, PbMaximumDpcQueueDepth(t5) // get current DPC queue depth ldl t1, KiMaximumDpcQueueDepth // get maximum DPC queue depth cmpult t3, t1, t2 // compare beq t2, 50f // if current not lt maximum, skip addl t3, 1, t4 // increment queue depth stl t4, PbMaximumDpcQueueDepth(t5) // update current DPC queue depth // // Update the time spent in kernel/user mode for the current processor. // // t5 = pointer to processor time to increment // 50: ldl t11, 0(t6) // get processor time addl t11, 1, t11 // increment processor time stl t11, 0(t6) // store updated processor time // // If the current thread is not the idle thread, decrement its // quantum and check to determine if a quantum end has occurred. // LDP t6, PbIdleThread(t5) // get address of idle thread cmpeq t6, t0, t7 // check if idle thread running bne t7, 60f // if ne, idle thread running LoadByte(t7, ThQuantum(t0)) // get current thread quantum sll t7, 56, t9 // sra t9, 56, t7 // subl t7, CLOCK_QUANTUM_DECREMENT, t7 // decrement quantum StoreByte(t7, ThQuantum(t0)) // store thread quantum bgt t7, 60f // if gtz, quantum remaining // // Put processor specific quantum end DPC in the system DPC list and initiate // a dispatch interrupt on the current processor. // // N.B. Since an interrupt must be active, simply set the software interrupt // request bit in the PRCB to request a dispatch interrupt directly from // the interrupt exception handler. // ldil a0, DISPATCH_INTERRUPT // set interrupt request mask stl a0, PbSoftwareInterrupts(t5) // request software interrupt stl a0, PbQuantumEnd(t5) // set quantum end indicator 60: ret zero, (ra) // return // // Atomic increment of user/kernel time failed. // #if !defined(NT_UP) 41: br zero, 40b // retry atomic increment #endif .end KeUpdateRunTime //++ // // VOID // KeProfileInterrupt ( // VOID // ) // // VOID // KeProfileInterruptWithSource ( // IN KPROFILE_SOURCE ProfileSource // ) // // VOID // KiProfileInterrupt( // IN KPROFILE_SOURCE ProfileSource, // IN PKTRAP_FRAME TrapFrame // ) // // Routine Description: // // This routine is entered as the result of an interrupt generated by the // profile timer. Its function is to update the profile information for // the currently active profile objects. // // N.B. This routine is executed on all processors in a multiprocess system. // // Arguments: // // ProfileSource (a0) - Supplies the source of the profile interrupt // KeProfileInterrupt is an alternate entry for backwards // compatibility that sets the source to zero (ProfileTime) // // Return Value: // // None. // //-- .struct 0 PfS0: .space 8 // saved integer register s0 PfRa: .space 8 // return address .space 2 * 8 // profile frame length ProfileFrameLength: NESTED_ENTRY(KeProfileInterrupt, ProfileFrameLength, zero) bis zero, zero, a0 // set profile source to ProfileTime ALTERNATE_ENTRY(KeProfileInterruptWithSource) GET_PROCESSOR_CONTROL_BLOCK_BASE // get current prcb address LDP a1, PbInterruptTrapFrame(v0) // get trap frame address ALTERNATE_ENTRY(KiProfileInterrupt) lda sp, -ProfileFrameLength(sp) // allocate stack frame stq ra, PfRa(sp) // save return address #if !defined(NT_UP) stq s0, PfS0(sp) // save integer register s0 #endif PROLOGUE_END // // Acquire profile lock. // #if !defined(NT_UP) lda s0, KiProfileLock // get address of profile lock 10: LDP_L t0, 0(s0) // get current lock value bis s0, zero, t1 // set ownership value bne t0, 15f // if ne, spin lock owned STP_C t1, 0(s0) // set spin lock owned beq t1, 15f // if eq, store conditional failed mb // synchronize memory access #endif GET_CURRENT_THREAD // get current thread address LDP a2, ThApcState + AsProcess(v0) // get current process address ADDP a2, PrProfileListHead, a2 // compute profile listhead address bsr ra, KiProcessProfileList // process profile list lda a2, KiProfileListHead // get system profile listhead address bsr ra, KiProcessProfileList // process profile list #if !defined(NT_UP) mb // synchronize memory access STP zero, 0(s0) // set spin lock not owned ldq s0, PfS0(sp) // restore s0 #endif ldq ra, PfRa(sp) // restore return address lda sp, ProfileFrameLength(sp) // deallocate stack frame ret zero, (ra) // return // // Acquire profile lock failed. // #if !defined(NT_UP) 15: LDP t0, 0(s0) // get current lock value beq t0, 10b // if eq, lock available br zero, 15b // spin in cache until lock ready #endif .end KeProfileInterrupt //++ // // VOID // KiProcessProfileList ( // IN KPROFILE_SOURCE Source, // IN PKTRAP_FRAME TrapFrame, // IN PLIST_ENTRY ListHead // ) // // Routine Description: // // This routine is called to process a profile list. // // Arguments: // // Source (a1) - Supplies profile source to match // // TrapFrame (a0) - Supplies a pointer to a trap frame. // // ListHead (a2) - Supplies a pointer to a profile list. // // Return Value: // // None. // //-- LEAF_ENTRY(KiProcessProfileList) LDP a3, LsFlink(a2) // get address of next entry cmpeq a2, a3, t0 // end of list ? bne t0, 30f // if ne, end of list LDP t0, TrFir(a1) // get interrupt PC address GET_PROCESSOR_CONTROL_REGION_BASE // get current pcr address ldl t6, PcSetMember(v0) // get processor member // // Scan profile list and increment profile buckets as appropriate. // 10: LDP t1, PfRangeBase - PfProfileListEntry(a3) // get base of range LDP t2, PfRangeLimit - PfProfileListEntry(a3) // get limit of range ldl t4, PfSource - PfProfileListEntry(a3) // get source ldl t7, PfAffinity - PfProfileListEntry(a3) // get affinity zapnot t4, 3, t4 // source is a SHORT cmpeq t4, a0, t5 // check against profile source and t7, t6, v0 // check against processor beq t5, 20f // if ne, profile source doesn't match beq v0, 20f // if ne, processor doesn't match cmpult t0, t1, v0 // check against range base cmpult t0, t2, t3 // check against range limit bne v0, 20f // if ne, less than range base beq t3, 20f // if eq, not less than range limit SUBP t0, t1, t1 // compute offset in range ldl t2, PfBucketShift - PfProfileListEntry(a3) // get shift count LDP v0, PfBuffer - PfProfileListEntry(a3) // profile buffer address zap t1, 0xf0, t1 // force bucket offset to 32bit unit srl t1, t2, t3 // compute bucket offset bic t3, 0x3, t3 // clear low order offset bits ADDP v0, t3, t3 // compute bucket address ldl v0, 0(t3) // increment profile bucket addl v0, 1, v0 // stl v0, 0(t3) // 20: LDP a3, LsFlink(a3) // get address of next entry cmpeq a2, a3, t1 // end of list? beq t1, 10b // if eq[false], more entries 30: ret zero, (ra) // return .end KiProcessProfileList