/*
 * runtime.S86  -  MGL runtime module
 *
 * Copyright (C) 2003-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: runtime.S86,v 1.13 2007/01/06 18:31:38 gkminix Exp $
 *
 *====================================================================
 */

#include "rtcommon.i86"
#include <pxe/general.i86>

.file __FILE__
.line __LINE__



/*
 *====================================================================
 *
 * Definitions local to this module
 */
#define INT_0_OFS	(0 * 4)		/* offset to interrupt vector 0 */
#define INT_4_OFS	(4 * 4)		/* offset to interrupt vector 4 */
#define INT_5_OFS	(5 * 4)		/* offset to interrupt vector 5 */




.line __LINE__
/*
 *====================================================================
 *
 * Define the text segment. This segment can start at any offset depending
 * on the loader used. The mknbi-mgl bootrom loader loads this program
 * at offset 0, while DOS loads this program at offset 0x0100.
 */

	.text

	.extern	prcrlf			# externals from runtime library
	.extern	prnstr
	.extern	prnnum
	.extern	keycheck
	.extern	keyget
	.extern	brk
	.extern	sbrk
	.extern	initpxe

	.extern	_init_bootp		# external initialization routines
	.extern	_init_console
	.extern	_init_net

	.extern	_rtint_raise		# external exception handler

	.global	_start			# global routines
	.global	_rtint_exit
	.global	_rtint_error
	.global	_rtint_chkstk

_start:	jmp	start



/*
 *====================================================================
 *
 * The header of the runtime module serves two purposes: first it gives
 * the compiler certain information about the size and location of various
 * parts of the runtime module, and secondly the compiler will give some
 * information back like the size of the produced code, etc.
 */

	.org	RT_DATAOFS

rtinfo:	.word	rtid			# offset to ID string
	.long	0			# total load size of program
	.word	__text_end		# end of runtime text segment
	.word	__data_end		# end of runtime data segment
	.word	__bss_end		# end of runtime BSS segment
	.word	0			# end of constant data
	.word	0			# end of variable data
	.word	__initext_end		# offset to data segment
	.word	exptable		# offset to debugging data/export table
	.word	0			# start address
	.word	RTSTK_DEFAULT		# default stack size

newds:	.word	0			# new data segment
oldi0:	.long	0			# old interrupt vectors
oldi4:	.long	0
oldi5:	.long	0

rtid:	.asciz	RT_IDSTRING		# runtime ID string



/*
 *====================================================================
 *
 * Here the actual game starts. This program gets called by a loader
 * which passes the pointer to an information structure in ES:SI.
 * In this program we can be sure to run on a 386+ processor.
 */
start:	push	ds
	push	es			# save the BOOTP record pointer
	push	si
	call	1f			# compute code offset
1:	pop	ax
	sub	ax,offset 1b
	mov	bx,ax			# save code offset in BX
	mov	dx,cs			# convert code segment so that
	shr	ax,4			# it starts at a zero offset
	add	dx,ax			# save code segment in DX

	mov	ax,cs:rtdata_dataofs[bx + rtinfo]
	shr	ax,4
	add	ax,dx
	mov	ds,ax
	mov	es,ax
	mov	cs:[bx + newds],ax	# save it for later

	cld
	xor	ax,ax
	mov	di,cs:rtdata_constend[bx + rtinfo]
	mov	cx,cs:rtdata_varend[bx + rtinfo]
	sub	cx,di
	inc	cx			# clear variable data area
	shr	cx,1
	rep	stosw

	std
	mov	di,cs:rtdata_constend[bx + rtinfo]
	mov	cx,di
	dec	di
	sub	cx,cs:rtdata_bssend[bx + rtinfo]
	mov	si,cs:rtdata_dataend[bx + rtinfo]
	add	si,cx
	dec	si			# copy constant data area into
	rep	movsb			# proper position

	cld
	mov	di,cs:rtdata_dataend[bx + rtinfo]
	mov	cx,cs:rtdata_bssend[bx + rtinfo]
	sub	cx,di
	rep	stosb			# clear BSS segment
	pop	dword ptr [infoptr]	# save the info record pointer

	push	dx			# push new code segment value
	mov	ax,offset 2f		# change into new code segment
	push	ax
	lret

2:	cli
	mov	word ptr [oldstack + 0],sp
	mov	word ptr [oldstack + 2],ss
	mov	ax,ds			# set new stack
	mov	ss,ax
	mov	sp,0xFFFE
	mov	word ptr [stktop],sp	# save top of stack pointer
	sti
	nop				# necessary for some debuggers

/*
 * Check for PXE support
 */

	push	es
	les	bx,[infoptr]
	les	bx,es:rtload_pxe[bx]
	call	initpxe			# initialize PXE support
	pop	es
	or	ax,ax
	jnz	4f			# check if we have PXE installed
	inc	byte ptr [_havepxe]

/*
 * Check for flags passed to us by the runtime loader. If the trap flag
 * has not been set, disable all INT3 instructions.
 */

4:	push	es
	les	bx,[infoptr]
	mov	eax,es:rtload_bootp[bx]
	mov	[_btpptr],eax
	mov	ax,es:rtload_bsize[bx]
	mov	[_btplen],ax
	mov	ax,es:rtload_flags[bx]
	pop	es
	test	ax,RTFLAG_LOAD
	setnz	byte ptr [_canload]
	test	ax,RTFLAG_RUN
	setnz	byte ptr [_canrun]
	test	ax,RTFLAG_BOOTP
	setnz	byte ptr [_canchange]
	test	ax,RTFLAG_TRAP
	jz	3f
	jmp	0f

	.align	8
	.ascii	"trapadr$"		# string to find the trap address in
					# the runtime binary
trapaddr:
	.word	0			# address where to trap into the
					# debugger

0:	int3				# trap into debugger to allow changing
	nop				# the trap address
	mov	si,offset traplist
1:	mov	bx,cs:[si]		# scan through list of int3
	or	bx,bx			# instructions
	jz	3f
	mov	byte ptr cs:[bx],0xCC	# replace it with a INT3 instruction
	add	si,2
	jmp	1b


traplist:
	.word	trap1			# list of int3 instruction
	.word	trap2			# addresses
	.word	trap3
	.word	0


/*
 * This is required for GCC, which may use EBP for other purposes than
 * a stack pointer.
 */

3:	xor	ebp,ebp

/*
 * Setup all interrupt vectors required for the user program
 */

	cli
	push	es
	xor	ax,ax
	mov	es,ax
	mov	eax,dword ptr es:[INT_0_OFS]
	mov	dword ptr cs:[oldi0],eax
	mov	eax,dword ptr es:[INT_4_OFS]
	mov	dword ptr cs:[oldi4],eax
	mov	eax,dword ptr es:[INT_5_OFS]
	mov	dword ptr cs:[oldi5],eax

	mov	word ptr es:[INT_0_OFS + 0],cs
	mov	word ptr es:[INT_4_OFS + 0],cs
	mov	word ptr es:[INT_5_OFS + 0],cs
	mov	word ptr es:[INT_0_OFS + 2],offset rtint0
	mov	word ptr es:[INT_4_OFS + 2],offset rtint4
	mov	word ptr es:[INT_5_OFS + 2],offset rtint5
	pop	es
	sti

/*
 * Set the stack size and the correct beginning of the heap
 */

	mov	ax,cs:rtdata_stksize[rtinfo]
	mov	[stksiz],ax
	mov	ax,cs:rtdata_varend[rtinfo]
	call	brk			# set new heap pointer
	cmp	ax,-1			# check that we have enough memory
	jne	6f

5:	mov	ax,ENOMEMORY + NOUSER_ERROR_FLAG
	call	_rtint_raise

6:	mov	ax,[stktop]
	sub	ax,[stksiz]		# compute stack base
	jc	5b
	mov	[stkbase],ax

/*
 * Call module initialization routines. Note that the network
 * initialization routine has to come first!
 */

	call	_init_net
	call	_init_bootp
	call	_init_console
trap1:	nop				# gets replaced by int3

/*
 * Now call the user program. It should never return except for a
 * runtime error. The normal program termination is via the "load"
 * system call. Note that the user program expects the direction
 * flag cleared all the time.
 */

	cld
	mov	ax,word ptr cs:rtdata_start[rtinfo]
	call	ax
	mov	bx,RTERROR_ABORT	# prepare return value

/*
 * Call all cleanup routines
 */

doexit:
trap2:	nop				# gets replaced by int3
	xor	ax,ax
	mov	[rtaddr],ax
	xchg	ax,[_atexit]		# check if cleanup routine defined
	or	ax,ax
	jz	7f
	xchg	ax,bx
	movzx32	eax,ax
	call	bx			# call daisy-chained cleanup routines
	mov	bx,ax

/*
 * Restore all interrupt vectors
 */

7:	cli
	push	es
	xor	ax,ax
	mov	es,ax
	mov	eax,cs:dword ptr [oldi0]
	mov	dword ptr es:[INT_0_OFS],eax
	mov	eax,cs:dword ptr [oldi4]
	mov	dword ptr es:[INT_4_OFS],eax
	mov	eax,cs:dword ptr [oldi5]
	mov	dword ptr es:[INT_5_OFS],eax
	pop	es
	sti

/*
 * After restoring the stack, return to the caller via a far return
 */

8:	cli
	mov	ss,word ptr [oldstack + 2]
	mov	sp,word ptr [oldstack + 0]
	sti				# restore stack and return to
	mov	ax,bx			# caller
	pop	ds
	lret



/*
 *====================================================================
 *
 * Handle runtime errors
 * Input:  none
 * Output: none
 * Registers changed: all (routine doesnt return)
 */
rtint0:	push	si
	mov	si,EDIVZERO		# division by zero
	jmp	1f

rtint4:	push	si
	mov	si,EOVERFLOW		# integer overflow
	jmp	1f

rtint5:	push	si
	mov	si,EOUTOFBOUNDS		# bounds error
1:	push	ax
	push	bp
	mov	bp,sp
	mov	ax,cs
	cmp	ax,[bp + 8]		# check if the error was produced by
	pop	bp			# our own program
	pop	ax
	je	6f

	cmp	si,EDIVZERO
	jne	3f
	pop	si			# call old interrupt 0
	jmp	dword ptr cs:[oldi0]

3:	cmp	si,EOVERFLOW
	jne	4f
	pop	si			# call old interrupt 4
	jmp	dword ptr cs:[oldi4]

4:	cmp	si,EOUTOFBOUNDS
	jne	6f
	pop	si			# call old interrupt 5
	jmp	dword ptr cs:[oldi5]

/*
 * Process a runtime error in our own program
 */

6:	sti
	pop	ax
	mov	ax,cs:[newds]		# this is just for safety
	mov	ds,ax
	mov	es,ax
	mov	ax,si
	jmp	_rtint_raise		# raise an exception



/*
 *====================================================================
 *
 * Check the stack pointer and set the runtime address. This routine
 * gets called by all routines callable from the user program.
 * Input:  AX   -  Required stack size
 * Output: None
 * Registers changed: None
 */
_rtint_chkstk:

	push	bp
	push	ax
	mov	bp,sp
	mov	ax,[bp + 6]		# save return address
	sub	ax,3
	mov	[rtaddr],ax
	sub	bp,[bp + 0]
	jc	1f
	cmp	bp,[stkbase]		# check that we have enough stack
	ja	3f			# space

1:	xor	ax,ax			# determine top of heap space
	call	sbrk
	cmp	ax,bp			# if the stack already grew into the
	mov	ax,ESTKOVERFLOW		# heap, the user exception info might
	jb	2f			# be destroyed, so we dont allow scan-
	add	ax,NOUSER_ERROR_FLAG	# ning the user handlers in that case.
2:	call	_rtint_raise

3:	mov	ax,[rtaddr]
	cmp	ax,cs:[trapaddr]	# check if we have to call the
	jne	9f			# debugger trap
trap3:	nop				# gets replaced by int3

9:	pop	ax
	pop	bp
	ret


_rtproc_chkstk:
	push	bp
	mov	bp,sp
	mov	ax,[bp + 4]			# get required stack size
	pop	bp
	call	_rtint_chkstk			# call checking routine
	ret	2



/*
 *====================================================================
 *
 * Terminate the program
 * Input:  AX  -  Return code
 * Output: None
 * Registers changed: all (routine does not return)
 */
_rtint_exit:

	pop	bx			# discard return address
	mov	bx,ax
	jmp	doexit			# terminate the program


_rtproc_exit:

	pop	bx			# discard return address
	mov	bx,RTERROR_ABORT
	jmp	doexit			# terminate the program



/*
 *====================================================================
 *
 * Print a runtime error
 * Input:  AX  -  Pointer to error string
 * Output: None
 * Registers changed: all (routine does not return)
 */
_rtint_error:

	pop	bx			# discard return address
	push	ax
	mov	si,offset rterr
	call	prnstr			# print error message
	pop	si
	call	prnstr

	cld
	mov	si,[debugptr]
	or	si,si			# check if we have a debugging info
	jz	8f			# table
	mov	bx,[rtaddr]
	or	bx,bx			# check if we have a valid address
	jz	8f
	xor	cx,cx
2:	lodsw				# scan through the debug table to
	or	ax,ax			# find the address
	jz	7f
	cmp	ax,bx
	ja	7f
	lodsw
	mov	cx,ax			# remember last line number
	jmp	2b

7:	jcxz	8f			# check if we found a line number
	mov	si,offset linerr
	call	prnstr
	movzx32	eax,cx			# print the line number
	call	prnnum

8:	call	prcrlf
	mov	si,offset keyerr	# print "keypress" message
	call	prnstr
	call	keycheck		# clear keyboard buffer
	call	keyget			# wait for a key press
9:	mov	bx,RTERROR_ABORT	# indicate error return
	jmp	doexit			# terminate the program





/*
 *====================================================================
 *
 * Export table
 */
	.section .initext

exptable:


/*
 * Version information
 */

	.byte	RTEXP_VERSION		# type of export record
	.byte	VALTYPE_NUM		# version information is string type
	.word	RTIF_VERSION
	.word	0
	.asciz	"Interface Version"	# name of record


/*
 * "exit" routine
 */
	.byte	RTEXP_PROC		# type of export record
	.byte	VALTYPE_NONE		# return type
	.word	_rtproc_exit		# offset to routine
	.word	0			# number of arguments
	.asciz	"exit"			# name of routine


/*
 * "chkstk" routine
 */
	.byte	RTEXP_PROC		# type of export record
	.byte	VALTYPE_NONE		# return type
	.word	_rtproc_chkstk		# offset to routine
	.word	1			# number of arguments
	.asciz	"chkstk"		# name of routine
	.byte	VALTYPE_NUM		# 1st arg: number
	.byte	VALTYPE_NONE		# 1st arg: passed by value




/*
 *====================================================================
 *
 * Data area
 */
	_rodata


/* Copyright message */
cpyrgt:	.ascii	"MGL runtime module "
	.ascii	VERSION
	.byte	0x0D, 0x0A
	.ascii	COPYRIGHT
	.byte	0x0D, 0x0A
	.byte	0x0D, 0x0A
	.byte	0


/* Error messages */
rterr:	.asciz	"Runtime error: "
linerr:	.asciz	" at line "
keyerr:	.asciz	"Press any key to continue"




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

	.extern	stksiz			# stack size for i386 library




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


/* Global variables for the runtime module */

	.global	_atexit
	.global	_btpptr
	.global	_btplen
	.global	_canload
	.global	_canrun
	.global	_canchange
	.global	_havepxe

	.lcomm	_atexit,2		# pointer to first cleanup routine
	.lcomm	_btpptr,4		# pointer to bootrom BOOTP buffer
	.lcomm	_btplen,2		# size of bootrom BOOTP buffer
	.lcomm	_canload,1		# flag if we accept RTERROR_LOAD
	.lcomm	_canrun,1		# flag if we accept RTERROR_RUN
	.lcomm	_canchange,1		# flag if we accept BOOTP changes
	.lcomm	_havepxe,1		# flag if we have PXE support


/* Local variables */

	.global	stktop			# required for i386 library

	.align	2
	.lcomm	rtaddr,2		# current runtime address
	.lcomm	infoptr,4		# pointer to info record
	.lcomm	stktop,2		# pointer to top of stack
	.lcomm	stkbase,2		# pointer to base of stack
	.lcomm	debugptr,2		# pointer to debugging info
	.lcomm	oldstack,4		# old stack from caller




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

