;MP/M Utility routines in assembly language to provide operations
; which are difficult or unavailable from Pascal MT+
;
;Pascal MT+ definitions for procedures/functions in this package:
;
; external function create_process(pd_p: ^process_descriptor): boolean;
; external procedure delay(ticks: integer);
; external procedure dispatch;
; external procedure fix_stack(taskp: ^integer; 
;			       udap: ^user_data_area;
;			       size: integer);
; external procedure init_mpm_util(size: integer);
; external function queue_make(qd_p: ^queue_descriptor): boolean;
; external function queue_oper(op: byte; 
;			       qp_p: ^queue_parm_block): boolean;
; external function set_priority(priority: byte): boolean;
; external procedure terminate;
; external procedure abort(pd_p: ^process_descriptor);
; external function x_time: longint;
; external procedure r_con_raw(str: ^string);
; external procedure get_tod(tod: ^tod_record);
; external function who_am_i: ^process_descriptor;


	name	mpm_util

	assume	cs: code, ds: data

spoff		equ	34h		;offset of sp in UDA
csoff		equ	50h		;offset of cs in UDA
dsoff		equ	52h		;offset of ds in UDA
esoff		equ	54h		;offset of es in UDA
ssoff		equ	56h		;offset of ss in UDA

mpm_call	equ	224		;mpm call interrupt value

mpm_read_raw	equ	  3		;mpm read raw console function
mpm_bios	equ	 50		;mpm direct BIOS call function
mpm_make_q	equ	134		;mpm make queue function
mpm_min_q	equ	135		;smallest mpm queue function code
mpm_max_q	equ	140		;largest mpm queue function code
mpm_delay	equ	141		;mpm delay function
mpm_dispatch	equ	142		;mpm dispatch function
mpm_terminate	equ	143		;mpm terminate function
mpm_create_p	equ	144		;mpm create process function
mpm_set_prior	equ	145		;mpm set priority function
mpm_get_sysdat	equ	154		;mpm get system data area addr fcn
mpm_get_tod	equ	155		;mpm get time of day
mpm_get_pd	equ	156		;mpm get process desc address
mpm_abort	equ	157		;mpm abort specified process fcn


data	segment	public

stack_offset	dw	?

data	ends


code	segment	public

	public	delay, dispatch, terminate, abort, xtime
	public	createprocess, setpriority
	public	queuemake, queueoper
	public	fixstack, initmpmutil
;	public	whoami			;temp fix -- causes problem
	public	rconraw, gettod


;************************************************************************
;	PROCEDURE INIT_MPM_UTIL(SIZE: INTEGER)				*
;Inputs:								*
;	Size of stack area to reserve for main program			*
;Outputs:								*
;	None								*
;Processing:								*
;	Internal initialization for FIX_STACK.  INIT_FIX_STACK must be	*
;	called exactly once before any calls to FIX_STACK.  It notes 	*
;	amount of stack space to reserve for the main program.		*
;	Also sets up location of XIOS entry for X_TIME.			*
;************************************************************************


initmpmutil  proc  near

	pop	dx		;return address
	pop	ax		;size
	mov	stack_offset,ax	;initialize offset with size

	push	dx		;restore return address
	push	es		;save extra segment

	mov	cl,mpm_get_sysdat	;get system data area address
	int	mpm_call		;... in es:bx
	mov	cx,es: word ptr 28h[bx]	;XIOS entry offset
	mov	dx,es: word ptr 2Ah[bx]	;...and segment
	mov	cs:xtoff,cx		;put into CALLF instruction
	mov	cs:xtseg,dx

	pop	es			;restore extra segment
	ret

initmpmutil  endp


;************************************************************************
; 	PROCEDURE FIX_STACK(TASKP: ^INTEGER;				*
;			    UDAP: ^USER_DATA_AREA;			*
;			    SIZE: INTEGER);				*
;Inputs:								*
;	Starting address of task					*
;	Address of User Data Area					*
;	Size of stack area to reserve for this task			*
;Outputs:								*
;	None								*
;Processing:								*
;	Puts segment registers in UDA table.  Computes stack area to	*
;	use for this task, puts stack location in UDA table,		*
;	puts task address at stack location.  Assumes small model.	*
;	Init must have been called first to set up 'offset' variable.	*
;************************************************************************

fixstack  proc  near

	pop	cx			;return address
	pop	ax			;size value
	pop	bx			;offset of UDA
	pop	dx			;segment of UDA (don't care)
	push	ax			;save size

	mov	word ptr csoff[bx],cs	;store segment registers in UDA
	mov	word ptr dsoff[bx],ds
	mov	word ptr esoff[bx],es
	mov	word ptr ssoff[bx],ss

	mov	ax,sp			;current stack pointer
	sub	ax,stack_offset		;subtract offset
	mov	word ptr spoff[bx],ax	;put new stack pointer in UDA
	pop	dx			;size
	add	stack_offset,dx		;new offset value for next time

	pop	dx			;offset of task
	pop	bx			;segment of task (don't care)
	push	cx			;return address back on stack

	push	bp
	mov	bp,ax			;get new stack offset
	mov	word ptr [bp],dx	;put task address at that location
	pop	bp

	ret				;all done

fixstack  endp


;************************************************************************
;	PROCEDURE DELAY(TICKS: INTEGER);				*
;Inputs:								*
;	Number of clock ticks (1/60 second) to delay			*
;Outputs:								*
;	None								*
;Processing:								*
;	Calls MP/M delay function for given number of ticks.		*
;	If TICKS is 0 or negative (>32767) there is no delay.		*
;************************************************************************

delay	proc	near

	pop	bx		;return address
	pop	dx		;number of ticks
	push	bx		;save return address
	cmp	dx,0
	jle	delend		;no delay if zero or negative
	mov	cl,mpm_delay
	int	mpm_call	;call mpm
delend:	ret

delay	endp


;************************************************************************
;	PROCEDURE DISPATCH;						*
;Inputs:								*
;	None								*
;Outputs:								*
;	None								*
;Processing:								*
;	Calls MP/M dispatch to allow scheduling of another task.	*
;************************************************************************

dispatch  proc	near

	mov	cl,mpm_dispatch
	int	mpm_call
	ret

dispatch  endp


;************************************************************************
;	PROCEDURE TERMINATE;						*
;Inputs:								*
;	None								*
;Outputs:								*
;	None								*
;Processing:								*
;	Calls MP/M function to terminate this task.			*
;************************************************************************

terminate  proc  near

	mov	cl,mpm_terminate
	int	mpm_call
	ret

terminate  endp


;************************************************************************
;	PROCEDURE ABORT(PD_P: ^PROCESS_DESCRIPTOR);			*
;Inputs:								*
;	Address of Process Descriptor table				*
;Outputs:								*
;	None								*
;Processing:								*
;	Calls MP/M function to abort the specified task.		*
;************************************************************************

abort	proc  near

	pop	ax		;return address
	pop	dx		;PD offset
	pop	bx		;PD segment
	push	ax		;save return address
	push	ds		;save current data segment
	mov	ds,bx		;ds of the PD
	add	dx,48		;offset of the APB in the PD
	mov	cl,mpm_abort
	int	mpm_call	;call mpm to abort
	pop	ds		;restore ds
	ret			;exit

abort	endp


;************************************************************************
;	FUNCTION X_TIME: LONGINT;					*
;Inputs:								*
;	None								*
;Outputs:								*
;	Time in ticks since system start				*
;Processing:								*
;	XIOS is called directly to get at non-standard XIOS		*
;	call #4, implemented as misc extra calls in G&G XIOS. 		*
;	32-bit integer counter returned in bx, ax (Pascal longint)	*
;	INIT_MPM_UTIL must have been called first to set up XIOS entry.	*
;************************************************************************

xtime	proc  near

	pushf				;save interrupt state
	cli				;disable interrupts
	mov	al,4			;XIOS call number
	mov	ch,10			;extended call number
	db	9Ah			;CALLF to XIOS
xtoff	dw	0			;time returned in dx,cx
xtseg	dw	0
	popf				;restore interrupt state

	mov	ax,cx			;time lsw
	mov	bx,dx			;time msw
	ret				;return

xtime	endp


;************************************************************************
;	FUNCTION CREATE_PROCESS(PD_P: ^PROCESS_DESC): BOOLEAN;		*
;Inputs:								*
;	Address of Process Descriptor table				*
;Outputs:								*
;	True if success, False if failure				*
;Processing:								*
;	Creates subtask for given Process Descriptor table.		*
;************************************************************************

createprocess  proc  near

	pop	ax		;return address
	pop	dx		;offset of PD
	pop	bx		;segment of PD
	push	ax		;save return address
	push	ds		;save current data segment

	mov	ds,bx		;get PD segment
	mov	cl,mpm_create_p
	int	mpm_call	;call MP/M
	pop	ds		;restore ds
	and	ax,ax		;ax=0 if success

	jz	cpok		;jump if success
	xor	ax,ax		;return 'False'
	ret

cpok:	inc	ax		;return 'True'
	ret

createprocess  endp


;************************************************************************
;	FUNCTION SET_PRIORITY(PRIOR: BYTE): BOOLEAN;			*
;Inputs:								*
;	Priority value to use						*
;Outputs:								*
;	True if success, False if failure				*
;Processing:								*
;	Sets current task priority to given value			*
;************************************************************************

setpriority  proc  near

	pop	ax		;return address
	pop	dx		;priority
	push	ax		;save return address
	mov	cl,mpm_set_prior
	int	mpm_call	;call MP/M
	and	ax,ax		;ax=0 if success

	jz	spok		;jump if success
	xor	ax,ax		;return 'False'
	ret

spok:	inc	ax		;return 'True'
	ret

setpriority  endp


;************************************************************************
;	FUNCTION QUEUE_MAKE(QD_P: ^QUEUE_DESCRIPTOR): BOOLEAN;		*
;Inputs:								*
;	Address of Queue Descriptor table				*
;Outputs:								*
;	True if success, False if failure				*
;Processing:								*
;	Creates a queue according to Queue Descriptor table.		*
;************************************************************************

queuemake  proc  near

	pop	ax		;return address
	pop	dx		;offset of QD
	pop	bx		;segment of QD
	push	ax		;save return address
	push	ds		;save current data segment

	mov	ds,bx		;get QD data segment
	mov	cl,mpm_make_q
	int	mpm_call	;call MP/M
	pop	ds		;restore ds
	and	ax,ax		;ax=0 if success

	jz	qmok		;jump if success
	xor	ax,ax		;return 'False'
	ret

qmok:	inc	ax		;return 'True'
	ret

queuemake  endp


;************************************************************************
; 	FUNCTION QUEUE_OPER(OP: INTEGER;				*
;			    QP_P: ^QUEUE_PARM_BLOCK): BOOLEAN		*
;Inputs:								*
;	Operation code for a queue operation (135 - 140)		*
;	Address of Queue Parameter Block				*
;Outputs:								*
;	True if success, False if failure				*
;Processing:								*
;	Ensures OP is a queue operation other than make_queue.		*
;	Performs operation, returns status				*
;************************************************************************

queueoper  proc  near

	pop	ax		;return address
	pop	dx		;qpb offset
	pop	bx		;qpb segment
	pop	cx		;operation code
	push	ax		;return address back on stack
	push	ds		;save current data segment

	mov	ds,bx		;get qpb segment
	cmp	cl,mpm_min_q	;validate op_code
	jb	qobad
	cmp	cl,mpm_max_q
	ja	qobad
	int	mpm_call	;op code ok - call MP/M
	pop	ds		;restore ds
	and	ax,ax		;check returned status

	jnz	qobad		;jump if failed
	inc	ax		;return 'True'
	ret

qobad:	xor	ax,ax		;return 'False'
	ret

queueoper  endp


;************************************************************************
;	PROCEDURE R_CON_RAW(STR: ^STRING)				*
;Inputs:								*
;	Address of string to read into					*
;Outputs:								*
;	Raw console input written to string				*
;Processing:								*
;	Reads raw console input up to a CR.				*
;	Chars placed in STR, which must be big enough.  CR not included.*
;	Equivalent to READLN(KBD, STR)					*
;************************************************************************

rconraw	proc	near

	pop	ax		;return address
	pop	bx		;offset of string
	push	bx		;put them back
	push	ax
	mov	byte ptr [bx],0	;set string length to 0

loop:	mov	cl,mpm_read_raw
	int	mpm_call	;read raw char from console
	cmp	al,0Dh		;is it cr?
	je	done		;exit if so

	pop	dx		;return address
	pop	bx		;offset of string
	push	bx		;put them back
	push	dx
	inc	byte ptr [bx]	;bump string length byte
	mov	cl,byte ptr [bx] ;get length of string
	mov	ch,0		;make 16 bits
	add	bx,cx		;addr of next character
	mov	byte ptr [bx],al ;store new character
	jmp short loop		;repeat until done

done:	ret	4		;exit, pop offset and segment

rconraw	endp


;************************************************************************
;	PROCEDURE GET_TOD(TOD: ^TOD_RECORD);				*
;Inputs:								*
;	Address of TOD record: record 					*
;				 day: integer				*
;				 sec, min, hour: byte			*
;			       end					*
;Outputs:								*
;	Record set as described below					*
;Processing:								*
;	day set to number of days since Jan 1, 1978			*
;	hour, min, sec set to binary value				*
;************************************************************************

gettod	proc  near
	pop	ax		;return address
	pop	dx		;offset of tod record
	pop	cx		;...segment
	push	cx		;put back input data
	push	dx
	push	ax
	push	ds		;save current data segment

	mov	ds,cx		;segment of tod record
	mov	cl,mpm_get_tod	;call mpm tod function
	int	mpm_call

	pop	dx		;original data segment
	pop	ax		;return address
	pop	bx		;offset of tod record
	pop	ds		;...segment
	push	ax		;restore return address
	push	dx		;restore original data segment
	add	bx,2		;point to hour byte
	call	todxlate	;translate from BCD to binary in place
	inc	bx		;point to minute byte
	call	todxlate
	inc	bx		;point to second byte
	call	todxlate
	pop	ds
	ret

gettod	endp

;	translate in place from BCD to binary
;	new = upper(old)*10 + lower(old)
;	where 'upper' and 'lower' means nybble

todxlate  proc  near

	mov	al,byte ptr[bx]	;BCD value in al
	mov	dl,al
	and	dl,0Fh		;just lower nybble in dl
	mov	ah,0
	mov	cl,4
	shr	ax,cl		;just upper nybble in ax
	mov	cl,10
	mul	cl		;upper * 10 in ax
	add	al,dl		;upper*10 + lower
	mov	byte ptr[bx],al	;store in place
	ret

todxlate  endp


;************************************************************************
;	FUNCTION WHO_AM_I: ^PROCESS_DESCRIPTOR				*
;Inputs:								*
;	None								*
;Outputs:								*
;	Address of calling process's PD					*
;Processing:								*
;	Returns PD address						*
;************************************************************************

whoami	proc  near

	push	es
	mov	cl,mpm_get_pd	;call mpm function
	int	mpm_call
	mov	bx,es		;segment address, offset in ax
	pop	es
	ret

whoami	endp


code	ends

	end
