;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;   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

	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
@@:
	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

	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

@@:
	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
