/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  dosinit.S86
 * Purpose: Initialize the DOS simulator, setup all interupt vectors etc.
 * Entries: initdos, cleandos, call21, unused
 *
 **************************************************************************
 *
 * 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: dosinit.S86,v 1.7 2007/01/06 18:30:48 gkminix Exp $
 *
 */


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

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * Define a macro to produce jump table entries.
 *
 */
.macro	tab	func

	.extern	dos\func		# define label as near external

	.byte	0x\func			# save function number
	.word	dos\func		# save pointer to label

.endm

#define	TABSIZE	3			/* number of bytes per table entry */




.line __LINE__
/*
 **************************************************************************
 *
 * Interrupt handler table.
 *
 */
	.data

inttab:	.word	0x20 * 4, int20		# terminate program
	.word	0x21 * 4, int21		# dos function scheduler
	.word	0x27 * 4, int27		# terminate and stay resident
	.word	0x29 * 4, int29		# print character

#define	INTNUM	4			/* number of interrupts in table */
#define	FRSTINT	0x20			/* first interrupt used by DOS */
#define	LSTINT	0x2F			/* last interrupt used by DOS */
#define	VECTNUM	(LSTINT - FRSTINT + 1)	/* number of DOS vectors */



.line __LINE__
/*
 **************************************************************************
 *
 * The DOS simulator needs its own stack when int 0x21 is called. This stack
 * is placed in the BSS segment. Note that the real DOS uses three different
 * stacks. This will not be completely simulated. The stack for function
 * 0x59 is not necessary, so we only simulate two different stacks. The
 * stack signature is contained in the registers structure, so this struc-
 * ture has to come before the stack itself.
 *
 */
	.bss				# start BSS segment

	.extern	first_mcb		# segment of first MCB in list
	.extern	memory_end		# first segment past end of memory

#define	STACKSIZE	512		/* pretty small, so be careful. */

	.global	dos1_regs
	.global	dos2_regs

	.lcomm	dos1_regs,old_reg_size + STACKSIZE
	.lcomm	dos2_regs,old_reg_size + STACKSIZE


	.lcomm	temp_ax,2		# temporary storage for AX
	.lcomm	temp_bx,2		# temporary storage for BX


	.global	alt_handler
	.global	dos_active

	.lcomm	alt_handler,2		# alternative DOS handler
	.lcomm	dos_active,1		# DOS active counter




.line __LINE__
/*
 **************************************************************************
 *
 * Start code segment.
 *
 */
	.text

	.global	initdos			# define entry points
	.global	cleandos
	.global	call21
	.global	unused

	.extern	dos50			# directly called DOS functions
	.extern	dos51

	.extern	prnchr			# general library functions



/*
 **************************************************************************
 *
 * Save the DOS simulator data here so that we can access it from the
 * DOS interrupt routine.
 *
 */
datseg:	.word	0




/*
 **************************************************************************
 *
 * Initialize the mini-DOS simulator module.
 * Input:  DX  -  first segment usable for DOS memory
 * Output: None
 * Registers changed: (E)AX, BX, CX, DX, DI, ES
 *
 */
initdos:

	cld

/*
 * Initialize DOS memory by setting up the first two memory control blocks. The
 * first block has a size of one paragraph and contains all zeros. It can be
 * used as an empty environment for calling the DOS programs.
 */

	push	ds
	mov	ax,cs
	mov	cs:[datseg],ds
	mov	[first_mcb],dx		# save pointer to first MCB
	mov	[memory_end],ax		# save end of memory segment
	mov	ds,dx
	mov	byte ptr ds:[mcb_id],MCB_MEM_ID
	mov	word ptr ds:[mcb_owner],0xFFFF
	mov	word ptr ds:[mcb_size],1
	add	dx,2			# the memory block has already been
	dec	ax			# cleared by ROM loader
	sub	ax,dx			# compute size of free DOS memory
	dec	ax
	mov	ds,dx			# set MCB of first free memory block
	mov	byte ptr ds:[mcb_id],MCB_END_ID
	mov	word ptr ds:[mcb_owner],0
	mov	word ptr ds:[mcb_size],ax
	pop	ds

/*
 * Install interrupt handlers. We dont have to save the old vectors as
 * that will be done by the general interrupt handling routines.
 */

	xor	ax,ax
	mov	es,ax
	mov	di,FRSTINT * 4
	mov	cx,VECTNUM
	cli
	mov	ax,cs
	shl	eax,16
	mov	ax,offset unused
	rep	stosd			# save pointer to 'unused' routine
	mov	bx,offset inttab	# pointer to interrupt table
	mov	cx,INTNUM
3:	mov	di,[bx + 0]		# get pointer to interrupt vector
	mov	ax,[bx + 2]
	stosw				# save offset, segment is already stored
	add	bx,4			# continue with next interrupt
	loop	3b
	sti

/*
 * Clear VDS (Virtual DMA Specification) status byte in BIOS data area so
 * that a bus master network driver doesnt try to accidentally use a non-
 * existent VDS service.
 */

	push	ds
	mov	ax,BIOS_SEG
	mov	ds,ax
	mov	byte ptr ds:[BIOS_VDSFLAG],0
	pop	ds
	ret



/*
 **************************************************************************
 *
 * Clean all DOS interrupts from interrupt vector buffer
 * Input:  SS:BP  -  buffer of interrupt vectors
 * Output: None
 * Registers changed: (E)AX, CX, SI, DI
 *
 */
cleandos:

	cld
	push	es
	mov	cx,ss
	mov	es,cx
	lea	di,[bp + FRSTINT * 4]
	mov	cx,VECTNUM		# simply clear all interrupt vectors
	xor	eax,eax			# in the interrupt vector buffer
	rep	stosd
	pop	es
	ret



/*
 **************************************************************************
 *
 * Jump table for main dispatcher interrupt. Put it into code segment
 * (instead const segment) to save space in lower ram area.
 *
 */
jmptab:

/*
 * functions for basic I/O:
 */
	tab	01
	tab	02
	tab	03
	tab	04
	tab	05
	tab	06
	tab	07
	tab	08
	tab	09
	tab	0A
	tab	0B
	tab	0C

/*
 * functions for system management:
 */
	tab	0D
	tab	0E
	tab	19
	tab	1A
	tab	25
	tab	2E
	tab	2F
	tab	30
	tab	34
	tab	35
	tab	37
	tab	54

/*
 * functions for time operations:
 */
#ifdef NEEDTIME
	tab	2A
	tab	2B
	tab	2C
	tab	2D
#endif

/*
 * functions for memory and process management:
 */
	tab	00
	tab	31
	tab	48
	tab	49
	tab	4A
	tab	4C
	tab	4D
	tab	58

/*
 * functions for file handling:
 */
	tab	3C
	tab	3D
	tab	3E
	tab	3F
	tab	40
	tab	44

	.byte	0xFF			# end of table


/*
 * Those functions which were only implemented before DOS2.0 return
 * a different error code than those which were introduced with
 * DOS2.0 and later. The first "later" function is 30h:
 */

#define	DOS20FN	0x30



.line __LINE__
/*
 **************************************************************************
 *
 * Routine for main dispatcher interrupt 0x21. This involves changing to a
 * new stack, and then searching for the required function in the function
 * table. The function handler has to get the callers data segment from
 * old_ds if necessary.
 * Input:  AH  -  DOS function number
 *         others depending on function number
 * Output: depending on function number
 * Registers changed: depending on function number
 *
 */
int21:

/*
 * First check for functions which dont use an internal stack.
 */

	sti
	push	ds
	mov	ds,cs:[datseg]
	cmp	ah,0x33			# function 33h is not implemented
	je	9f
	cmp	ah,0x64			# function 64h is not implemented
	je	9f

	cmp	ah,0x59
	jne	1f
	mov	ax,0x00FF		# function 59h is not implemented,
	mov	bx,0x0D05		# so just return some dummy values
	mov	ch,0x01
	jmp	9f

1:	cmp	ah,0x50
	jne	2f
	call	dos50			# call DOS function 0x50 to set PSP
	jmp	9f

2:	cmp	ah,0x51
	je	3f			# function 0x51 is the same as 0x62
	cmp	ah,0x62
	jne	4f
3:	call	dos51			# implement function 0x51 and 0x62
9:	pop	ds
	iret

/*
 * For all other functions set new stack and then check the function table
 * for the function handler. Select the correct stack for the function
 * used. If changing anything in this code remember to also change the
 * basicio functions! Also note that interrupts have to be disabled while
 * using the temporary register variables.
 */

4:	cli
	mov	[temp_ax],ax		# temporarily save callers AX register
	mov	[temp_bx],bx		# temporarily save callers BX register
	mov	bx,bp
	mov	bp,offset dos1_regs
	or	ah,ah
	jz	5f
	cmp	ah,0x0C			# select correct stack
	jbe	6f
5:	mov	bp,offset dos2_regs
6:	pop	ds:old_ds[bp]		# save callers DS register
	mov	ds:old_bp[bp],bx	# save callers BP register
	mov	word ptr ds:old_stack[bp + 0],sp
	mov	word ptr ds:old_stack[bp + 2],ss
	mov	ax,ds
	lea	bx,[bp + old_reg_size + STACKSIZE - 2]
	mov	ss,ax			# set new stack
	mov	sp,bx

/*
 * Search for the function in the function table.
 */

	mov	bx,offset jmptab
	mov	ax,[temp_ax]
7:	mov	al,cs:[bx]
	cmp	al,0x0FF		# check for end of table
	je	8f
	cmp	al,ah			# found function value?
	je	1f
	add	bx,TABSIZE		# not found, loop to next
	jmp	7b			# table entry

/*
 * Found table entry, so prepare registers and jump to the routine. This
 * is done by putting its address onto the stack, and then executing a
 * near return instruction.
 * The network driver has the chance of using its own DOS handler by
 * setting 'alt_handler' to point to this routine. If the drivers routine
 * is willing to handle the DOS function, it has to remove the return
 * address and the address of the DOS simulator function from the stack.
 */

1:	inc	byte ptr [dos_active]	# increment active counter
	push	fs
	push	offset 3f		# push return address onto stack
	push	word ptr cs:[bx + 1]	# push jump address onto stack
	mov	bx,[temp_bx]
	mov	ax,[temp_ax]		# restore all registers except DS
	sti				# and BP

	test	word ptr [alt_handler],0xFFFF
	jz	2f
	push	old_bp[bp]
	push	old_ds[bp]
	call	[alt_handler]		# call alternative handler
	pop	old_ds[bp]
	pop	old_bp[bp]
2:	ret				# jump to handler routine

/*
 * Return to the caller by restoring the stack without changing any
 * registers and flags except carry.
 */

3:	pop	fs
	pushf
	cli
	dec	byte ptr [dos_active]	# decrement active counter
	mov	[temp_bx],bx
6:	mov	[temp_ax],ax
	pop	ax			# get flags
	mov	bx,word ptr ds:old_stack[bp + 0]
	mov	ss,word ptr ds:old_stack[bp + 2]
	mov	sp,bx			# this is the place where the INT
	mov	byte ptr ss:[bx + 4],al	# operation did put the flags
	mov	ax,[temp_ax]
	mov	bx,[temp_bx]
	push	ds:old_bp[bp]
	mov	ds,ds:old_ds[bp]	# restore callers registers
	pop	bp
unused:	iret

/*
 * We couldnt find the requested function in the function table, so return
 * with an error code in AX and the carry flag set. However, if it is a
 * FCB related function, return with the proper error code for that. Note
 * that interrupts are disabled when entering this code.
 */

8:	mov	al,0xFF			# return with proper error code
	cmp	ah,DOS20FN		# check if its an old function
	jb	7f
	mov	ax,DOS_ERR_NOFN		# return error code
7:	stc				# set carry flag to indicate error
	pushf
	jmp	6b



/*
 **************************************************************************
 *
 * INT20h handler: terminate current program.
 * Input:  none
 * Output: routine does not return
 *
 */
int20:

	xor	ah,ah
	jmp	int21			# just call DOS function 00h



/*
 **************************************************************************
 *
 * INT27h handler: terminate current program and stay resident.
 * Input:  DX  -  number of bytes in program memory block
 * Output: routine does not return
 *
 */
int27:

	add	dx,0x000F
	rcr	dx,1			# round byte count to nearest
	shr	dx,3			# paragraph
	mov	ah,0x31			# just call DOS function 31h
	jmp	int21



/*
 **************************************************************************
 *
 * INT29h handler: print character onto screen
 * Input:  AL  -  character to print
 * Output: none
 * Registers changed: none
 *
 */
int29:

	push	ax
	call	prnchr			# just print using the BIOS
	pop	ax
	iret



/*
 **************************************************************************
 *
 * Another way for a program to call the function dispatcher is to call
 * address psp:0005. This will jump to the following address.
 *
 */
call21:

	push	bp
	mov	bp,sp
	push	ax
	mov	ax,[bp + 6]		# get return address to caller
	mov	[bp + 2],ax		# and put it into place
	pushf
	pop	[bp + 6]		# replace the old return address
	pop	ax			# with the flags. this makes the
	pop	bp			# call look like an INT.
	jmp	int21




/*
 **************************************************************************
 *
 */

	.end

