/*-=-=-=-=-=-=-=-=-=-=-=- RCCL/RCI Notice of Copyright -=-=-=-=-=-=-=-=-=-=-*
 *									    *
 *	Copyright (C) 1987, 1991 by John E. Lloyd.			    *
 *									    *
 *	Information on copyright permissions, as well as a complete	    *
 *	disclaimer of warranty, is given in the file COPYRIGHT.		    *
 *									    *
 *-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/

#ifndef VXWORKS_CFG
#include <sys/file.h>
#include <errno.h>
#endif
#include <strings.h>

#include "../h/IOdevice.h"
#include "../h/errorCodes.h"
#include "../rci/link.h"

extern int errno;
extern char *copyofstr();

#if !defined(VXWORKS_CFG) && !defined(sgi)
#define USE_LOCK_FILES 1
#else
#define USE_LOCK_FILES 0
#endif

typedef struct _UNITDESC {
	struct _UNITDESC *next;
	struct _UNITDESC *prev;

	int address;
	char *addrStr;
	int unit;
	int externlock;
	int internlock;
	int refcount;
	int fd;
	int lock;
} UNITDESC;

typedef struct _ATTACH {
	struct _ATTACH *next;
	struct _ATTACH *prev;
	int access;
	struct _UNITDESC *unitd;	
} ATTACH;

extern IO_DRIVER_SPEC IOdrivers[];
extern int numIOdrivers;

static detachDevice();
static openLockFile();

static ATTACH *attachList = NULL;
static UNITDESC *drivertab = NULL;
static int initDone = 0;
static doInit()
{
	int i;

	if (!drivertab)
	 { if ((drivertab = (UNITDESC*)malloc(
		   	     sizeof(UNITDESC)*numIOdrivers)) == NULL)
	    { return (-1);
	    }
	   for (i=0; i<numIOdrivers; i++)
	    { drivertab[i].prev = &drivertab[i];
	      drivertab[i].next = &drivertab[i];
	      drivertab[i].unit = -1;
	    }
	 }
	if (attachList)
	 { deallocAllDevices();
	 }
	else if ((attachList = ALLOC(ATTACH)) == NULL)
	 { return (-1);
	 }
	attachList->next = attachList->prev = attachList;
	requestFlagInit(&initDone);
	initDone = 1;
	return(1);
}

allocIOdevice (dev, devAddr, access)
IO_DEVICE *dev;
struct devAddr *devAddr;
int access;
{
	/* instantiates an I/O device structure with the information
	   which corresponds to a particular name/address pair.

	   The name/address pair is locked as per the specification
	   int the 'access' mask.
	 */

	int driverNum;
	IO_DRIVER_SPEC *spec;
	ATTACH *attach;
	UNITDESC *head;
	UNITDESC *unitd;
	int lock;
	int fd;
	int k;
		
	char *pfx = "allocIOdevice() --";

	if (!initDone)
	 { if (doInit() < 0)
	    { errorMsg (EMemory,"%s can't initialize -- out of memory\n",pfx);
	      return (-1);
	    }
	 }

	/* -- get the driver number and search to see if unit is defined -- */

	if ((driverNum = getDriverSpec(&spec, devAddr->name)) < 0)
	 { errorMsg (ENotDefined, "%s driver %s not defined\n", pfx, 
		     devAddr->name);
	   return (-1);
	 }
	head = &drivertab[driverNum];
	for (unitd = head->next; unitd != head; unitd = unitd->next)
	 { if (devAddr->address == unitd->address &&
	       (devAddr->addrStr == NULL || 
		strcmp (devAddr->addrStr, unitd->addrStr) == 0))
	    { break;
	    }
	 }

	/* ---- there is no unit, so add one ---- */
	
	if (unitd == head)
	 { int unit = unitd->prev->unit + 1;

	   if ((unitd = ALLOC (UNITDESC)) == NULL)
	    { errorMsg (EMemory, "%s out of memory\n", pfx);
	      return (-1);
	    }
	   unitd->unit = unit;
	   unitd->address = devAddr->address;
	   unitd->addrStr = copyofstr(devAddr->addrStr);
	   unitd->refcount = 0;
	   unitd->externlock = 0;
	   unitd->internlock = 0;
	   unitd->fd = UNDEF;
	   unitd->lock = 0;

	   LINK (unitd, head);
	 }

	/* ---- check for internal locks ---- */

	if (unitd->internlock)
	 { return (0);
	 }
	if (!(access & DEV_INTERNAL) && unitd->refcount > 0)
	 { return (0);
	 }
	
	/* ---- allocate attach structure ---- */

	if ((attach = ALLOC(ATTACH)) == NULL)
	 { errorMsg (EMemory, "%s out of memory\n", pfx);
	   return (-1);	
	 }	

	/* ---- open a lock file for the device if necessary ---- */

#if USE_LOCK_FILES
	if (unitd->fd == UNDEF)
	 { if ((fd = openLockFile (devAddr)) < 0)
	    { errorMsg (getErrorCode(0),
			"%s error trying to open lock file\n", pfx);
	      free(attach); return(-1);
	    }
	   unitd->lock = 0;
#if TEST
	   printf ("opening lock file ...\n");
#endif
	 }
	else
	 { fd = unitd->fd;
	 }

	/* ---- change the lock if necessary ---- */

	if (!(access & DEV_EXTERNAL) || unitd->externlock)
	 { lock = LOCK_EX;
	 }
	else
	 { lock = LOCK_SH;
	 }
	if (lock != unitd->lock)
	 { int retcode;
	   if (flock (fd, LOCK_NB | lock) < 0)
	    { if (errno == EWOULDBLOCK)
	       { retcode = 0;
	       }
	      else
	       { sysErrorMsg (EBadAccess, pfx, "flock");
		 errorMsg (EBadAccess, "%s can't flock device %s,0x%x:%s\n",
			   pfx, devAddr->name, devAddr->address, 
				devAddr->addrStr);
		 retcode = -1;
	       }
	      free (attach);
	      if (unitd->fd == UNDEF)
	       { close (fd);
#if TEST
	   printf ("closing lock file ...\n");
#endif
	       }
	      return (retcode);
	    }
#if TEST
	   printf ("lock set to %s\n", lock == LOCK_SH ? "SHARE" : "EXCLUDE");
#endif
	 }
#endif
	
	STRNCPY (dev->name, devAddr->name, DEVICE_NAME_SIZE);

	dev->location  = NULL;
	dev->unit      = unitd->unit;
	dev->address   = devAddr->address;
	dev->addrStr   = unitd->addrStr;
	
	for (k=0; k<MAX_DEVARGS; k++)
	 { dev->args[k] = devAddr->args[k];
	 }

	dev->error     = 0;
	dev->byteCount = 0;
	dev->timeout   = 1;
	dev->size      = 0;

	dev->read      = spec->read;
	dev->testRead  = spec->testRead;
	dev->write     = spec->write;
	dev->testWrite = spec->testWrite;

	dev->probe     = spec->probe;
	dev->attach    = spec->attach;
	dev->detach    = spec->detach;
	dev->signal    = spec->signal;

	dev->ioctl     = spec->ioctl;

	if (!(access & DEV_EXTERNAL))
	 { unitd->externlock++;
	 }
	if (!(access & DEV_INTERNAL))
	 { unitd->internlock++;
	 }
	unitd->refcount++;
#if USE_LOCK_FILES
	if (unitd->fd == UNDEF)
	 { unitd->fd = fd;
	 }
#endif
	unitd->lock = lock;

	attach->unitd = unitd;
	attach->access = access;
	LINK (attach, attachList);

	return ((int)attach);
}

deallocIOdevice (handle)
int handle;
{
	ATTACH *attach;

	if (!attachList)
	 { return (0);
	 }

	/* --- find the specified device --- */

	for (attach=attachList->next; attach!=attachList; attach=attach->next)
	 { if (attach == (ATTACH *)handle)
	    { break;
	    }
	 }
	if (attach == attachList)
	 { return (0);
	 }

	/* -- and detach it -- */

	detachDevice (attach);
	UNLINK (attach);
	free (attach);	
	return (1);
}

static detachDevice(attach)
ATTACH *attach;	
{
	UNITDESC *unitd;

	unitd = attach->unitd;
	
	if (--unitd->refcount == 0)	/* decrement the reference count */
	 { 
#if USE_LOCK_FILES
	   close (unitd->fd);
	   unitd->fd = UNDEF;
#endif
	 }
#if USE_LOCK_FILES
	if (!(attach->access & DEV_EXTERNAL))
	 { if (--unitd->externlock == 0 && unitd->fd != UNDEF)
	    { unitd->lock = LOCK_SH;
	      flock (unitd->fd, LOCK_SH | LOCK_NB);
	    }
	 }
#endif
	if (!(attach->access & DEV_INTERNAL))
	 { --unitd->internlock;
	 }
}

static openLockFile (devAddr)
struct devAddr *devAddr;
{
	/* Open a lock file for a particular device/address pair. */

#if USE_LOCK_FILES
#define FNS	256
	char fileName[FNS];	
	char *rcclDir;
	int fd;

	char *pfx = "openLockFile() --";

	if ((rcclDir = (char *)getenvRCCL()) == NULL)
	 { errorMsg (EEnviron,
	   "%s environment variable RCCL not set\n", pfx);
	   return (-1);
	 }
	if (devAddr->addrStr)
	 { sprintf (fileName, "%s/lock/%s%d,%s", rcclDir, devAddr->name, 
			   devAddr->address, devAddr->addrStr);
	 }
	else
	 { sprintf (fileName, "%s/lock/%s%d", rcclDir, devAddr->name, 
			   devAddr->address);
	 }
	if ((fd = open (fileName, O_CREAT | O_WRONLY, 0666)) < 0)
	 { errorMsg (EBadAccess,
	   "%s can't open lock file %s, errno %d\n", pfx, fileName, errno);
	   return (-1);
	 }
	fchmod (fd, 0666);
	return (fd);	   
#endif
}


getDriverSpec (spec, name)
IO_DRIVER_SPEC **spec;
char *name;
{
	register int i;
	
	for (i=0; i<numIOdrivers; i++)
	 { if (strcmp (IOdrivers[i].name, name) == 0)
	    { *spec = &IOdrivers[i];
	      return (i);
	    }
	 }
	return (-1);
}

deallocAllDevices()
{
	/* called to close down and deallocate all devices */

	ATTACH *attach;
	ATTACH *attach_next;

	if (!attachList)
	 { return;
	 }
	for (attach=attachList->next; attach!=attachList; attach=attach_next)
	 { attach_next = attach->next;
	   detachDevice (attach);
	   FREE (attach);
	 }
}

getIOinterrupt (intr, devAddr, vector, ipl)
IO_INTERRUPT *intr;
struct devAddr *devAddr;
int vector, ipl;
{
	/* instantiates an I/O interrupt structure with the information
	   which corresponds to a particular name/address pair.
	 */

	int driverNum;
	IO_DRIVER_SPEC *spec;
	int k;
		
	char *pfx = "getIOinterrupt() --";

	/* -- get the driver number and search to see if unit is defined -- */

	if ((driverNum = getDriverSpec(&spec, devAddr->name)) < 0)
	 { errorMsg (ENotDefined, 
"%s driver %s not defined\n", pfx, devAddr->name);
	   return (-1);
	 }
	if (!spec->intrAttach || !spec->intrHandler)
	 { errorMsg (ENotDefined, 
"%s driver %s doesn't have attach and/or handler fxns\n", pfx, devAddr->name);
	   return (-1);
	 }
	STRNCPY (intr->name, devAddr->name, DEVICE_NAME_SIZE);

	intr->location  = NULL;
	intr->address   = devAddr->address;
	for (k=0; k<MAX_DEVARGS; k++)
	 { intr->args[k] = devAddr->args[k];
	 }
	intr->ipl         = ipl;
	intr->vector      = vector;
	intr->intrAttach  = spec->intrAttach;
	intr->intrHandler = spec->intrHandler;

	return (1);
}

#if TEST

static unsigned char *rciIOpage = 0;

static rciBadAddr (x)
{
	return (1);
}

#define	COMMAND(s)	(strcmp (key_buf, s) == 0)
#define	COMMANDS(s)	(strncmp (key_buf, s, strlen(s)) == 0)
char key_buf [256];

main()
{
	char *keytree, *tree_match_parse(), *tree_match_err_at();
	char name[256];
	char *address;
	int handle;

	keytree = tree_match_parse("					\
(	quit								\
	alloc 								\
	 (%s", name, ",			\"device name\"			\
	   (%s", &address, ",,,	\"address (string) \"			\
	     (NONE EXTERNAL INTERNAL ALL)))				\
	dealloc								\
	 (%d", &handle, ",,,			\"handle\")		\
	show								\
)",0);
	if (keytree == 0)
	 { printf ("ERROR/Parse error at %.30s\n", tree_match_err_at());
	   exit (-1);
	 }
	do
 	 { IO_DEVICE dev;

	   tree_match (keytree, "IODRIVER> ", key_buf);

	   if (COMMANDS("alloc"))
	    { int status;
	      int access;
	      struct devAddr devAddr;

	      if (COMMAND ("alloc EXTERNAL"))
	       { access = DEV_EXTERNAL;
	       }
	      else if (COMMAND ("alloc INTERNAL"))
	       { access = DEV_INTERNAL;
	       }
	      else if (COMMAND ("alloc ALL"))
	       { access = DEV_ALL;
	       }
	      else
	       { access = 0;
	       }

	      parseAddressString (&devAddr, address);
	      devAddr.name = name;

	      if ((status = allocIOdevice (&dev, &devAddr, access)) < 0)
	       { printErrors();
		 clearErrors();
	       }
	      printf ("status = %d\n", status);
	      if (status)
	       { printf ("dev.unit =      %d\n", dev.unit);
		 printf ("dev.address =   %d\n", dev.address);
		 printf ("dev.addrStr =   %s\n", dev.addrStr);
	       }
	    }
	   else if (COMMAND ("dealloc"))
	    { int status;

	      status = deallocIOdevice (handle);
	      printf ("status = %d\n", status);
	    }
	   else if (COMMAND ("show"))
	    { UNITDESC *unitd;
	      UNITDESC *head;
	      int i;
	
	      for (i=0; i<numIOdrivers; i++)
	       { printf ("%s\t", IOdrivers[i].name);
		 head = &drivertab[i];
		 for (unitd = head->next; unitd != head; unitd = unitd->next)
		  { printf ("UNIT %d at %d,%s",unitd->unit, 
					       unitd->address,unitd->addrStr);
		  }
		 putchar ('\n');
	       }
	    }
	 }
	while (!(COMMAND("quit")));
}

#include <strings.h>
#include <ctype.h>

parseAddressString (devAddr, str)
struct devAddr *devAddr;
char *str;
{
	char *cp;
	char *lp;

	devAddr->address = 0;
	devAddr->addrStr = NULL;

	/* if all digits, just set the number */
	cp = str;
	while (isdigit(*cp))
	 { cp++;
	 }
	lp = cp;
	if (lp != str && (*lp == '\0' || *lp == ','))
	 { devAddr->address = atoi(str);
	 }
	if (*lp != '\0')
	 { devAddr->addrStr = lp;
	 }	
}

#endif
