/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  process.S86
 * Purpose: Process management functions for DOS simulator
 * Entries: dos00, dos31, dos4C, dos4D, loadprog, runprog
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: process.S86,v 1.9 2007/01/06 18:30:48 gkminix Exp $
 *
 */


/*
 **************************************************************************
 *
 * Include assembler macros:
 *
 */
#include <common.i86>
#include <romconfig.i86>
#include <i386/ctype.i86>
#include <system/bootrom.i86>
#include <dos/errors.i86>
#include <dos/exec.i86>
#include <dos/memory.i86>
#include "dossim.i86"

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * Data segment
 *
 */
		.data

psp_template:				# PSP template
		.byte	0xCD,0x20	# code for INT20h
		.word	MEMEND		# first allocatable seg after program
		.byte	0		# reserved
		.byte	0x9A		# far call OP-code to INT21h handler
		.word	call21		# pointer to INT21h handler
		.word	0
		.long	0		# saved interrupt 22h vector
		.long	0		# saved interrupt 23h vector
		.long	0		# saved interrupt 24h vector
		.word	0		# PSP of parent process
		.byte	0xFF,0xFF,0xFF	# reserved file handles
		.byte	0xFF,0xFF
		.space	20 - 5		# file handle table
		.word	0		# segment of program environment
		.long	0		# stack pointer of parent
		.word	20		# number of file handles
		.word	psp_handles	# offset of file handle table
		.word	0		# segment of file handle table
		.space	24		# reserved
		.byte	0xCD,0x21,0xCB	# OP-codes for INT21h and RETF
psp_template_end:




/*
 **************************************************************************
 *
 * BSS segment
 *
 */
	.bss

	.extern	dos_active		# DOS active flag
	.extern	first_mcb		# segment of first MCB

	.global	curr_psp


	.lcomm	curr_psp,2		# current PSP
	.lcomm	ret_code,2		# program return code
	.lcomm	new_psp,2		# segment of new PSP
	.lcomm	new_prog,4		# pointer to new program copy
	.lcomm	new_spss,4		# new stack pointer
	.lcomm	new_ipcs,4		# new execution address
	.lcomm	prognum,1		# number of currently loaded program




/*
 **************************************************************************
 *
 * Start code segment.
 *
 */
	.text

	.global	dos00
	.global	dos31
	.global	dos4C			# define entry poimts
	.global	dos4C
	.global	dos4D
	.global	dos50
	.global	dos51
	.global	loadprog
	.global	runprog

	.extern	dos48
	.extern	dos4A
	.extern	freeall
	.extern	call21
	.extern	unused

	.extern	prnstr			# external library routines
	.extern	prnnum
	.extern	prcrlf



/*
 **************************************************************************
 *
 * Get exit code.
 * Input:  none
 * Output: AL  -  exit code
 *         AH  -  exit cause
 * Registers changed: AX
 *
 */
dos4D:

	mov	ax,[ret_code]
	ret



/*
 **************************************************************************
 *
 * Set new PSP segment
 * Input:  BX  -  new PSP segment
 * Output: none
 * Registers changed: none
 *
 */
dos50:

	mov	[curr_psp],bx
	ret



/*
 **************************************************************************
 *
 * Get current PSP segment
 * Input:  none
 * Output: BX  -  current PSP segment
 * Registers changed: BX
 *
 */
dos51:

	mov	bx,[curr_psp]
	ret



/*
 **************************************************************************
 *
 * Terminate current process, but keep it resident in memory.
 * Input:  AL  -  return code
 *         DX  -  number of paragraphs to keep in memory (including PSP)
 * Output: BX  -  offset to error message passed to parent process
 * Registers changed: all
 *
 */
dos31:

	mov	ah,3
	mov	[ret_code],ax		# set return code
	mov	bx,dx
	mov	es,[curr_psp]
	call	dos4A			# resize the memory block
	jmp	dos4C0



/*
 **************************************************************************
 *
 * Terminate program.
 * Input:  none
 * Output: BX  -  offset to error message passed to parent process
 * Registers changed: all
 *
 */
dos00:

	les	bx,old_stack[bp]
	mov	dx,es:[bx + 2]		# load callers code segment
	mov	[curr_psp],dx		# set current PSP segment
	xor	al,al			# fall through to function 4C



/*
 **************************************************************************
 *
 * Terminate current program.
 * Input:  AL  -  return code
 * Output: BX  -  offset to error message passed to parent process
 * Registers changed: all
 *
 */
dos4C:

	xor	ah,ah
	mov	[ret_code],ax		# set return code
	mov	dx,[curr_psp]
	call	freeall			# free all memory used by this process
dos4C0:	jc	1f
	xor	bx,bx
	jmp	2f

1:	cmp	ax,DOS_ERR_NOMEM
	mov	bx,offset errnom
	je	2f
	mov	bx,offset errmem

/*
 * Restore the stack of the parent process, reset the PSP of the
 * parent process, reset the DOS active flag, and return to the parent
 * calling address. Also reset the interrupts $22 to $24.
 */

2:	cli
	mov	es,[curr_psp]
	mov	ax,es:[psp_parent]	# reset current PSP
	mov	[curr_psp],ax
	mov	byte ptr [dos_active],0	# reset DOS active flag
	lss	sp,es:[psp_oldstack]	# restore old stack

	cld
	xor	ax,ax
	mov	es,ax
	mov	di,0x22 * 4
	mov	cx,3
3:	mov	ax,offset unused	# restore interrupts 0x22 to 0x24
	stosw
	mov	ax,cs
	stosw
	loop	3b
	sti
	lret				# return to runprog



/*
 **************************************************************************
 *
 * Load a new process, but dont run it. This routine gets called by the 
 * network driver initialization code. It will internally store all values
 * necessary to run the program for later execution by 'runprog'.
 * The program image layout has to look like:
 *
 *       Offset
 *        0000  -  Two byte magic cookie
 *        0002  -  Length of command line in bytes
 *        0003  -  Command line (not terminated by zero)
 *        var   -  Length of program image in bytes
 *        var+2 -  Minimum number of paragraphs to allocate including PSP
 *        var+4 -  Maximum number of paragraphs to allocate including PSP
 *        var+6 -  Program image
 *
 * This routine will print a message in case of an error, and then return
 * with the carry flag set.
 *
 * Input:  ES:SI  -  pointer to program code image
 * Output: ES:SI  -  pointer to first byte after program code image
 *         BX     -  first segment of program code
 *         AX     -  new PSP segment
 *         Carry flag set if error
 * Registers changed: AX, BX, CX, DX, SI, DI, ES
 *
 */
loadprog:

	cld
	push	bp
	inc	byte ptr [prognum]

/*
 * First check that the program code image has the correct magic cookie
 * at its beginning.
 */

	cmp	word ptr es:[si],COM_MAGIC
	mov	bx,offset errprg
	jne	9f

/*
 * Now compute the size of memory required to load the program and try to
 * allocate it. For COM programs we simply try to get as much memory as
 * possible, while for EXE programs we have to use the amount specified
 * in the header. At the end of this part the registers are set as
 * follows:
 *
 *  AX:DI  -  pointer to loading area for program
 *  ES:SI  -  pointer to program image structure (see above)
 *  ES:BP  -  pointer to program image length value
 *  DX     -  PSP segment
 *
 * It is expected that the binary patch program has already verified that
 * the EXE file header is valid.
 */

	movzx	bx,byte ptr es:[si + 2]
	lea	bp,[bx + si + 3]

	mov	bx,es:[bp + 4]
	call	dos48			# check how much we can get at most
	jnc	1f
	cmp	ax,DOS_ERR_NOMEM
	jne	8f			# couldnt get that much, so try
	call	dos48			# to get as much as possible
	jc	6f
1:	cmp	bx,es:[bp + 2]		# check if the program will fit into
	jae	2f			# the reserved memory area

7:	mov	bx,offset errnom
	jmp	9f
6:	cmp	ax,DOS_ERR_NOMEM
	je	7b
8:	mov	bx,offset errmem
9:	call	doserror		# print error message (returns with
	jmp	0f			# carry flag set)

2:	mov	di,PSP_SIZE		# set program start address
	mov	dx,ax
	cmp	word ptr es:exehdr_sig[bp + 6],EXE_MAGIC
	jne	3f
	add	ax,PSP_SIZE / 16	# compute program start address
	xor	di,di			# for EXE program
3:	mov	[new_psp],dx		# save PSP and start addresses
	mov	word ptr [new_prog + 0],di
	mov	word ptr [new_prog + 2],ax

/*
 * Setup new PSP by copying the PSP template into it. The FCBs remain empty
 * as they are not used. BX still contains the size of the new memory block.
 */

	mov	dx,es			# save original ES
	mov	es,[new_psp]
	xor	ax,ax
	xor	di,di
	mov	cx,PSP_SIZE / 2		# first clear the whole PSP area
	rep	stosw

	mov	psp_seghndl[psp_template],es
	mov	ax,[curr_psp]		# setup PSP template
	mov	psp_parent[psp_template],ax
	mov	psp_dispatch[psp_template + 3],cs
	mov	ax,[first_mcb]		# the first MCB contains an empty memory
	inc	ax			# block which we can use as a dummy env.
	mov	psp_env[psp_template],ax
	add	bx,[new_psp]		# compute end of allocated memory
	mov	psp_endmem[psp_template],bx

	mov	bx,si			# save original SI
	xor	di,di
	mov	si,offset psp_template
	mov	cx,offset psp_template_end - offset psp_template
	rep	movsb			# copy PSP template into memory area

	push	ds
	mov	ds,dx
	mov	di,_I(psp_cmdl)
	lea	si,[bx + 3]
	movzx	cx,byte ptr [bx + 2]	# get size of command line
	jcxz	4f
	cmp	cx,PSP_MAXCMDL
	jbe	5f
	mov	cx,PSP_MAXCMDL
5:	mov	es:[psp_cmdlsize],cl
	rep	movsb			# save command line
4:	mov	al,CHR_CR		# terminate command line with CR
	stosb
	xor	al,al			# and with a zero byte
	stosb
	pop	ds

/*
 * Copy the program code into the new memory block. At the end of this part
 * the registers are set as follows:
 *
 *    DX:BP  -  pointer to first byte of program image
 *    DX:SI  -  pointer to first byte after program image
 *    ES:DI  -  pointer to first byte after new program copy
 */

	push	ds
	mov	ax,word ptr [new_prog + 0]
	mov	di,ax
	shr	ax,4			# adjust to lowest possible offset
	add	ax,word ptr [new_prog + 2]
	and	di,0x000F
	mov	es,ax

	mov	ax,si
	shr	ax,4			# adjust to lowest possible offset
	add	dx,ax
	and	si,0x000F
	mov	ds,dx

	mov	cx,word ptr [si]	# copy program code
	add	si,6
	mov	bp,si
	cmp	word ptr exehdr_sig[si],EXE_MAGIC
	jne	6f
	mov	ax,exehdr_headsize[si]
	shl	ax,4
	add	si,ax			# dont copy EXE header
	sub	cx,ax
6:	shr	cx,1
	rep	movsw			# use word copy for speed
	jnc	2f
	movsb				# copy the remaining byte
2:	pop	ds

/*
 * Now set the owner of all memory blocks. This owner ID is simply the PSP
 * segment. Also set the end of stack and execution address for COM programs.
 * At the end of this part the registers are set as follows:
 *
 *    DX:BP  -  pointer to first byte of program image
 *    DX:SI  -  pointer to first byte after program image
 *    BX     -  segment of new program copy
 *
 * DX:SI and BX have to remain intact until the end of the routine.
 */

	push	ds
	mov	bx,[new_psp]
	mov	ax,bx			# set program MCB
	dec	bx
	mov	ds,bx
	mov	ds:[mcb_owner],ax	# set owner of PSP memory block
	add	ax,ds:[mcb_size]	# get first paragraph following MCB
	pop	ds

	mov	bx,word ptr [new_prog + 2]
	sub	ax,bx			# compute program size
	cmp	ax,0x1000
	jb	3f			# dont allow a stack pointer above
	xor	ax,ax			# 0xFFFE
	jmp	4f

3:	shl	ax,4			# convert paragraph size into byte
4:	dec	ax			# offset for top of stack
	dec	ax	
	mov	word ptr [new_spss + 0],ax
	mov	word ptr [new_spss + 2],bx
	mov	ax,word ptr [new_prog + 0]
	mov	word ptr [new_ipcs + 0],ax
	mov	word ptr [new_ipcs + 2],bx

/*
 * Compute the starting segment of the program and stack for EXE programs.
 * Then adjust the relocation entries in the EXE header. At the end, we
 * have the following register allocation:
 *
 *    DX:SI  -  pointer to first byte of program image
 *    DX:BP  -  pointer to first byte after program image
 *    BX     -  code segment of new program copy
 */

	shr	ax,4			# compute segment of execution address
	add	ax,bx
	xchg	si,bp
	mov	es,dx			# check for EXE file
	cmp	word ptr es:exehdr_sig[si],EXE_MAGIC
	jne	5f

					# compute execution address
	mov	ax,word ptr es:exehdr_cs[si]
	add	word ptr [new_ipcs + 2],ax
	mov	ax,word ptr es:exehdr_ip[si]
	mov	word ptr [new_ipcs + 0],ax

					# compute stack pointer
	mov	ax,word ptr es:exehdr_ss[si]
	add	word ptr [new_spss + 2],ax
	mov	ax,word ptr es:exehdr_sp[si]
	mov	word ptr [new_spss + 0],ax

					# get number of relocation entries
	mov	cx,es:exehdr_relocnum[si]
	jcxz	6f
	push	ds
	mov	ds,dx
	add	si,exehdr_reloctab[si]	# get offset to relocation table
1:	lodsw				# get relocation offset
	mov	di,ax
	lodsw				# get relocation segment
	add	ax,bx
	mov	es,ax
	add	es:[di],bx		# adjust word to be relocated
	loop	1b
	pop	ds
6:	mov	ax,word ptr [new_ipcs + 2]

/*
 * Thats all. Adjust ES:SI as return value.
 */

5:	mov	bx,ax			# prepare BX return value
	mov	ax,bp
	shr	ax,4			# adjust to lowest possible offset
	add	ax,dx
	mov	es,ax
	and	bp,0x000F		# this will also clear the carry flag
	mov	si,bp
	mov	ax,[new_psp]		# return new PSP segment
0:	pop	bp
	ret



/*
 **************************************************************************
 *
 * Run a recently loaded program. If the DOS termination function found an
 * error, it will return the offset to the error message in BX.
 * Input:  none
 * Output: Carry flag set if error
 * Registers changed: AX, BX, CX, DX
 *
 */
runprog:

	cli
	push	ds
	push	es
	push	si
	push	di
	push	bp

/*
 * Push return address and save old stack pointer
 */

	push	cs
	push	offset runret		# push return address
	mov	dx,[new_psp]
	mov	es,dx			# save old stack pointer
	mov	word ptr es:[psp_oldstack + 0],sp
	mov	word ptr es:[psp_oldstack + 2],ss

/*
 * Make new program current, switch stack to new stack and push termination
 * address. Then set the data segment registers and call the new program.
 */

	mov	[curr_psp],dx
	lss	sp,[new_spss]
	push	0			# save near pointer to terminate fctn
	push	dword ptr [new_ipcs]
	sti
	mov	ds,dx			# set data segment register correctly
	mov	ax,0xFFFF		# indicate FCBs are invalid
	lret				# call the new program

/*
 * The called program will return here. Simply pop all registers from the
 * stack and return to the caller.
 */

runret:	pop	bp
	pop	di
	pop	si
	pop	es
	pop	ds
	or	bx,bx
	jz	9f			# set carry flag in case of error
	call	doserror		# print DOS error message
9:	ret



/*
 **************************************************************************
 *
 * Print DOS error message
 * Input:  DS:BX  - pointer to error message
 * Output: Carry flag set
 * Registers changed: AX
 *
 */
doserror:

	push	si
	mov	si,offset errmsg
	call	prnstr			# print starting message
	mov	si,bx
	call	prnstr			# print error message
	cmp	bx,offset errprg
	jne	9f
	push	eax
	push	dx
	mov	dl,10
	movzx32	eax,byte ptr [prognum]	# print program number
	call	prnnum
	pop	dx
	pop	eax
9:	call	prcrlf
	pop	si
	stc				# return to caller
	ret




/*
 **************************************************************************
 *
 * Strings and error messages
 */
	_rodata

errmsg:	.byte	0x0D,0x0A
	.asciz	"DOS ERROR: "
errnom:	.asciz	"no free mem"
errmem:	.asciz	"mem corrupt"
errprg:	.asciz	"corrupt prog "




/*
 **************************************************************************
 *
 */
	.end

