;*****************************************************
;*
;*	Dispatch Routines
;*
;*****************************************************

;=====
fdisp:
;=====
; This entry point used by interrupt routines in XIOS
; Note: if the XIOS is performing memory protection interrupt
; handlers must enable O.S. memory before calling the O.S.

	cli
	push ds ! mov ds,sysdat
	cmp indisp,true ! je nodisp		;if indisp=true then we are
	    mov ax_sav,ax			;in the dispatcher and
 	    mov al,true ! mov indisp,al		;this code is skipped
	    mov ax,es ! mov es_sav,ax
	    mov ax,rlr ! xchg ax,bx ! mov bx_sav,ax
	    mov es,p_uda[bx]
	    mov al,true ! mov u_in_int,al
	    pop ax ! mov u_ds_sav,ax
	    mov al,p_stat[bx]
	    mov u_stat_sav,al
	    mov p_stat[bx],ps_run
	    mov ax,es_sav
	    mov u_es_sav,ax

				; AX in AX_SAV
				; BX in BX_SAV
				; ES in U_ES_SAV
				; DS in U_DS_SAV
				; p_stat in U_STAT_SAV
	    jmp intdisp
		;dispatcher will jump to here if
		; u_in_int = true.

int_disp_exit:					;interrupts are off

				; AX in AX
				; BX in BX
				; ES in U_ES_SAV
				; DS in U_DS_SAV
				; p_stat in U_STAT_SAV

	    mov ax_sav,ax
	    mov ax,bx ! mov bx_sav,ax
						;check for
	    cmp drl,0 ! jnz intdisp		;interrupt occurence
						;on dispatcher exit

		mov al,false ! mov u_in_int,al
		mov ax,rlr ! mov bx,ax
		mov al,u_stat_sav
		mov p_stat[bx],al
		mov ax,bx_sav ! mov bx,ax
		mov ax,ax_sav
		mov indisp,false
		mov ds,u_ds_sav
		mov es,u_es_sav
		iret

nodisp:	pop ds
	iret


;========
farpdisp:
;========
; Intermodule pdisp (non-interrupt)

	call pdisp ! retf

;=====
pdisp:
;=====
; Call dispatcher with no special action

	push bx ! mov bx,rlr
	mov p_stat[bx],ps_run ! pop bx
	;jmp dsptch

;======
dsptch:
;======
;	The dispatch function looks like a noop to the
;	caller. All flags and registers are maintained.
;	No levels of user stack is used.
;	(jmp dispatch = ret)
;	Interrupt routines enter through fdisp.
;
;	Dispatch has two (2) arguments:
;	1. the p_stat field of the  process  descriptor
;	    determines the type of  action  to  perform
;	    for this process.
;	2. the dparam field of the uda is  an  argument
;	    to the action.
;	The main part of the dispatch routine takes the
;	currently running process off  the  Ready  list
;	and jmps to a routine which will put it on some
;	other list depending on  the  p_stat  argument.
;	The subsequent routine will then  jump  to  the
;	scheduler which will do polling of devices  and
;	move processes off the dispatch ready list onto
;	the Ready list.  The Ready List  is  maintained
;	in priority order with  round-robin  scheduling
;	of processes with equivalent  priorities.   The
;	first process on the ready list  will  then  be
;	switched in.

		; set indisp flag
	pushf ! cli
	cmp indisp,true ! jne dispin
	    popf ! ret
dispin:	mov indisp,true
	pop u_flag_sav

		; assumming bx=RLR:
		; if PLR=0 and DRL=0 then
		;    if p_stat[bx]=PS_RUN then
		;      if p_link[bx]=0 or
		;	  p_prior[p_link[bx]]<>p_prior[bx] then
		;	    don't do dispatch

	mov ax_sav,ax
	mov ax,bx ! mov bx_sav,bx
intdisp:
	cmp plr,0 ! jne dcont			;if Poll list = 0 and
	  cmp drl,0 ! jne dcont			;Dsptch Ready list = 0 and
	    mov ax,rlr ! mov bx,ax		;(RLR can never be 0 here)
	    cmp p_stat[bx],ps_run ! jne dcont	;our status is run and
	      cmp p_link[bx],0 ! je no_disp2    ;other PD to ready to run
		mov al,p_prior[bx]		;with an equal priority
		mov bx,p_link[bx]		;THEN skip the dispatch
		cmp al,p_prior[bx] ! je dcont
no_disp2:
		    mov ax,bx_sav ! mov bx,ax
		    mov ax,ax_sav
		    jmp dext

dcont:	
	mov u_ss,ss ! mov u_sp,sp
	mov ss,sysdat ! mov sp,offset dsptchtos
	mov ax,bx_sav ! mov bx,ax
	mov ax,ax_sav
	sti
	cld
		; save registers
		; NOTE: We will use DS instead of ES
		;  No segment overrides...

	push es ! pop ds
	mov ds:u_ax,ax
	mov ax,bx ! mov ds:u_bx,ax
	mov ax,cx ! mov ds:u_cx,ax
	mov ax,dx ! mov ds:u_dx,ax
	mov ax,di ! mov ds:u_di,ax 
	mov ax,si ! mov ds:u_si,ax
	mov ax,bp ! mov ds:u_bp,ax

;	Save interrupt vectors 0,1,3,4 not INT 2 which is NMI
;	MP/M-86, CCP/M-86 1.0 on the IBM PC saved NMI
;	Block move first 2

	xor bx,bx ! mov ds,bx
	mov si,bx ! mov di,offset u_ivectors
	mov dx,4
	mov cx,dx ! rep movsw
	mov cx,dx ! add si,dx		;get next 2
	add di,dx			;skip INT 2 location documented
	rep movsw			;now as reserved word in UDA
		; block move osint,debugint

	mov si,offset i_os_ip ! mov di,offset u_os_ip
	mov cx,dx ! rep movsw

		; take current process off RLR.
	mov ds,sysdat
;					;disable memory for the process
;					;we are taking out of context
					;turn off interrupts ?	
;	mov bx,rlr !; lea si,p_mem-ms_link[bx]
;dsp_dnxt:
;	mov si,ms_link[si]
;	test si,si !; jz dsp_disabled
;	  mov cx,ms_start[si] !; mov dx,ms_length[si]
;	  push si
;	  mov ax,io_disable !; call xiosif
;	  pop si

;	  jmps dsp_dnxt
;
;dsp_disabled:

	nop
	mov ax,rlr ! mov si,ax
	mov ax,p_link[si] ! mov rlr,ax
	mov p_link[si],0
		; We are now in NO-MAN's land
		; From now until the end of the
		; switch routine, There is no
		; process in context.
		; SI -> PD just taken out of context.
		; jump to routine for given status
	xor bh,bh ! mov bl,p_stat[si] ! shl bx,1
	jmp cs:dsp_table[bx]

		org ((offset $)+1) AND 0fffeh

dsp_table	dw	disp_act	;00 - run
		dw	disp_act	;01 - (nop)-poll device
		dw	delay_act	;02 - delay
		dw	disp_act	;03 - (nop)-swap
		dw	term_act	;04 - terminate
		dw	sleep_act	;05 - sleep
		dw	disp_act	;06 - (nop)-dq
		dw	disp_act	;07 - (nop)-nq
		dw	flag_act	;08 - flag wait
		dw	disp_act	;09 - (nop)-ciowait
		dw	disp_act	;10 - (nop)-sync

sleep_act:
;---------
; insert running process into list specified by
; u_dparam and set p_stat from p_scratch
; Note: we cannot sleep on the DLR since interrupts are on
; here, and flag_set can change the DLR

	mov ax,u_dparam
	mov bx,ax
	push si ! call insert_process
	pop si ! or p_flag[si],pf_resource
	mov al,p_scratch[si] ! mov p_stat[si],al
	jmp schedule

delay_act:
;---------
; Put the running process on the Delay List.  The
; delay list is built such that any process's
; remaining delay time is the additive of the delay
; times of all processes ahead of it plus the # of
; ticks in it's own p_wait field.  At each clock tick
; the p_wait field of the top process in the list
; is decremented.  If it reaches zero (0), all
; processes with a zero in the p_wait field are
; placed on the dispatch ready list.
;	input:	SI=pd address

	cli				;keep flag set from changing
if mpm					;TICK, and changing DLR
	push si ! mov al,io_strtclk
	call xiosif ! pop si
endif

if ccpm
	mov tick,true
endif

	mov bx,(offset dlr)-p_link
	mov cx,u_dparam ! inc cx
	cmp cx,0 ! jne del_lp
	    dec cx
del_lp:	mov di,p_link[bx]
	cmp di,0 ! je del_o
	    mov ax,p_wait[di]
	    cmp ax,cx ! ja del_o
		sub cx,ax ! mov bx,di ! jmps del_lp
del_o:	mov p_link[si],di ! mov p_link[bx],si
	mov p_wait[si],cx
	cmp di,0 ! je del_e
	    sub p_wait[di],cx
del_e:	jmp schedule

flag_act:
;--------
; place running process in flag table to wait
; for next flag. Note, flag may have been set...
;	input:  SI=pd address
;		U_DPARAM=address of Flag entry

	mov ax,u_dparam ! mov bx,ax
	cli				;protect from flag set
	cmp flg_pd[bx],flag_on ! je gflagon
	    mov flg_pd[bx],si ! mov p_link[si],0
	    jmp schedule
gflagon:				; Flag set since wait check
	mov flg_pd[bx],flag_off
	sti
	jmps disp_act

term_act:
;--------
; Terminate the running process, free memory, free pd, free sync
; structures.  Can only be called by TERMINATE_ENTRY.

;	input:	SI=pd address
;		MEM_SYNC owned by calling process

		; place PD on rlr for now.
	mov ax,rlr
	mov p_link[si],ax
	mov rlr,si

		; clean up consoles
	mov cx,f_cioterm ! call osif

		; clean up memory
		; (we own the MXmemory queue)
free_nxt:
	mov ax,rlr ! mov si,ax
	mov si,p_mem[si]
	test si,si ! jz end_free
	    push ds ! xor cx,cx ! push cx
	    push ms_start[si]
	    mov ax,ss ! mov ds,ax ! mov dx,sp
	    mov cx,f_memfree ! call osif
	    pop bx ! pop cx ! pop ds
	    jmps free_nxt
end_free:

					;release any sync structures
					;after releasing memory
	mov mem_cnt,0			;and MEM_CNT=1
					;on entry, also own THRD_SYNC
					;so it is safe to call
					;FREEPD below,
					;ASSIGN_SYNC cannot be called
					;with the THRD_SPB
					;SI=terminating process
	mov bx,offset slr - sy_link	;release any sync structures
t_nsync:				;owned by terminating PD
	mov bx,sy_link[bx]
	test bx,bx			;end of syncs?
	jz t_sync_done
	  push bx			;PD cannot be allowed to
	  call unsync_entry		;abort if in SY.NEXT
	  pop bx
	  jmps t_nsync

t_sync_done:

		; take off RLR
	mov si,rlr
	mov ax,p_link[si]
	mov rlr,ax
	mov p_link[si],0

;	cmp si,owner_8087		;release 8087 if we owned it
;	jne t_end ! mov owner_8087,0
;t_end:
		; free up PD
	call freepd ! jmp schedule 


disp_act:
;--------
; 	place process on RLR
;	input:	SI=pd address

	mov p_stat[si],ps_run
	mov bx,(offset rlr)-p_link
	call insert_process ! jmp schedule

;==============
insert_process:
;==============
;
; put PD# in list ordered by priority
;
;	entry:	BX = list root
;		SI = pd number
;	exit:   SI   is preserved
;		interrupt state as on entry

	 mov cx,pflag[si] ! and cx,pf_resource
						;if a process was waiting
ins_npd: mov di,p_link[bx]			;on a resource, insert
	 test di,di ! jz ins_out		;it ahead of equal priority
	    mov al,p_prior[di]			;process
	    cmp al,p_prior[si]
	    ja ins_out				;lowest priority first
		jb ins_nxt			;higher - keep going down list
		    jcxz ins_nxt		;equal and not resource
			jmps ins_out		;equal & resource
ins_nxt:	mov bx,di ! jmp ins_npd
ins_out: jcxz ins_exit
	   and p_flag[si],not pf_resource
ins_exit:
	 mov p_link[si],di ! mov p_link[bx],si ! ret

;========
schedule:
;========
; poll all required devices and place any ready
; processes on the Ready List

		;we can enable interrupts now.
		;there MUST be a process on the RLR
		;at this point, ie. IDLE...
	sti
		;go through the Poll List
	mov di,(offset plr)-p_link
		;get the next PD on list.
		;DI is the last one which
		;has already been checked.
polld_another:
	mov si,p_link[di]
		;SI is the next PD to check
	test si,si ! jz drltorlr
		;SI is valid PD, poll it.

		;If top PD on the PLR has a worse
		;priority compared to top PD on the RLR,
		;there is no reason to call the XIOS
		;and poll the device, this time through
		;the dispatcher.  We must poll on equal
		;priority to keep a compute bound
		;process and the CLOCK from locking
		;out a polling process.
		;Note, we stop polling after
		;the first process that has polled
		;successfully, or we get to the end of
		;the PLR.  The process is placed on
		;the RLR.

	    mov bx,rlr ! test bx,bx		;if RLR=0: poll
	    jz poll_it
	      mov al,p_prior[si]		;priority of 1st poll PD
	      cmp al,p_prior[bx] ! jbe poll_it	;poll if equal or better
		jmps drltorlr			;priority than head of RLR
poll_it:
	    push di
if mpm
	    mov cx,p_wait[si]
endif
if ccpm
	    mov dx,p_wait[si]
endif
	    mov al,io_polldev ! call xiosif
	    pop di ! mov si,p_link[di]
		;if AL=0, device not ready.
	    cmp al,0 ! je polld_next
			;device ready,
			;move SI from PLR to RLR
		mov ax,p_link[si] ! mov p_link[di],ax
		mov bx,(offset rlr)-p_link
		mov p_stat[si],ps_run
		call insert_process		;got one ready to run:
		jmps drltorlr			;stop polling
			;p_link[SI]=next PD to check
polld_next:		;SI has been checked
	    mov di,si ! jmps polld_another

drltorlr:
;--------
; Pull all processes on the dispatch ready list and
; place them on the Ready List.

		;We must disable interrupts while
		;playing with DRL since interrupts
		;doing a Flag_set may also.
		;We must competely drain the DRL since
		;it is in no particular order.

	cli ! mov si,drl		;protect DRL from flag set
	test si,si ! jz switch
	    mov ax,p_link[si] ! mov drl,ax
;	    test ax,ax			;is this the last PD on DRL?
;	      jnz drl_noi 		;yes - don't turn on interrupts
	    sti				;interrupts off guarentees
;drl_noi:				;the last DRL PD with the
	    mov p_stat[si],ps_run	;best priority will run
	    mov bx,(offset rlr)-p_link	;next and at least until
	    call insert_process		;it turns on interrupts
	    jmps drltorlr

switch:
;------
; switch to the first process on the Ready List 

	sti
	mov bx,rlr
		; if no next process, go back		;
		; to schedule.  Gives more immediate	;
		; response to polled and interrupt	;
		; driven devices			;
	test bx,bx ! jnz switch1			;
	    jmp schedule				;
switch1:						;

;					;enable memory for this process
;					;turn on interrupts ?
;	mov bx,rlr !; lea si,p_mem-ms_link[bx]
;dsp_enxt:
;	mov si,ms_link[si]
;	test si,si !; jz dsp_enabled
;	  mov cx,ms_start[si] !; mov dx,ms_length[si]
;	  push si
;	  mov ax,io_enable !; call xiosif
;	  pop si
;	  jmps dsp_enxt
;
;dsp_enabled:
;
;	;Save and restore the 8087 environment if process to run
;	;uses the 8087 and is not the owner.  Interrupts
;	;must be on.  Code from Intel Ap. Note. 113 page 29
;
;	;This code shouldn't be added unless interrupt windows in
;	;switch are allowed or the 8087 restore is separated
;	;from the 8086/8088 restore.  The switch code without this
;	;commented out 8087 code,
;	;creates an interrupt window approx. 100 to 200 micro
;	;secs on 5 to 4 meg CPU.

;	;Allowing interrupt windows in switch
;	;means we must check on leaving the dispatcher
;	;for an interrupt awakened process (DRL again <> 0)
;	;and call the
;	;dispatcher again to prevent a 16 milli second wakeup time
;	;for a PD doing a flagwait after the interrupt service routine.

;	;Calling the dispatcher at the end of the dispatch
;	;(see commented out code at end of dispatcher and at
;	;INT_DISP_EXIT:)
;	;creates contention problems between PDs waiting for a resource
;	;and PDs waking up from interrupts.  It cannot be guarenteed
;	;who will run next.  An interrupt awakened process can
;	;get a just freed resource is should have waited for.
;	;The RTM ASSIGN_SYNC_ENTRY is an untested solution
;	;for allowing interrupts in the dispatcher switch code.

;					;BX=PD to run next
;	test p_flag[bx],pf_8087		;does this process use the NPX ?
;	jz done8087
;	  cmp bx,owner_8087		;do we already own it
;	  je done8087
;	    xchg bx,owner_8087		;new owner, also set by terminate
;	    test bx,bx ! jz get8087	;no one owns it if BX=0
;	      mov dx,ds			;save sysdat in DX
;	      mov ds,p_uda[bx]		;old owner's UDA
;	      fnstcw ds:u_8087		;save IEM bit status
;	      nop			;delay while 8087 busy saves control reg
;	      fndisi			;disable 8087 busy signal
;	      mov ax,ds:u_8087		;get original control word
;	
;	      fsave ds:u_8087		;save NPX context
;	      fwait			;IEM=1.wait for save to finish
;	      mov ds:u_8087,ax		;save original control word
;get8087:
;	    mov ds,dx			;DS=sysdat
;	    mov bx,rlr
;	    mov ds,p_uda[bx]		;PD to run next
;	    frstor ds:u_8087		;get its 8087 environment
;	    push ds			;DS=UDA
;	    jmps restore		;UDA on stack
;
;done8087:				;BX=PD to run next

	mov dx,p_uda[bx] ! mov ds,dx	;DS=UDA
	push dx				;UDA on stack

	;Restore interrupt vectors

restore:
	xor ax,ax ! mov es,ax ! mov di,ax
	mov si,offset u_ivectors
	mov dx,4
	mov cx,dx ! rep movsw		;restore interrupt vectors 0,1
	mov cx,dx ! add di,dx		;don't touch NMI
	add si,dx			;skip what was NMI
	rep movsw			;restore interupt vectors 3,4

	mov si,offset u_os_ip		;DS=UDA
	mov di,offset i_os_ip
	mov cx,dx ! rep movsw

		; restore registers
	mov ax,ds:u_bx ! mov bx,ax
	mov ax,ds:u_cx ! mov cx,ax
	mov ax,ds:u_dx ! mov dx,ax
	mov ax,ds:u_si ! mov si,ax
	mov ax,ds:u_di ! mov di,ax
	mov ax,ds:u_bp ! mov bp,ax
	mov ax,ds:u_ax
		; restore DS and ES and stack
	pop es				;ES=UDA
	cli				;turn interrupts off for rest
	mov ss,u_ss			;of exit
	mov sp,u_sp
	mov ds,sysdat
dext:
	cmp u_in_int,true ! jne dret
	    jmp int_disp_exit
dret:
	push u_flag_sav
	mov indisp,false
	cmp drl,0 ! je dd_ret
	    popf		; someone is on DRL from interrupt during
	    jmp pdisp		; switch, dispatch now, no 16ms wait
dd_ret:
	popf
	ret
