/*-=-=-=-=-=-=-=-=-=-=-=- RCCL/RCI Notice of Copyright -=-=-=-=-=-=-=-=-=-=-*
 *									    *
 *	Copyright (C) 1991 by John E. Lloyd.				    *
 *									    *
 *	Information on copyright permissions, as well as a complete	    *
 *	disclaimer of warranty, is given in the file COPYRIGHT.		    *
 *									    *
 *-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*---------------------------------------------------------------------------
 *		
 * MODULE NAME:	sharedMemory
 *
 * DESCRIPTION:	Routines to set up the shared memory regions between
 *		RCI task CPUs and allocate space from these regions.
 *
 * CAVEATS:	The allocation provided by these routines is permanent;
 *		i.e., the memory cannot be given back except by either
 *		exiting the program or calling rciClose().
 * 
 * AUTHOR/DATE:	John Lloyd, Fall 1989
 *
 * MODIFICATION HISTORY:
 *
 *--------------------------------------------------------------------------*/

#include "../h/cdefs.h"
#include "../h/rciConfig.h"
#include "../h/errorCodes.h"
#include "../h/assignMem.h"

#include "../rci/sharedMemory.h"

struct exportInfo {
	int present;		/* region has been set up */
	int size;		/* size of the region */
	int qbhandle;		/* (Qbus export address) */
	char *arbAddr;		/* address of importing region on CPU */
	char *auxImportAddr;	/* base address of aux import areas */
	char *upperMark;	/* allocation pointer at the top */
	char *lowerMark;	/* allocation pointer at the bottom */
};

extern SECTION_DESC sharedMemTable[][MAX_CPUS];

static struct exportInfo export[MAX_CPUS];
static int pagesize;
static int firstAuxCpu = UNDEF;
static int arbCpuExported;

static int initDone = 0;
#define INIT_CHECK()	if (!initDone) doInit();
static doInit()
{
	initDone = 1;
	firstAuxCpu = UNDEF;	
	arbCpuExported = 0;
	bzero ((char*)export, sizeof(export));
	requestFlagInit(&initDone);
}

/* ----- Error Messages. Defined externally to save space ----- */

static char *nullMsg		= "%s --\n";
static char *alreadyCalledMsg	= "%s -- function called twice\n";
static char *badSizeMsg		= "%s -- illegal size (%d) requested\n";
static char *cantAllocMemMsg	= "%s -- can't allocate memory on cpu %d\n";
static char *notSetupMsg	= "%s -- shared mem. not set up on cpu %d\n";
static char *cantOpenCpuMsg	= "%s -- can't open cpu %d\n";
static char *cantLoadCpuMsg	= "%s -- can't load cpu %d\n";
static char *illegalAddrMsg	= "%s -- bad address given: 0x%x\n";
static char *cpuIsBusyMsg	= "%s -- cpu %d is busy\n";

#define AUX_IMPORT_BASE	((char*)0x100000)

computeExportSize (cpu)
int cpu;
{
	/* Decide how big to make the exported global section for a particular
	   CPU. To do this intelligently, we should read back from that
	   CPU how much memory is left, but we don't have any code to do 
	   the just yet.
	 */

	extern int cpuExportSizes[];

	return (cpuExportSizes[cpu]);
}

void *getImportAddress (importCpu, exportCpu, size)
int importCpu;
int exportCpu;
int size;
{
	/* Called by setupSharedRegions(). Compute the address
	   at which global memory being exported by 'exportCpu'
	   will be imported by 'importCpu'. 
	 */

	char *fxnName = "getImportAddress()";
	char *mem;

	if (importCpu == ARBITER_CPU)
	 { if (localMemoryAlloc (ARBITER_CPU, &mem, size) < 0)
	    { errorMsg (getErrorCode(0), nullMsg, fxnName);
	      return (NULL);
	    }
	   export[exportCpu].upperMark = mem + size;
	   export[exportCpu].lowerMark = mem;
	   export[exportCpu].arbAddr = mem;
	 }
	else
	 { if (export[importCpu].auxImportAddr == NULL)
	    { export[importCpu].auxImportAddr = AUX_IMPORT_BASE;
	    }
	   mem = export[importCpu].auxImportAddr;
	   export[importCpu].auxImportAddr += size;
	 }
	sharedMemTable[importCpu][exportCpu].low = (void*)mem;
	sharedMemTable[importCpu][exportCpu].high = (void*)(mem + size);
	return ((void*)mem);
}

sharedRegionsCreated (cpu)
int cpu;
{
	INIT_CHECK();

	return (export[cpu].present);
}

setupSharedRegions (cpu)
int cpu;
{
	/* Set the global memory regions on the indicated cpu, and
	   import them to all other CPUs. Called the first time 
	   shared memory is requested from a partiuclar CPU.
	 */

	char *fxnName = "setupSharedRegions()";

	char *mem;			
	int handle;
	int size;
	int status;
	int k;

	INIT_CHECK();

	if (export[cpu].present)
	 { return (0);
	 }
	size = computeExportSize (cpu);

	if ((status = openCpu(cpu)) < 0)
	 { errorMsg (getErrorCode(0), cantOpenCpuMsg, fxnName, cpu);
	   return (-1);
	 }
	if (status == 0)
	 { errorMsg (EBusy, cpuIsBusyMsg, fxnName, cpu);
	   return (-1);
	 }
	if (cpu != ARBITER_CPU)
	 { if (openCpu(ARBITER_CPU) < 1)
	    { errorMsg (getErrorCode(0), cantOpenCpuMsg, fxnName,ARBITER_CPU);
	      return (-1);
	    }
	   if (loadCpu (cpu) < 0)
	    { errorMsg (getErrorCode(0), cantLoadCpuMsg, fxnName, cpu);
	      return (-1);
	    }
	 }
	if (localMemoryAlloc (cpu, &mem, size) < 0)
	 { errorMsg (getErrorCode(0), nullMsg, fxnName);
	   return (-1);
	 }
	export[cpu].size = size;
	sharedMemTable[cpu][cpu].low = (void*)mem;
	sharedMemTable[cpu][cpu].high = (void*)(mem + size);

	if (cpu == ARBITER_CPU)
	 { export[cpu].arbAddr = mem;
	   export[cpu].upperMark = mem + size;
	   export[cpu].lowerMark = mem;
	 }

	if (export[ARBITER_CPU].present && !arbCpuExported)
	 { if ((handle = exportMemory (ARBITER_CPU, 
				       export[ARBITER_CPU].arbAddr,
				       export[ARBITER_CPU].size, cpu)) < 0)
	    { errorMsg (getErrorCode(0), nullMsg, fxnName);
	      return (-1);
	    }
	   export[ARBITER_CPU].qbhandle = handle;
	 }

	if (cpu != ARBITER_CPU || firstAuxCpu != UNDEF)
	 { int exportCpu;

	   exportCpu = (firstAuxCpu != UNDEF) ? firstAuxCpu : cpu;
	   if ((handle = exportMemory (cpu, (void*)mem, size, exportCpu)) < 0)
	    { errorMsg (getErrorCode(0), nullMsg, fxnName);
	      return (-1);
	    }
	   export[cpu].qbhandle = handle;
	 }

	/* Now have the CPU import memory from all other CPUS, and
	   have all other CPUs import the new memory */

	for (k=0; k<MAX_CPUS; k++)
	 { if (export[k].present)
	    { void *addr;

	      if ((addr = getImportAddress (cpu, k, export[k].size)) == NULL)
	       { errorMsg (getErrorCode(0), nullMsg, fxnName);
		 return (-1);
	       }
	      if (importMemory (cpu, export[k].qbhandle, 
				     export[k].size, addr) < 0)
	       { errorMsg (getErrorCode(0), nullMsg, fxnName);
		 return (-1);
	       }

	      if ((addr = getImportAddress (k, cpu, size)) == NULL)
	       { errorMsg (getErrorCode(0), nullMsg, fxnName);
		 return (-1);
	       }
	      if (importMemory (k, export[cpu].qbhandle, size, 
				   addr) < 0)
	       { errorMsg (getErrorCode(0), nullMsg, fxnName);
		 return (-1);
	       }
	    }
	 }
	if (cpu != ARBITER_CPU && firstAuxCpu == UNDEF)
	 { firstAuxCpu = cpu;
	 }
	export[cpu].present = 1;
	return (1);
}

closeSharedRegions()
{
	/* Called by rciClose() to put everything away */

	int k;

	for (k=0; k<MAX_CPUS; k++)
	 { export[k].present = 0;
	 }
}

void *allocSharedMemLower (cpu, size)
int cpu;
int size;
{
	/* Allocate size bytes of global memory from the bottom
	   of the shared memory space for the indicated 'cpu'. */

	char *fxnName = "allocSharedMemLower()";
	char *mem;
	int asize;

	INIT_CHECK();

	if (!export[cpu].present)
	 { if (setupSharedRegions (cpu) < 0)
	    { errorMsg (getErrorCode(0), nullMsg, fxnName);
	      return (NULL);
	    }
	 }
	if ((asize = ALIGN_SIZE(size)) < 0)
	 { errorMsg (EInternal, badSizeMsg, fxnName, asize);
	   return (NULL);
	 }
	if ((mem = export[cpu].lowerMark) + asize > export[cpu].upperMark)
	 { errorMsg (EMemory, cantAllocMemMsg, fxnName, cpu);
	   return (NULL);
	 }
	export[cpu].lowerMark += asize;
	return ((void*)mem);
}

deallocSharedMemLower (cpu, size)
int cpu;
int size;
{
	/* A bit of a hack function that lets us put back shared
	   memory that we JUST allocated. This gets in cases where
	   a function allocates some memory, fails for another reason,
	   and then would like to give the memory back. Obviously,
	   it is assumed this function is accessed by only one task
	   at a time (which is OK since global memory is only allocated
	   by the planning task).
	 */

	char *fxnName = "deallocSharedMemLower()";
	int asize;

	INIT_CHECK();

	if (!export[cpu].present)
	 { errorMsg (EInternal, notSetupMsg, fxnName, cpu);
	   return (-1);
	 }
	if ((asize = ALIGN_SIZE(size)) < 0 ||
	    export[cpu].lowerMark - asize < export[cpu].arbAddr)
	 { errorMsg (EInternal, badSizeMsg, fxnName, asize);
	   return (-1);
	 }
	export[cpu].lowerMark -= asize;
	return (1);
}

void *allocSharedMemUpper (cpu, size)
int cpu;
int size;
{
	/* Allocate size bytes of global memory from the top
	   of the shared memory space for the indicated 'cpu'. */

	char *fxnName = "allocSharedMemUpper()";
	char *mem;
	int asize;

	INIT_CHECK();

	if (!export[cpu].present)
	 { if (setupSharedRegions (cpu) < 0)
	    { errorMsg (getErrorCode(0), nullMsg, fxnName);
	      return (NULL);
	    }
	 }
	if ((asize = ALIGN_SIZE(size)) < 0)
	 { errorMsg (EInternal, badSizeMsg, fxnName, asize);
	   return (NULL);
	 }
	if ((mem = export[cpu].upperMark - asize) < export[cpu].lowerMark)
	 { errorMsg (EMemory, cantAllocMemMsg, fxnName, cpu);
	   return (NULL);
	 }
	export[cpu].upperMark -= asize;
	return ((void*)mem);
}

deallocSharedMemUpper (cpu, size)
int cpu;
int size;
{
	/* Counterpart to deallocSharedMemLower() */

	char *fxnName = "deallocSharedMemUpper()";
	int asize;

	INIT_CHECK();

	if (!export[cpu].present)
	 { errorMsg (EInternal, notSetupMsg, fxnName, cpu);
	   return (-1);
	 }
	if ((asize = ALIGN_SIZE(size)) < 0 || 
	    export[cpu].upperMark + asize > 
	    export[cpu].arbAddr + export[cpu].size)
	 { errorMsg (EInternal, badSizeMsg, fxnName, asize);
	   return (-1);
	 }
	export[cpu].upperMark += asize;
	return (1);
}
