Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

705 lines
20 KiB

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; LOCAL.ASM
;
; Copyright (c) Microsoft Corporation 1989, 1990. All rights reserved.
;
; This module contains the routines which interface with the
; timer counter hardware itself.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
?PLM=1 ; pascal call convention
?WIN=0 ; Windows prolog/epilog code
?DF=1
PMODE=1
.xlist
include cmacros.inc
include windows.inc
include mmddk.inc
include mmsystem.inc
include timer.inc
.list
externFP DriverCallback ; in MMSYSTEM.DLL
externFP StackEnter ; in MMSYSTEM.DLL
externFP StackLeave ; in MMSYSTEM.DLL
externFP tddEndMinPeriod ; timer.asm
externA __WinFlags ; Somewhere in Kernel ?
.286p
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Local data segment
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
externW Events
externD lpOLDISR
externB PS2_MCA
sBegin Data
; Current Time
public CurTime
CurTime dw 3 dup(0) ; 48 bit current tick count.
public wProgTime
wProgTime dw 0 ; Time currently programmed into timer chip
; ...NOTE 0=64k !!!
public wNextTime
wNextTime dw 0 ; Time next programmed into timer chip
public nInt8Count
nInt8Count dw 0 ; # times int8 handler re-entered
ifdef DEBUG
public RModeIntCount, PModeIntCount
RModeIntCount dd 0
PModeIntCount dd 0
endif
public IntCount
IntCount dw 0
fBIOSCall dw 0 ; Bios callback needed: TRUE or FALSE
fIntsOn dw 0 ; Interrupts have already been turned back on
ifdef RMODE_INT
dRModeTicks dd ? ; Temporary storage for Rmode ticks
endif
public dTickUpdate
dTickUpdate dd 0 ; Amount to actually update times with
sEnd Data
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Code segment
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
sBegin Code286
assumes cs,Code286
assumes ds,data
assumes es,nothing
CodeFixWinFlags dw __WinFlags
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Local (private) functions
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; @doc INTERNAL
;
; @asm tddRModeISR | Service routine for timer interrupts on IRQ 0.
; when in REAL mode
;
; @comm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ifdef RMODE_INT
assumes ds,nothing
assumes es,nothing
externD RModeOldISR
public RModeDataSegment
RModeDataSegment dw 0
public tddRmodeISR
tddRmodeISR proc far
push ds
push ax
push bx
mov ax,cs:[RModeDataSegment]
mov ds,ax
assumes ds,Data
inc [IntCount]
ifdef DEBUG
add [RModeIntCount].lo,1
adc [RModeIntCount].hi,0
endif
mov ax,[wNextTime] ; Next time programmed into timer chip
xchg ax,[wProgTime] ; Update current time if it was reset
xor bx,bx
dec ax ; convert 0 -> 64k
add ax,1
adc bx,bx
cmp [nInt8Count],1 ; Do not allow multiple re-entrancy
jge tddRmodeISRNormalExit
cld
push di
push cx
mov di,DataOFFSET Events ; DS:DI --> first event
mov cx,MAXEVENTS
tddRmodeISRLoop:
cmp [di].evID,0 ; is this event active?
jz tddRmodeISRNext
cmp [di].evDestroy,EVENT_DESTROYING
je tddRmodeISRNext
test [di].evFlags,TIME_BIOSEVENT
jz tddRmodeISRNext
mov dRModeTicks.lo,ax
mov dRModeTicks.hi,bx
add ax,[dTickUpdate.lo]
adc bx,[dTickUpdate.hi]
cmp [di].evTime.hi,bx
jg @f
jl tddRmodeISRChain
cmp [di].evTime.lo,ax
jle tddRmodeISRChain
@@:
mov ax,dRModeTicks.lo
mov bx,dRModeTicks.hi
jmp tddRmodeISRSearchExit
tddRmodeISRChain:
pop cx
pop di
pop bx
pop ax
push [RModeOldISR.hi]
push [RModeOldISR.lo]
push bp ; Restore DS from stack
mov bp,sp
mov ds,[bp+6] ; stack: [ds] [RModeOldISR.hi] [RModeOldISR.lo] [bp]
assumes ds,nothing
pop bp
retf 2
tddRmodeISRNext:
assumes ds,Data
add di,SizeEvent ; Increment to next event slot
loop tddRmodeISRLoop
tddRmodeISRSearchExit:
pop cx
pop di
tddRmodeISRNormalExit:
add CurTime[0],ax
adc CurTime[2],bx
adc CurTime[4],0
add [dTickUpdate.lo],ax ; Update total needed to be added
adc [dTickUpdate.hi],bx
ifndef NEC_98
cmp PS2_MCA,0 ; Check for a PS/2 Micro Channel
jz @f
in al,PS2_SysCtrlPortB ; Get current System Control Port status
or al,PS2_LatchBit ; Set latch clear bit
IO_Delay
out PS2_SysCtrlPortB,al ; Set new System Control Port status
@@:
endif ; NEC_98
mov al,SPECIFIC_EOI ; specific EOI for IRQ 0 interrupt line
out PICDATA,al ; send End-Of-Interrupt to PIC DATA port
pop bx
pop ax
pop ds
assumes ds,nothing
iret
tddRmodeISR endp
endif
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;@doc INTERNAL TIMER
;
;@asm tddISR |
; Service routine for timer interrupts on IRQ 0.
;
; The ISR runs through all the event slots available, looking for
; slots that are currently in used, and are not currently being
; destroyed. For each valid event found the callback time is updated.
; After all times have been updated, the table is run through again,
; calling all events that are due, and removing any due events that are
; oneshots. By updating all the events first, any new events that are
; created during a callback will not be accidentally called too early.
;
; Note that interrupts are not immediately restored, as this causes even
; more problems with slow machines. Also, the EOI is not sent to the
; PIC, as the BIOS interrupt handler does a non-specific EOI, and this
; would in turn EOI the last outstanding interrupt.
;
; First there is a special check for the presence of a Micro Channel,
; in which case, the System Control Port B must have bit 7 set in order
; to have the IRQ 0 latch released. This flag is aquired during Enable
; time with an int 15h service C0h, requesting machine information, which
; includes the presence of a Micro Channel.
;
; The ISR then updates the tick count based on the count that was in
; the timer's CE register. While retrieving that previously programmed
; time, it updates it to the new time that is contained in the timer's
; CR register, in case these to items are different. Note that the
; maximum CE value of 0 is converted to 65536 through the decrement and
; adding with carry.
;
; Next, the ISR must determine if it is re-entering itself. If this is
; so, callbacks are not performed, and only a "missed ticks" count is
; updated, indicating how many additional ticks should be subtracted
; from each event due time. This allows the ISR to finish immediately
; if a timer interrupt is currently being serviced. This is important
; for both speed in general, and for slow machines that might generate
; mouse events during timer events. Note that only 6 bytes have been
; pushed onto the stack for this case, and that everything but DS must
; be removed before jumping to the exit label. In this case, the
; function can safely EOI the PIC, as the BIOS call will not be
; performed, then the function will just return.
;
; In the normal case, the ISR is not being re-entered, and timer event
; due times are updated, and callbacks are made. In this case, the
; number of "missed ticks" is added to the CE tick count, bringing the
; total up to the number of ticks passed since the last time the event
; times were updated. This global counter is then zeroed for the next
; time re-entrancy occurs. Note that interrupts are still turned off
; at this point, and there is no need to fear bad things happening.
;
; When checking for a valid event ID, the Destroy flag must be checked
; in case the interrupt occured during a kill timer function call after
; the Destroy flag was grabbed the second time, but before the actual ID
; could be reset.
;
; When a valid ID is found, its due time is updated with the CE value,
; plus the amount of ticks that were missed because of re-entrancy, if
; any.
;
; After updating times, the event list is checked again, this time to
; perform any of the callbacks that are due. To make things easy, a
; global flag is used to determine if interrupts have been turned back
; on, and thus stacks have been switched.
;
; If a valid event is found that is also due, meaning that the callback
; time is <= 0, the fIntsOn flag is checked to determine if the stack
; has already been switched and interrupts are already on. If not, then
; just that occurs. The <f>tddEvent<d> function is then called to
; service the event.
;
; After all events have been called, interrupts are turned back off if
; needed, and the original stack restored. If no callback actually
; occurred, then the stack is never switched. The function then either
; exits as a normal ISR would, or it chains to the BIOS ISR. This is
; done if the BIOS event was up for being called, and the fBIOSCall flag
; was set because of that. Since the flag cannot be set when this ISR
; is being pre-entered, as callbacks are not performed, there is no need
; to do a test and set proceedure on the fBIOSCall flag, just a simple
; compare will do. Note though that the nInt8Count re-entrancy count is
; not decremented until after interrupts are turned off.
;
; Interrupts are also cleared to ensure that the BIOS ISR is not
; re-entered itself, since there is no re-entrancy control after this
; function chains to BIOS. Notice that DS was the first register pushed
; onto the stack, and therefore the last item to get rid of, which is
; done with the "retf 2". DS itself is restored from stack before
; chaining so that lpOLDISR (BIOS) can be accessed and pushed onto stack
; as the return address.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assumes ds,nothing
assumes es,nothing
public tddISR
tddISR proc far
push ds ; This is pushed first for the case of BIOS
;----------------------------------------------------------------------------
;If we are on a 386 save all registers.
;----------------------------------------------------------------------------
test cs:[CodeFixWinFlags],WF_WIN286
jnz @F
.386
pushad
push fs
push gs
.286p
@@:
push ax
push bx
mov ax,DGROUP ; set up local DS
mov ds,ax
assumes ds,Data
ifndef NEC_98
cmp PS2_MCA,0 ; Check for a PS/2 Micro Channel
jz @f
in al,PS2_SysCtrlPortB ; Get current System Control Port status
or al,PS2_LatchBit ; Set latch clear bit
IO_Delay
out PS2_SysCtrlPortB,al ; Set new System Control Port status
@@:
endif ; NEC_98
inc [IntCount] ; Ever-increasing Int counter
inc [nInt8Count] ; Number of times int 8 re-entered
mov ax,[wNextTime] ; Next time programmed into timer chip
xchg ax,[wProgTime] ; Update current time if it was reset
xor bx,bx
dec ax ; convert 0 -> 64k
add ax,1 ; Force carry flag
adc bx,bx ; Set bx:ax == current tick count
add CurTime[0],ax ; Add tick count to total ticks
adc CurTime[2],bx
adc CurTime[4],0
ifdef DEBUG
; cmp [nInt8Count],1 ; Re-entrancy counter
; je @f
; add [RModeIntCount].lo,1
; adc [RModeIntCount].hi,0
;@@:
add [PModeIntCount].lo,1 ; For debug Pmode count message
adc [PModeIntCount].hi,0
endif
cmp [nInt8Count],1 ; Do not allow multiple re-entrancy
je tddISRCheckCallbacks
add [dTickUpdate.lo],ax ; Update total needed to be added
adc [dTickUpdate.hi],bx
pop bx
jmp tddISREOIExit ; EOI before exiting
tddISRCheckCallbacks:
add ax,[dTickUpdate.lo] ; Add any extra ticks from re-entrancy
adc bx,[dTickUpdate.hi]
push cx
xor cx,cx
mov [dTickUpdate.lo],cx ; Reset tick re-entrant counter
mov [dTickUpdate.hi],cx
cld ; never assume the value of this in an ISR!
push di
mov di,DataOFFSET Events ; DS:DI --> first event
mov cx,MAXEVENTS
tddISRUpdateTimeLoop:
cmp [di].evID,0 ; is this event active?
jz tddISRUpdateTimeNext
sub [di].evTime.lo,ax ; Subtract the amount of ticks gone by
sbb [di].evTime.hi,bx
tddISRUpdateTimeNext:
add di,SizeEvent ; Increment to next event slot
loop tddISRUpdateTimeLoop
mov fIntsOn,0 ; Initialize interrupts set flag
mov di,DataOFFSET Events ; DS:DI --> first event
mov cx,MAXEVENTS
tddISRCallLoop:
cmp [di].evID,0 ; is this event active?
jz tddISRNextEvent
cmp [di].evDestroy,EVENT_DESTROYING
je tddISRNextEvent
cmp [di].evTime.hi,0 ; Is it time to call the event?
jg tddISRNextEvent ; evTime <= 0
jl tddISREvent
cmp [di].evTime.lo,0
jg tddISRNextEvent
tddISREvent:
test [di].evFlags,TIME_BIOSEVENT
jnz tddISRCallEvent ; No need to switch, as no call will be made.
cmp fIntsOn,0 ; Have interrupts been turned on already?
jnz tddISRCallEvent
inc fIntsOn ; fIntsOn == TRUE
cCall StackEnter ; Switch to a new stack
sti ; Can be re-entered now with new stack
; A timer callback needs to be called, but first before calling it,
; we need to check to determine if the original timer interrupt function
; is to be called during this interrupt. The reason is that a timer
; callback could take a long time, and the PIC should be EOI'ed as soon
; as possible.
; It is not possible to just do a specific EOI, as the BIOS timer
; interrupt performs a non-specific EOI, which would turn back on some
; other random interrupt. So if the the BIOS needs to be called, it
; is done now, else the EOI is performed now. This assumes that the
; BIOS callback is the first item in the list of callbacks.
; If the BIOS callback occurs now, then the fBIOSCall flag is reset,
; as there is no need to chain to it at the end of this interrupt. So
; if no other callbacks are to be performed, the BIOS interrupt is
; chained to, else it is just called before the first timer callback
; is performed.
cmp [fBIOSCall],0 ; Does BIOS need to be called?
je tddISREOI
mov [fBIOSCall],0 ; No need to call BIOS again at the end
pushf ; Simulate an interrupt call
call lpOLDISR ; Call original timer interrupt
jmp tddISRCallEvent ; Do actual timer callback
; No BIOS interrupt call is to be performed, so do EOI.
tddISREOI:
mov al,SPECIFIC_EOI ; specific EOI for IRQ 0 interrupt line
out PICDATA,al ; send End-Of-Interrupt to PIC DATA port
tddISRCallEvent:
call tddEvent ; handle the event
tddISRNextEvent:
add di,SizeEvent ; Increment to next event slot
loop tddISRCallLoop
cmp fIntsOn,0 ; Where interrupts turned back on?
jz @f
cli ; Interrupts were turned on, so remove them
cCall StackLeave ; Switch back to old stack
@@:
pop di ; Restore everything except DS
pop cx
pop bx
cmp [fBIOSCall],0 ; Does BIOS need to be called?
je tddISREOIExit
pop ax
mov [fBIOSCall],0
;----------------------------------------------------------------------------
;If we are on a 386 restore all registers.
;----------------------------------------------------------------------------
test cs:[CodeFixWinFlags],WF_WIN286
jnz @F
.386
pop gs
pop fs
popad
.286p
@@:
push [lpOLDISR.hi] ; Push return address
push [lpOLDISR.lo]
dec [nInt8Count] ; exiting, decrement entry count
push bp ; Restore DS from stack
mov bp,sp
mov ds,[bp+6] ; stack: [ds] [lpOLDISR.hi] [lpOLDISR.lo] [bp]
assumes ds,nothing
pop bp
retf 2 ; Chain to BIOS ISR, removing DS from stack
tddISREOIExit:
mov al,SPECIFIC_EOI ; specific EOI for IRQ 0 interrupt line
out PICDATA,al ; send End-Of-Interrupt to PIC DATA port
pop ax
assumes ds,Data
dec [nInt8Count] ; exiting, decrement entry count
;----------------------------------------------------------------------------
;If we are on a 386 restore all registers.
;----------------------------------------------------------------------------
test cs:[CodeFixWinFlags],WF_WIN286
jnz @F
.386
pop gs
pop fs
popad
.286p
@@:
pop ds
assumes ds,nothing
iret
tddISR endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;@doc INTERNAL TIMER
;
;@asm tddEvent |
; Handle an event when it is due.
;
; For a valid event, the ID is saved in case the slot needs to be zeroed
; and the type of event is checked. If this is a oneshot event
; timer, the entry is freed. Note that at this point, as in the kill
; event function, the Destroy flag must be checked to determine if the
; slot is currently being checked. If so, the EVENT_DESTROYED flag must
; be set instead of resetting the flag so that the function that was
; interrupted can determine that the entry was killed while being
; checked.
;
; After saving the event handle, the function checks to see if the event
; is a One Shot, in which case it is destroyed, and the event's
; resolution is removed from resolution the table.
;
; If on the other hand the event is a periodic one, the next calling
; time is updated with the delay period. Note that if the event is far
; behind, or the last minimum resolution was very large, many delay
; periods are added to the next call time.
;
; If this is a BIOS event, then the fBIOSCall flag is set so that the
; ISR chains to the old BIOS ISR instead of returning normally. If this
; is a normal event, the parameters are pushed, and the driver callback
; function is called using the DCB_FUNCTION flag.
;
; After returning from the callback, the return value from
; <f>DriverCallback<d> is checked to determine if the callback succeeded.
; If it did not, then the timer event needs to be removed. The timer
; event however may have been a oneshot, in which case it was already
; been removed before the call was made, and the EVENT_DESTROYED flag
; may have been set, so it is just left alone. If the event is still
; present however, it is destroyed after doing the checking to see if
; this interrupt came while the event was being destroyed. Note that
; there is no check to see if the event IDs are the same before destroying
; the event. This is because if the callback failed, then the timer
; structure cannot have changed, and no check is needed.
;
;@parm DS:DI |
; Points to the event slot.
;
;@comm Uses AX,BX.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assumes es,nothing
assumes ds,Data
cProc tddEvent, <NEAR, PUBLIC>, <>
cBegin
push dx
mov dx,[di].evID
test [di].evFlags,TIME_PERIODIC
jnz tddEventPeriodic
tddEventKillOneShot:
xor ax,ax
mov [di].evID,ax ; Invalidate slot
cmp [di].evDestroy,EVENT_CHECKING ; Did this interrupt a Kill?
jne @f
mov al,EVENT_DESTROYED ; Let the interrupted Kill know
@@:
mov [di].evDestroy,al
mov [di].evCreate,ah ; pEvent->evCreate = FALSE
push dx
push cx
cCall tddEndMinPeriod,<[di].evResolution>
pop cx
pop dx
jmp tddEventCallback
tddEventPeriodic:
mov ax,[di].evDelay.lo
mov bx,[di].evDelay.hi
@@:
add [di].evTime.lo,ax
adc [di].evTime.hi,bx
jl @b
tddEventCallback:
test [di].evFlags,TIME_BIOSEVENT
jz tddEventDriverCallback
inc [fBIOSCall]
jmp tddEventExit
tddEventDriverCallback:
push cx
push es
;
; call DriverCallback() in MMSYSTEM
;
push [di].evCallback.hi ; execute callback function
push [di].evCallback.lo
push DCB_FUNCTION or DCB_NOSWITCH; callback flags
push dx ; idTimer
xor dx,dx
push dx ; msg = 0
push [di].evUser.hi ; dwUser
push [di].evUser.lo
push dx ; dw1 = 0
push dx
push dx ; dw2 = 0
push dx
call DriverCallback ; execute callback function
pop es
or ax,ax ; Check for a successful return
jnz tddEventSucceed ; If callback succeeded, just continue
cmp [di].evID,ax ; If the timer was already destroyed,
jz tddEventSucceed ; just leave
mov [di].evID,ax ; Else destroy the event
cmp [di].evDestroy,EVENT_CHECKING ; Did this interrupt a Kill?
jne @f
mov al,EVENT_DESTROYED ; Let the interrupted Kill know
@@:
mov [di].evDestroy,al
mov [di].evCreate,ah ; pEvent->evCreate = FALSE
cCall tddEndMinPeriod,<[di].evResolution>
tddEventSucceed:
pop cx
tddEventExit:
pop dx
cEnd
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; @doc INTERNAL
;
; @asm GetCounterElement | Low level routine which loads the tick count
; from the timer counter device, and returns the number of ticks that
; have already passed.
;
; @rdesc Returns the tick count in AX.
;
; @comm All registers preserved.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
public GetCounterElement
GetCounterElement proc near
; Get rid of any latched count if this is called during interrupt time
cmp [nInt8Count],1
jb @f
in al,TMR_CNTR_0
IO_Delay
in al,TMR_CNTR_0
@@:
; read counter first time
xor ax,ax ; LATCH counter 0 command
out TMR_CTRL_REG,al ; send command
in al,TMR_CNTR_0 ; read low byte
mov ah,al
in al,TMR_CNTR_0 ; read high byte
xchg al,ah
sub ax,wProgTime ; Convert to number of ticks already past
neg ax
ret
GetCounterElement endp
sEnd
end