include mc.ash
.MODEL SMALL
;
;Time and interrupt functions for IBM hardware.
;set_clk() and reset_clk() enable/disable
;this code from the IBM INT 08 vector. 
;
;The built-in counters millisec, second
;and minute increment continually once 
;started.
;
; h= set_tick(&timer);
; int h;
; long *timer;
;
;    Returns the handle of an allocated
;timer tick; the counter will be continuously
;incremented until the handle is released.
;The timer has a resolution of 1 mS, and
;unknown accuracy. It is generally incremented
;in increments of the system clock. -1 is
;returned if no handles are available.
;
; clr_tick(h);
; int h;
;
; Release a handle.
;

.DATA

public _millisec,_seconds,_minutes
_millisec dd 0		;milliseconds
_seconds dw 0		;seconds
_minutes dw 0		;and so on

ticks dw 0		;work counter
MAXTICK	equ 16		;max. handles
pile dw MAXTICK dup (0)	;comma table of

.CODE
oldvec dd (?)		;saved orig. vector,
;
; h= set_tick(&timer);
;
func _set_tick
	mov	cx,MAXTICK
	mov	ax,0	;AX == handle
st1:	mov	bx,ax
	shl	bx,1	;word ptr
	cmp	word ptr pile[bx],0
	jne	st2	;find a zero,
	mov	dx,arg0
	mov	pile[bx],dx ;store pointer
	jmp	st3	;return handle
st2:	inc	ax	;next handle ...
	loop	st1
	mov	ax,-1	;no free handles
st3:
endf _set_tick
;
; clr_tick(h);
;
func _clr_tick
	mov	ax,-1
	mov	bx,arg0
	cmp	bx,MAXTICK
	jae	ct1
	shl	bx,1
	mov	word ptr pile[bx],0
	mov	ax,0
ct1:
endf _clr_tick

;
;Set clock: Insert our routine in series
;with the PCs INT 08h.
;
func _set_clk
	mov	al,8		;vector number,
	call	getvec
	mov	word ptr cs:oldvec,bx ;save it,
	mov	word ptr cs:oldvec + 2,es

	push	ds
	mov	al,8
	mov	dx,cs		;set for ticker
	mov	ds,dx
	mov	dx,offset ticker
	call	setvec
	pop	ds
endf _set_clk
;
;Clear the clock.
;
func _clr_clk
	pushf
	cli
	mov	ticks,0
;	mov	word ptr _millisec,0
;	mov	word ptr _millisec + 2,0
	mov	_seconds,0
	mov	_minutes,0
	popf
endf _clr_clk
;
;Remove our timer from the circuit.
;
func _reset_clk
	push	ds
	mov	al,8
	mov	dx,word ptr cs:oldvec
	mov	ds,word ptr cs:oldvec + 2
	call	setvec
	pop	ds
endf _reset_clk
page
;
;Ticker routine for the IBM PC, XT, etc etc.
;This generates two time signals: the seconds
;minutes thing, and a millisecond
;timer, which is just a handy thing to have
;around. (And is actually required)
;
ticker:	push	ds
	push	dx
	push	ax
	mov	ax,dgroup
	mov	ds,ax
;
;Do the milliseconds first. Its not really
;milliseconds, since the interrupt is actually
;every 55 mS, but its good enough as a general
;purpose timer.
;
	mov	ax,55
	call	tickfunc

	add	word ptr _millisec,55
	jnc	t1
	inc	word ptr _millisec + 2
;
;Now we have to coerce seconds amd minutes 
;out of the thing. No way we can count 
;seconds accurately at 55 mS/tick.
;
t1:	inc	ticks		;ticks/min
	cmp	ticks,1092	;full min. yet?
	jb	t3
	inc	_minutes	;yup, next min
	mov	ticks,0
;
;Now, calculate seconds as accurately as 
;possible from the minutes. We count in
;1092nds of a minute, so:
;
;1092 ticks/min= 18.2 ticks/second
;
;A close approximation is:
;
; (N / 16) - (N / 128)
;
t3:	mov	ax,ticks
	shr	ax,1
	shr	ax,1
	shr	ax,1
	shr	ax,1		; N / 16
	mov	dx,ax		;save it,
	shr	ax,1
	shr	ax,1
	shr	ax,1		; N / 128,
	sub	dx,ax
	mov	_seconds,dx
;
;Done. Dispatch to the original timer tick,
;that keeps MSDOS time, etc.
;
	pop	ax
	pop	dx
	pop	ds
	jmp	dword ptr cs:[oldvec] ;chain.
;
;Interrupt service routine for
;the timer ticks. AX contains the number
;of milliseconds since the last tick. This
;must preserve all registers.
;
tickfunc:
	push	si
	push	bx
	xor	bx,bx
tf1:	mov	si,pile[bx]
	or	si,si
	jz	tf2
	add	word ptr [si],ax
	jnc	tf2
	inc	word ptr [si + 2]
tf2:	add	bx,2
	cmp	bx,(MAXTICK * 2)
	jb	tf1
	pop	bx
	pop	si
	ret
;
;Set interrupt vector AL to DS:DX.
;
setvec:
	push	es
	push	bx
	mov	bx,0
	mov	es,bx
	mov	bl,al
	mov	bh,0
	shl	bx,1
	shl	bx,1
	pushf
	cli
	mov	es:[bx],dx
	mov	es:[bx + 2],ds
	popf
	pop	bx
	pop	es
	ret
;
;Get interupt vector AL, return in ES:BX.
;
getvec:	push	ds
	push	di
	mov	di,0
	mov	ds,di
	mov	ah,0
	mov	di,ax
	shl	di,1
	shl	di,1
	pushf
	cli
	mov	bx,ds:[di]
	mov	es,ds:[di + 2]
	popf
	pop	di
	pop	ds
	ret

	end
