/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */

#ifndef lint
static char    *rcsid = "$Header:pcifmove.c 12.0$";
#endif



#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <dos.h>
#include <fcntl.h>
#include <io.h>
#include <sys\types.h>
#include "pcparam.h"
#include "rb.h"
#include "bios.h"
#include "loadall.h"
#include "vars.h"



extern char    *rbrcsid;

extern struct loadall_area far *loadall_area;


#ifdef DEBUG
extern struct defaults defaults[];
#endif /* DEBUG */

struct slop
{
	char            buff[2];
}               slop;


/*
 * These structures are used in the pcif_move/block_move routines.
 * WARNING: The BIOS block move function is dependent on these
 *          structures being declared in this order, DO NOT
 *          add/remove or otherwise mess with the declaration!
 *
 * See the BIOS listing on INT 15 for a description of how this works.
 *
 */

struct descriptor_block
{
	struct DESC     dummy;
	struct DESC     gdtseg;
	struct DESC     src;
	struct DESC     tgt;
	struct DESC     bioscs;
	struct DESC     stk;
}               desc_block =
{
	                0
};



#define NUMDESC 6
#define CPLO_EO 0x9b		/* Current Privilege Level Execute only */
#define CPLO_RW 0x93		/* Current Privilege Level Read/Write */

/*************************************************************************
 * movein - Copy data from Coprocessor to PC.
 *
 *           Front end to the pcifmove function.
 *
 */
int movein(unixaddr, pcaddr, count)
	u_long          unixaddr;
	char far       *pcaddr;
	u_short         count;
{
	register int    rc;


#ifdef DEBUG
	if (*dbugptr & MOVDEBUG)
		printf("movein: pcaddr = %#lx, unixaddr = %#lx, count = %#x\n",
		       pcaddr, unixaddr, count);
#endif /* DEBUG */

	cur_cbcb->read_count++;
	cur_cbcb->read_bytes += count;
	unixaddr &= 0x00ffffff;
	if ((unixaddr + count) > cur_cbcb->physmem_bytes)
		return (ERR_BADADDR);

	if ((cpu_type == MODEL_80) || (option_flag & OPTION_BLOCKMOVE))
		rc = bpcifmove(READ, pcaddr, unixaddr, count);
	else
		rc = pcifmove(READ, pcaddr, unixaddr, count);

	/*
	 * Pcifmove only works for counts that are a multiples of 2. If the
	 * request is for an odd number of bytes the length is truncated. To
	 * get that ODD byte transferred we copy it into a local buffer then
	 * copy it to the calling program. 
	 */

	if ((count & 1) && (rc >= 0))
	{
		printf("Odd Count sent to movein!\n");
		unixaddr += count - 1;
		if ((cpu_type == MODEL_80) || (option_flag & OPTION_BLOCKMOVE))
			rc = bpcifmove(READ, (char far *) &slop, unixaddr, 2);
		else
			rc = pcifmove(READ, (char far *) &slop, unixaddr, 2);
		if (rc >= 0)

			pcaddr[count] = slop.buff[0];
	}
	return (rc);
}

/*************************************************************************
 * moveout - Copy data from the PC to coprocessor memory.
 *
 *           Front end to the pcifmove function.
 *
 *
 *       WARNING ! WARNING ! WARNING!  WARNING!  WARNING!
 *
 *   The BIOS block move routine only knows how to transfer data in
 *   even byte quantities. If you call us with an ODD count there is
 *   a possibility that Unix memory could be CORRUPTED! To minimize
 *   the chance we simulate a read-modify-write (SLOW), however if Unix
 *   changes the data between our read and our write the data will
 *   be corrupted.
 *
 *
 */

int moveout(unixaddr, pcaddr, count)
	u_long          unixaddr;
	char far       *pcaddr;
	u_short         count;
{
	int             rc;


#ifdef DEBUG
	if (*dbugptr & MOVDEBUG)
		printf("moveout: pcaddr = %#lx, unixaddr = %#lx, count = %#x\n",
		       pcaddr, unixaddr, count);
#endif /* DEBUG */

	cur_cbcb->write_count++;
	cur_cbcb->write_bytes += count;
	unixaddr &= 0x00ffffff;
	if ((unixaddr + count) > cur_cbcb->physmem_bytes)
		return (ERR_BADADDR);

	if ((cpu_type == MODEL_80) || (option_flag & OPTION_BLOCKMOVE))
		rc = bpcifmove(WRITE, pcaddr, unixaddr, count);
	else
		rc = pcifmove(WRITE, pcaddr, unixaddr, count);

	/*
	 * Pcifmove only works for counts that are a multiples of 2. If the
	 * request is for an odd number of bytes the length is truncated. To
	 * get that ODD byte transferred AND not corrupt storage we read the
	 * data first then do the write for the last byte. 
	 */

	if ((count & 1) && (rc >= 0))
	{
		printf("Odd Count sent to moveout!\n");
		unixaddr += count - 1;
		if ((cpu_type == MODEL_80) || (option_flag & OPTION_BLOCKMOVE))
			rc = bpcifmove(READ, (char far *) &slop, unixaddr, 2);
		else
			rc = pcifmove(READ, (char far *) &slop, unixaddr, 2);
		if (rc >= 0)
		{
			slop.buff[0] = pcaddr[count];
			if ((cpu_type == MODEL_80) || 
					     (option_flag & OPTION_BLOCKMOVE))
				rc = bpcifmove(WRITE, pcaddr, unixaddr, 2);
			else
				rc = pcifmove(WRITE, pcaddr, unixaddr, 2);
		}
	}
	return (rc);
}

/*************************************************************************
 * pcifmove - Move data in and out of PC and Romp memory.
 *
 *    Since the coprocessor is addressed above 1MB we need to use
 *    the LOADALL block move function to address it. This routine is
 *    a front end to that machine instruction.
 *
 *    WARNING: This routine only works on data transfers that have an even
 *             length. You should  move data in and out of coprocessor
 *             memory by calling the movein(), or moveout(), functions.
 *
 */

static int
pcifmove(op, pcaddr, rompaddr, length)
	int             op;
	char far       *pcaddr;
	u_long          rompaddr;
	u_short         length;
{

	register struct LADES far *src = &loadall_area->t_dsdes;
	register struct LADES far *tgt = &loadall_area->t_esdes;
	register struct LADES far *csdes = &loadall_area->t_csdes;
	register struct LADES far *ssdes = &loadall_area->t_ssdes;
	register struct LADES far *idt = &loadall_area->t_idtdes;
	register struct LAGDT far *gdt = &loadall_area->t_gdtdes;
	register u_short far *cs = &loadall_area->t_cs;
	register u_short far *es = &loadall_area->t_es;
	register u_short far *ds = &loadall_area->t_ds;
	register u_short far *ss = &loadall_area->t_ss;

	int             i;
	u_short         codeseg;/* Segment register */
	u_long          raddr;	/* PC address that maps to rompaddr */
	u_long          paddr;	/* pcaddr as a linear address        */


#ifdef DEBUG
	u_short         ldebug = *dbugptr & BMDEBUG;
#endif /* DEBUG */


	DEBUGF(ldebug, printf("pcifmove: op = %#x, pcaddr = %#lx, rompaddr = %#lx, len = %#x\n",
			      op, pcaddr, rompaddr, length));

	/*
	 * Zero out the descriptors (Required by BIOS) 
	 */

	src->Access = CPLO_RW;	/* source access byte */
	tgt->Access = CPLO_RW;	/* target access byte */
	idt->Limit = 0xffff;	/* initialize idt descriptor limit to max */
	gdt->gdt_Limit = 0xffff;/* initialize the gdt descriptor limit to max */


	/*
	 * Set up the PCIF window for the transfer. 
	 */

	raddr = set_window(0, rompaddr);
	paddr = physaddr((u_long) pcaddr, (long) 0, (long) 0);

	if (op == WRITE)
	{
		DEBUGF(ldebug, printf("\tpcifmove: WRITE pc %#lx (%#lx), romp %#lx (%#lx)\n",
				      pcaddr, paddr, rompaddr, raddr));

		src->LoWord = LOWORD((u_long) paddr);
		src->HiByte = LOBYTE(HIWORD((u_long) paddr));
		src->Limit = 0xffff;
		tgt->LoWord = LOWORD(raddr);
		tgt->HiByte = LOBYTE(HIWORD(raddr));
		tgt->Limit = 0xffff;


		/*
		 * PCIF Buffered Writes If the transfer length is a multiple
		 * of 4, then turn buffering on, else turn it off. 
		 */

		if (length & 3)
		{

			/* Not a muttiple of 4, turn buffering off */

			RSETBIT(cur_cbcb->port+P_CTRL, P_BUF1);
			RSETBIT(cur_cbcb->port+P_CTRL, P_BUF2);
		} else
		{
			/* Is a muttiple of 4, turn buffering ON */

			SETBIT(cur_cbcb->port+P_CTRL, P_BUF1);
			SETBIT(cur_cbcb->port+P_CTRL, P_BUF2);
		}

	} else
	{

		DEBUGF(ldebug, printf("\tpcifmove: READ pc %#lx (%#lx), romp %#lx (%#lx)\n",
				      pcaddr, paddr, rompaddr, raddr));

		src->LoWord = LOWORD(raddr);
		src->HiByte = LOBYTE(HIWORD(raddr));
		src->Limit = 0xffff;
		tgt->LoWord = LOWORD((u_long) paddr);
		tgt->HiByte = LOBYTE(HIWORD((u_long) paddr));
		tgt->Limit = 0xffff;

	}
#ifdef DEBUG
	if (ldebug)
	{
		printf("\t     Src: basel = %#x, baseh = %#x, len = %#x\n",
		       src->LoWord, src->HiByte, src->Limit);
		printf("\t     Tgt: basel = %#x, baseh = %#x, len = %#x\n",
		       tgt->LoWord, tgt->HiByte, tgt->Limit);
	}
#endif /* DEBUG */

/* Setup LoadAll area */

	codeseg = getcs();	/* get code segment */
	*cs = codeseg;		/* setup segment registers */
	*es = codeseg;
	*ds = codeseg;

	csdes->LoWord = (u_short) codeseg << 4;	/* setup cs descriptor */
	csdes->HiByte = (char) (codeseg >> 12);
	csdes->Access = CPLO_EO;
	csdes->Limit = 0xffff;

	codeseg = getss();	/* get stack segment */
	*ss = codeseg;		/* setup stack segment */

	ssdes->LoWord = (u_short) codeseg << 4;	/* setup ss descriptor */
	ssdes->HiByte = (char) (codeseg >> 12);
	ssdes->Access = CPLO_RW;
	ssdes->Limit = 0xffff;

	i = loadall(length / 2);/* word transfer */
	return (i);


}


/*************************************************************************
 * bpcifmove - Move data in and out of PC and Romp memory.
 *
 *    Since the coprocessor is addressed above 1MB we need to use
 *    the BIOS block move function to address it. This routine is
 *    a front end to that BIOS function.
 *
 *    WARNING: This routine only works on data transfers that have an even
 *             length. You should  move data in and out of coprocessor
 *             memory by calling the movein(), or moveout(), functions.
 *
 */

int bpcifmove(op, pcaddr, rompaddr, length)
	int             op;
	char far       *pcaddr;
	u_long          rompaddr;
	u_short         length;
{

	register struct DESC *src = &desc_block.src;
	register struct DESC *tgt = &desc_block.tgt;

	struct DESC far *gdt = (struct DESC far *) & desc_block.dummy;
	int             i;
	char           *cptr;
	u_long          raddr;	/* PC address that maps to rompaddr */
	u_long          paddr;	/* pcaddr as a linear address        */


#ifdef DEBUG
	u_short         ldebug = *dbugptr & BMDEBUG;
#endif /* DEBUG */


	DEBUGF(ldebug, printf("bpcifmove: op = %#x, pcaddr = %#lx, rompaddr = %#lx, len = %#x\n",
			      op, pcaddr, rompaddr, length));

	/*
	 * Zero out the descriptors (Required by BIOS) 
	 */

	cptr = (char *) &desc_block;
	for (i = 0; i < sizeof(struct descriptor_block); i++)
		*cptr++ = 0;


	src->acc = CPLO_RW;
	tgt->acc = CPLO_RW;

	/*
	 * Set up the PCIF window for the transfer. 
	 */

	raddr = set_window(0, rompaddr);
	paddr = physaddr((u_long) pcaddr, (long) 0, (long) 0);

	if (op == WRITE)
	{
		DEBUGF(ldebug, printf("\tbpcifmove: WRITE pc %#lx (%#lx), romp %#lx (%#lx)\n",
				      pcaddr, paddr, rompaddr, raddr));


		src->basel = LOWORD((u_long) paddr);
		src->baseh = LOBYTE(HIWORD((u_long) paddr));
		src->lmt = length;
		tgt->basel = LOWORD(raddr);
		tgt->baseh = LOBYTE(HIWORD(raddr));
		tgt->lmt = length;


		/*
		 * PCIF Buffered Writes If the transfer length is a multiple
		 * of 4, then turn buffering on, else turn it off. 
		 */

		if (length & 3)
		{

			/* Not a muttiple of 4, turn buffering off */

			RSETBIT(cur_cbcb->port+P_CTRL, P_BUF1);
			RSETBIT(cur_cbcb->port+P_CTRL, P_BUF2);
		} else
		{
			/* Is a muttiple of 4, turn buffering ON */

			SETBIT(cur_cbcb->port+P_CTRL, P_BUF1);
			SETBIT(cur_cbcb->port+P_CTRL, P_BUF2);
		}

	} else
	{

		DEBUGF(ldebug, printf("\tbpcifmove: READ pc %#lx (%#lx), romp %#lx (%#lx)\n",
				      pcaddr, paddr, rompaddr, raddr));
		src->basel = LOWORD(raddr);
		src->baseh = LOBYTE(HIWORD(raddr));
		src->lmt = length;
		tgt->basel = LOWORD((u_long) paddr);
		tgt->baseh = LOBYTE(HIWORD((u_long) paddr));
		tgt->lmt = length;
	}
#ifdef DEBUG
	if (ldebug)
	{
		printf("\t     Src: basel = %#x, baseh = %#x, len = %#x\n",
		       src->basel, src->baseh, src->lmt);
		printf("\t     Tgt: basel = %#x, baseh = %#x, len = %#x\n",
		       tgt->basel, tgt->baseh, tgt->lmt);
	}
#endif /* DEBUG */

	i = blockmove(gdt, (length / 2));
	return (i);
}



/*
 * This routine is used to set up a window into the coprocessors memory.
 *
 * PC to ROMP windows must reside on a 64K boundary in ROMP memory.
 * To set up a window in ROMP memory we take the high order 8 bits
 * of the ROMP address and use it to set up the window. The remaining
 * 16 bits represent the offset from the base needed address the data.
 * These 16 bits are added to the PC ADDRESS that maps in the PCIF and
 * returned to the calling function.
 *
 * For example:
 *             AT Memory Size 1 MB  (so pcif is addressed 1MB - 1.25 MB)
 *             ROMP addr to write to  0x123456
 *             We want to use the first window
 *
 *             To get at this address we need to set the window at 0x12
 *             and to access it we must us the PC address  0x103456
 *             (pcif address of first window + offset of ROMP addr)
 *
 * Note: At present the PC code has entire control of these windows.
 *       To avoid any boundary problems (a transfer spans a single
 *       window) we set the next window to be contigeous. A problem
 *       will arise if MEMBLOCK is ever increased above 64K (it is
 *       not likley that Unix could ever request a single request
 *       this large)
 *
 * Warning: This routine does not do any boundary checking. As mentioned
 *          above we alaways set up 2 windows. This gives the programmer
 *          128K - rval  of contigueous Coprocessor address space. However this
 *          is only done when using windows 0,1 and 2 (since 3 is the
 *          last window the limit is (64K - rval))
 */

static u_long
set_window(window, rompaddr)
	int             window;
	u_long          rompaddr;
{
	u_long          rval;
	u_short         wval = LOBYTE(HIWORD(rompaddr));

	if (window > 3)		/* Value check window, only 0-3 valid */
		return (-1);

	DEBUGF(*dbugptr & BMDEBUG, printf("\tset_window: Set window %d to rompaddr 0x%lx\n", window, (long) rompaddr));

	IOOUT(cur_cbcb->port + window, wval);

	/*
	 * Set up the next window. 
	 */
	if (window < 3)
		IOOUT(cur_cbcb->port + window + 1, wval + 1);

	rval = (u_long) cur_cbcb->window + LOWORD(rompaddr);

	DEBUGF(*dbugptr & BMDEBUG, printf("\tset_window: wval = 0x%x, pcaddr = 0x%lx\n", wval, (long) rval));
	return (rval);



}
