/*
 * Copyright (c) 1987 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#if defined(LIBC_SCCS) && !defined(KERNEL) && !defined(SUPERVISOR)
	<@(#)csv.s	2.4 (2.11BSD GTE) 12/24/92\0>
	.even
#endif

/*
 * C register save and restore routines.  When a C routine is called its stack
 * frame (after csv or ovhandlr have set things up) looks like this:
 *
 *	_________________________________
 *	| return address to caller 	|
 *	|-------------------------------|
 * r5->	| old r5 ("frame pointer")	|
 *	|-------------------------------|
 *	| previous __ovno		|
 *	|-------------------------------|
 *	| old r4			|
 *	|-------------------------------|
 *	| old r3			|
 *	|-------------------------------|
 *	| old r2			|
 *	|-------------------------------|
 * sp->	| empty parameter slot		|
 *	|_______________________________|
 *
 * The "empty parameter slot" is a simple optimization the compiler uses to
 * avoid overhead in its generated parameter cleanup code after a function
 * has been called.  Rather than (for example) generating:
 *
 *	mov	foo,-(sp)	/ push parameter
 *	jsr	pc,bar		/ call function
 *	tst	(sp)+		/ throw away parameter
 *
 * The compiler always keeps one empty slot on the stack and generates:
 *
 *	mov	foo,(sp)	/ pass parameter
 *	jsr	pc,bar		/ call function
 *
 * The savings are not quite so dramatic when more than one parameter is
 * passed, but still worth the effort.  If the function has any local stack
 * variables, space for them will have be be allocated by the function thereby
 * "moving" the empty parameter slot down.
 */

/*
 * KERNEP and SUPERVISOR (network code) notes:
 *
 * Stack checking can be enabled for the KERNEL (and SUPERVISOR network)
 * by defining CHECKSTACK in the config file.  Looks to see if the
 * kernel and supervisor (network) stacks have collided, etc.
 *
 * Networking kernel doesn't currently need overlays (and probably never
 * will).  If it's ever necessary to overlay the networking code, it would
 * be done in exactly the same manner as that used by the kernel.  This would
 * actually end up simplifying things ...
 *
 * It's debatable whether or not the standard library, KERNEL, and
 * SUPERVISOR copies of csv/cret should be integrated this way, because
 * the ifdef's sometimes make it hard to keep track of things.  But the
 * code and comments here are complicated enough that it's worth the effort
 * to keep all three versions in general sync.
 */
#include "DEFS.h"

#ifndef SUPERVISOR		/* networking kernel doesn't use overlays */
#ifdef KERNEL
#include "../machine/mch_iopage.h"
#include "../machine/koverlay.h"
#endif

.data
#ifdef KERNEL
.globl	ova, ovd		/ overlay descriptor tables
#endif
.globl	__ovno			/ currently mapped overlay
__ovno:	0
.text

.globl	_etext			/ used by cret to determine returns to overlay
				/ area (any return address higher than _etext)
				/ the loader defines only if referenced

/*
 * The loader (/bin/ld) generates `thunks' for functions loaded in overlays.
 * A thunk resides in the base segment and takes the name _foo which is the
 * function's interface to the outside world (this allows pointers to functions
 * to work even if functions are located in overlays).  The function itself is
 * renamed to ~foo.
 *
 * ovhndlr(1-9,a-f) are called from the thunks via a jsr r5 after r1 is set to
 * the location of the first instruction in the subroutine after the call to
 * csv (~foo+4).
 *
 *	_foo:	mov	$~foo+4,r1
 *		jsr	r5,ovhndlr`n'
 *
 * The ovhndlr`n' in turn move the requested overlay number into r0 and
 * branch to ovhndlr which sets the overlay, simulates a csv and transfers to
 * (r1) (~foo+4).  Thus, the function's call to csv is bypassed.
 */
#define	ovh(x, n)	.globl	ovhndlr/**/x; \
		ovhndlr/**/x: \
			mov	$n,r0; \
			br	ovhndlr;

ovh(1,001);	ovh(2,002);	ovh(3,003);	ovh(4,004)
ovh(5,005);	ovh(6,006);	ovh(7,007);	ovh(8,010)
ovh(9,011);	ovh(a,012);	ovh(b,013);	ovh(c,014)
ovh(d,015);	ovh(e,016);	ovh(f,017)

#ifndef KERNEL
emt	= 0104000		/ overlays switched by emulator trap
				/ overlay number is placed in r0
#endif KERNEL

/*
 * ovhndlr(ov::r0, ~foo+4::r1, _foo+8::r5)
 *
 * Ovhandler builds the new stack frame as fast as possible to avoid problems
 * with getting interrupted halfway through.  if we get interrupted between the
 * "jsr r5,ovhndlr`x'" and the "mov sp,r5" below and a longjmp is attempted,
 * rollback will fail utterly.  If we get interrupted before we finish saving
 * registers reserved for register variables and an attempt is made to longjmp
 * to the caller of our service routine rollback will incorrectly pull `saved'
 * register values from the incomplete stack frame leading to
 * non-deterministic behavior.
 *
 * There's very little that can be done about this asside from changing the
 * entire stack frame sequence which involves changes to the compiler, loader,
 * this file and any other routine (like rollback) which `knows' how the C
 * stack frame is put together ...
 */
ovhndlr:
#if defined(KERNEL) && defined(CHECKSTACK)
	cmp	sp, $_u		/ before the u. area?
	blo	9f		/   yes, assume it's remapped, so OK
	cmp	sp, $KERN_SBASE	/ falling off the bottom?
	bhi	9f		/   no, also OK
	spl	7		/ lock out interrupts
	halt			/ can't 'panic', that uses the stack!
	br	.		/ in case continue is done
9:
#endif
	mov	sp,r5		/ simulate a csv ...
	mov	__ovno,-(sp)
	cmp	r0,(sp)		/ is the requested overlay already mapped?
	beq	1f
#ifdef KERNEL
	mov	PS,-(sp)	/ save PS
	SPL7
	mov	r0,__ovno	/ set new overlay number
	asl	r0		/ compute descriptor index and map
	mov	ova(r0), OVLY_PAR / the new overlay in
	mov	ovd(r0), OVLY_PDR
	mov	(sp)+,PS	/ restore PS, unmask interrupts
#else
	emt			/   no, ask the kernel for it ...
	mov	r0,__ovno	/     and indicate the new overlay
	/*
	 * SPECIAL NOTE: the pair of instructions "emt" and "mov r0,__ovno"
	 * form a race condition.  A signal can interrupt the sequence
	 * causing all sorts of nastiness if the signal routine calls a
	 * routine in the old overlay or is itself in the old overlay ...
	 * There is no solution that doesn't involve changing the way
	 * an overlay switch from the kernal.  Most [potential] solutions
	 * significantly increase the overhead of an overlay switch.
	 * A tenable solution has yet to be found ...
	 */
#endif
1:
	mov	r4,-(sp)
	mov	r3,-(sp)
	mov	r2,-(sp)
	jsr	pc,(r1)		/ jsr leaves empty parameter slot on stack
#endif /* !SUPERVISOR */


/*
 * Csv for routines called directly (in base or intra-overlay calls).
 * no overlays have been changed, so we just save the previous overlay
 * number on the stack.  Note that r0 isn't set to the current overlay
 * because we weren't called through a thunk.
 */
.globl	csv
csv:
#if (defined(KERNEL) || defined(SUPERVISOR)) && defined(CHECKSTACK)
#ifdef SUPERVISOR
	cmp	sp,$NET_STOP	/ out of the top of the stack?
	bhi	8f		/   yes, die
	cmp	sp,$NET_SBASE	/ falling off the bottom?
	bhi	9f		/   no, so OK
8:
	spl	7		/ lock out interrupts
	halt			/ can't 'panic', that uses the stack!
	br	.		/ in case continue is done
9:
#else
	cmp	sp,$_u		/ before the u. area?
	blo	9f		/ assume it was remapped, so OK
	cmp	sp,$KERN_SBASE	/ falling off the bottom?
	bhi	9f		/ no, so OK
	spl	7		/ lock out interrupts
	halt			/ can't 'panic', that uses the stack!
	br	.		/ in case continue is done
9:
#endif
#endif
	mov	r5,r1		/ save transfer address,
	mov	sp,r5
#ifdef SUPERVISOR
	clr	-(sp)
#else
	mov	__ovno,-(sp)
#endif
	mov	r4,-(sp)
	mov	r3,-(sp)
	mov	r2,-(sp)
	jsr	pc,(r1)

/*
 * Cret is used by everyone so it has to check if we're returning to an
 * overlay different from the one that may be currently mapped.
 */
.globl	cret
cret:
#if (defined(KERNEL) || defined(SUPERVISOR)) && defined(CHECKSTACK)
#ifdef SUPERVISOR
	cmp	sp,$NET_STOP	/ out of the top of the stack?
	bhi	8f		/   yes, die
	cmp	sp,$NET_SBASE	/ falling off the bottom?
	bhi	9f		/   no, so OK
8:
	spl	7		/ lock out interrupts
	halt			/ can't 'panic', that uses the stack!
	br	.		/ in case continue is done
9:
#else
	cmp	sp,$_u		/ before the u. area?
	blo	9f		/ assume it was remapped, so OK
	cmp	sp,$KERN_SBASE	/ falling off the bottom?
	bhi	9f		/ no, so OK
	spl	7		/ lock out interrupts
	halt			/ can't 'panic', that uses the stack!
	br	.		/ in case continue is done
9:
#endif
#endif
	mov	r5,r2
#ifdef SUPERVISOR
	tst	-(r2)		/ skip over overlay slot
#else
	mov	-(r2),r4	/ r4 = old __ovno - if non-zero we've started
	bne	2f		/   using overlays so we'll have to make
				/   sure the old overlay is mapped if we're
				/   returning to the overlay area
1:
#endif
	mov	-(r2),r4	/ restore registers, reset stack, pop frame
	mov	-(r2),r3	/   pointer and return
	mov	-(r2),r2
	mov	r5,sp		/ (more interrupt problems here *sigh* ...)
	mov	(sp)+,r5
	rts	pc
#ifndef SUPERVISOR
2:
	/*
	 * Not returning to base segment, so check that the right
	 * overlay is mapped in, and if not change the mapping.
	 */
	cmp	r4,__ovno	/ current overlay same as old overlay?
	beq	1b		/   lucked out!
#if	defined(KERNEL) && defined(INET)
	cmp	2(r5),$Kretu	/ must always restore overlays if returning
	beq	3f		/   from SKcall
#endif
	cmp	2(r5),$_etext	/ returning to base segment?
	blos	1b		/   sometimes things *do* work out ...
#ifdef KERNEL
3:
	mov	PS,-(sp)	/ (sigh) returning to a different overlay
	SPL7
	mov	r4,__ovno	/ set new overlay number
	asl	r4		/ and map the new overlay in
	mov	ova(r4), OVLY_PAR
	mov	ovd(r4), OVLY_PDR
	mov	(sp)+,PS	/ restore PS, unmask interrupts
	br	1b
#else
	mov	r0,r3		/ (sigh) returning to a different overlay -
	mov	r4,r0		/   have to save r0 because we can't trash
	emt			/   a function result and ask UNIX to switch
	mov	r4,__ovno	/   the old overlay in
	mov	r3,r0		/ note that as with ovhndlr the pair "emt"
	br	1b		/   "mov r4,__ovno" can be interrupted
#endif
#endif /* !SUPERVISOR */
