;version 1 released 6-10-86
	title timdif - real time at different than clock rate
debug=-1 ;set to -1 to turn on debugging code
;written by Aaron Wohl
;       for Roger Dannerberg
;        at The Center for the Arts and technolagy
;           Carnegie Mellon University
;	    5000 Forbes Ave
;	    Pittsburgh, PA 15xxx
;
;change log:
; Jun 10 86 avw	  - release version 1
; Jun 04 86 avw	  - create from wohl's personal timer.asm
;
;this module provides a 32bit timer that counts at a rate different than
;the ~54ms time provided by the bios.  It is currently configured to
;count in 10ms tics.
;
;The idea is to do subtractions and keep a remainder instead of dividing
;the bios and timer chip time to save divide or multiply instructions.
;This is probably only a good idea if the rate you want is near the
;bios rate.
;
;things to do:
; change dodiv to be alot smarter and faster.  it currently loops
; subtracting divr from rem, instead subtract 3*divr and see if there
; is a borrow and go from there.  this will decrease the maximum divide time.

;************************************************************************
;*									*
;*	Module interface specifications					*
;*									*
;************************************************************************
; ibm_time - this routine is the reason this module exists, it returns the
;	   current time in 10ms tics.
;     note:ibm_time() may not be called with interupts disabled
;usage: unsigned long i,ibm_time(); i=ibm_time();
;
; cletime - stop our timer.  crash the system if timer not currently running.
;usage: cletime();
;
; settime - zero our timer and start counting.  crash the system if our timer
;	   is already running.
;	   if debbuging, also zero the value gettic() returns (see gettic)
;usage: settime();
;
; overall usage:
;  a) call settime() to start the timer
;  b) call ibm_time() as many times as you like to get the time.
;  c) call cletime() to stop timing
;  d) exit the program, or if you like, goto step a)

;************************************************************************
;*									*
;*	Module debug interface specifications				*
;*									*
;************************************************************************
; gettic - return high precision (8253 counter 0) reading from when
;	   the latest interupt happened.  latest meaning the most late, the
;	   interupt delayed for the longest time. usefull for debugging to see
;	   if someone is leaving the interups off for too long.
;     note:we must be called with debug on for gettic to function, always
;	   returns zero otherwise
; usage: int i,getic(); i=gettic();
;
; getbtim - return the 32bit bios time (it counts in ~54ms tics).
;	    usefull for debugging.  since the rest of this module runs
;	    off the same clock, this time (when converted to 10ms ticks)
;	    should never be more then 6 (10ms) ticks different.
;usage: unsigned long i,getbtim(); i=getbtim();
;

;************************************************************************
;*									*
;*	Timeing constants, linkages, libraries				*
;*									*
;************************************************************************

divr	equ 3054545		;divider for 11931.81667 to get 10ms
divlow	equ 09bd1h		;low word of divr
divhig	equ 0002eh		;high word of divr

	include macsym.lib	;control constructs
	include prcsym.lib	;procedure data declarators
	cmods_			;set linkage mode to c small model


;************************************************************************
;*									*
;*	data global to timdif						*
;*									*
;************************************************************************

;define memory location of timer interupt vector
ivec	segment at 0
	org	8h*4
timvec	label word
ivec	ends

	dseg timdat
	if debug
tictim	dw 0			;remeber when ticks happen
	endif

;remainder from previous divisions as a 32 bit number low byte to high byte
;also, used to remember part of current second already used
rem	label dword
rem0w	label word		;a few names for things since we
rem0b	db 0			; sometimes do byte operations and sometimes
rem1w	label word		; do word operations
rem1b	db 0
rem2w	label word
rem2b	db 0
rem3b	db 0

now	label dword			;current time in our units
now0w	dw 0				;for returning result to user
now2w	dw 0
	endds


	pseg prog
;************************************************************************
;*									*
;*	dodiv - divide bx(low word) ax(high word)			*
;*		by tivr							*
;*		result in cx, remainder in ax,bx
;*									*
;*									*
;************************************************************************

;do divide
dodiv	proc near
	mov cx,0		;initilize result of division
	do_
	 inc cx			;divide happend one more time
	 sub bx,divlow
	 sbb ax,divhig
	 jnc top_		;loop till we borrow
	od_
	dec cx			;correct for going once to many times
	add bx,divlow		;undo last subtract
	adc ax,divhig
	ret
dodiv	endp

;************************************************************************
;*									*
;*	Timer routine to piggy back on bios timer interupt		*
;*	data addressed by cs for interupt routine			*
;*									*
;************************************************************************

romwd	label word		;word address of rom tim
romtim	dd ?			;rom timer interupt address
timds	dw ?			;data segment for timer routines

;************************************************************************
;*									*
;*	Macro to read time into ax					*
;*	This is not a routine to save the subroutine call overhead	*
;*									*
;************************************************************************
timprt	equ 040h		;timer 0 port
timcmd	equ (timprt+3)		;timer control port
latcmd	equ 000h		;command to latch a time, timer 0
modcmd	equ 034h		;set to mode 2 instead of mode 3

;************************************************************************
;*									*
;*	Routine to return to user, when the latest timer tick happened	*
;*									*
;************************************************************************
	public gettic
	prcent gettic
	if debug
	  mov ax,tictim
	else
	  xor ax,ax
	endif
	prcexi gettic		;return the tic time to lattice c

;************************************************************************
;*									*
;*	Routine for returning the current bios time			*
;*	getbtim - return a 32bit time in our bios tics			*
;*	only used to check our accuracy					*
;*									*
;************************************************************************
	public getbtim
	prcent getbtim,,,<,ds>
	mov ax,040h		;address of rom data
	mov ds,ax
	cli
	mov bx,ds:[06ch]	;get high and low words of the time
	mov ax,ds:[06eh]
	sti
	prcexi getbtim		;return the time to lattice c

;read a latched time into cx cl=low, cx=high
REDTIM	macro
	mov al,latcmd		;latch a timer
	out timcmd,al		;send command to 8153
	in al,timprt		;get low byte of time
	mov cl,al		;save it
	in al,timprt		;get high byte of the time
	mov ch,al
	neg cx
	endm


;************************************************************************
;*									*
;*	Routine for returning the current time				*
;*	ibm_time - return a 32bit time in our units			*
;*									*
;************************************************************************
	public ibm_time
	prcent ibm_time

	do_
	 push es		;free up es to point to
	 mov ax,040h		;address of rom data
	 mov es,ax
	 cli
	 REDTIM			;read timer into ax
	 sti			;do next instruction, then ints on
	 mov bx,es:[06ch]	;(interupts off here, see sti def) get time
	 cli
	 cmp bx,es:[06ch]	;time change? set z flag if ok
	 pop es
	 jnz top_		;if time changed then counter is wrong
	od_
;note: interupts are off when exiting the loop! so that now doesn't
;      get bashed on by the timer interupt code while we are updating it
;note: the 8253 timer counts down, so ax has the negative of the part of
;      the tick
	mov bx,rem0w		;get low word of remainder
	mov ax,rem2w		;get high word
	add bh,cl
	adc al,ch
	adc ah,0

	call dodiv		;fixup now to be correct

	add cx,now0w		;get now plus part of tick
	mov ax,0
	adc ax,now2w
	sti			;done, allow interupts now
	mov bx,cx		;put low bits where lattice c wants them
	prcexi ibm_time         ;return bx,ax (current time)

;************************************************************************
;*									*
;*	Routine for stopping our timer					*
;*	cletime - stop timer remove patch into bios timer		*
;*									*
;************************************************************************

;clear the timer interupt routine
	public cletime
	prcent cletime,,,<,ds>
	assume ds:ivec		;address current vectors
	mov ax,ivec
	mov ds,ax
	
	cli
	mov ax,romwd
	mov timvec,ax		;restore original interupt vector
	mov ax,romwd[2]
	mov timvec[2],ax
	sti
	
	assume ds:dgroup
	prcexi cletime
;************************************************************************
;*									*
;*	Routine for starting our timer					*
;*	settime - zero now (time starts from zero), also patch into	*
;*		 bios timer interupt so we can call timint on each	*
;*		 timer tick						*
;*									*
;************************************************************************

	public settime
	prcent settime,,,<,ds>

	xor ax,ax		;get a zero
	if debug
	 mov tictim,ax		;clear latest tick time
	endif
	mov now0w,ax		;clear now
	mov now2w,ax
	mov rem0w,ax		;clear remainder
    	mov rem2w,ax

;change the timer to mode 2 (rate generator ) so we can see what part of the
;current tick we are in.  the rom sets it to mode 3 (square wave).  mode 3
;counts by two then twidiles the counter output line.  thus if read the
;timer you can't tell which half of the tick you are in.  mode 2 counts
;by one then gives a one clock pulse tick, which is just fine with
;the 8259 interupt controller chip.
	mov al,034h		;set timer mode
	cli			;lets not mess with the time interupt routine
	out timcmd,al
	xor al,al		;something to send to timer for time
	out timprt,al		;after setting mode need to send a time
	out timprt,al		;to restart the timer
	sti

	mov ax,ds		;save stack segment
	mov timds,ax

	assume ds:ivec		;address current vectors
	mov ax,ivec
	mov ds,ax
	
	mov ax,timvec		;save original interupt vector
	mov romwd,ax
	mov ax,timvec[2]
	mov romwd[2],ax

	mov ax,cs		;get new code segment
	mov bx,offset timint	;code routine
	cli
	mov timvec,bx		;set new timer offset
	mov timvec[2],ax	;set new timer segment
	sti

	assume ds:dgroup
	prcexi settime

;************************************************************************
;*									*
;*	More timer routine to piggy back on bios timer interupt		*
;*	This page has the code that gets called by the hardware		*
;*	Instead of the bios code					*
;*	It saves AX,BX,CX,DS then falls into the next page of code	*
;*	on the users stack.						*
;*									*
;************************************************************************

;new timer interupt
	public timint;for debugging only
timint	proc far
	assume ds:nothing,es:nothing,ss:nothing	;we know nothing, nothing
;note: it is safe to use 5 stack levels in a timer routine since the
;      bios interupt we replace does.  (save 4 regs, allowing one stack
;      level left for the call to dodiv)
	push ax
	push bx
	push cx
	push ds
	mov ax,timds		;set new ss and sp
	mov ds,ax		;set ds for user routine
	assume ds:dgroup,ss:dgroup

;************************************************************************
;*									*
;*	More timer routine to piggy back on bios timer interupt		*
;*	We take care of keeping our time up to date			*
;*									*
;*	Debugging code, remember what part of a tick interupts happen	*
;*									*
;************************************************************************
	if debug
	  REDTIM		;see when it is
	  cmp tictim,cx		;later than before?
	  ifskp_ nc		;remember time if yes
	   mov tictim,cx	;remember it
	  endif_
	endif

;************************************************************************
;*									*
;*	More timer routine to piggy back on bios timer interupt		*
;*	We take care of keeping our time up to date			*
;*									*
;************************************************************************

;get number to divide
	mov bx,rem0w		;get low word of remainder
	mov ax,rem2w		;get high word
	inc ah			;one more tick has passed

	call dodiv		;update now accordingly

;put back current remainder
	mov rem0w,bx
	mov rem2w,ax

;add result into now
	add now0w,cx		;move the time forward
	ifskp_ nc		;carry out of low word?
	 inc now2w		;had a carry, propagate it
	endif_

;************************************************************************
;*									*
;*	NOTE: execution runs into this page from above (save call/ret)	*
;*	More timer routine to piggy back on bios timer interupt		*
;*	This page restores AX,BX,CX,DS,SS,SP then jumps to the normal	*
;*	bios code.							*
;*									*
;************************************************************************

	assume ds:nothing,ss:nothing
	pop ds
	pop cx
	pop bx
	pop ax
	jmp romtim
timint	endp

	endps
	end
