/*
 * This is the driver for the shared memory RCI device. It basically just 
 * provides a connect to interrupt for a dual port shared memory board
 * connecting a Q-bus with another bus, made by BIT 3 Inc.
 *
 * This driver is cloned directly from the template UNIX RCI driver
 * in ../rciext/rci_template.c.
 */

#include "shm.h"
#if NSHM > 0

#include "../h/param.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/proc.h"
#include "../h/errno.h"
#include "../rciext/kacrash.h"
#include "../rciext/kacomm.h"
#include "../rciext/rci.h"
#include "../rciext/rciioctl.h"

#include "../machine/pte.h"
#include "../h/map.h"
#include "../h/buf.h"
#include "../vaxuba/ubareg.h"
#include "../vaxuba/ubavar.h"

#include "../vaxuba/shmreg.h"

#include "../rciext/rciioctl.h"	/* to get Qbus virtual address */

/* This takes a unit number as an argument and tells whether it is an	 */
/*  RCI exclusive-open unit or not.  You may want to change this if the	 */
/*  unit number for your device already has magic bits.			 */

#define RCI_UNIT_P(unit) ((unit)&0x80)

/* This complements the above definition.  It takes a unit number and	 */
/*  returns the "real" unit number.  This may be called regardless of	 */
/*  whether or not RCI_UNIT_P is true of the unit number.		 */

#define RCI_REAL_UNIT(unit) ((unit)&0x7f)

#define NSHMSOFT	4	/* no. of soft units per hard unit */

RCIINFO shmrciinfo[NSHM*NSHMSOFT];
unsigned short *shmintrreg[NSHM];
int shmintrAddr[NSHM];
int     shmprobe(), shmattach(), shmintr(), shm_timeout();
struct  uba_device *shmdinfo[NSHM*NSHMSOFT];
u_short shmstd[] = { 0 };
struct  uba_driver shmdriver = 
    { shmprobe, 0, shmattach, 0, shmstd, "shm", shmdinfo };

shmprobe(reg, ui)
caddr_t reg;
struct uba_device *ui;
{
	register int br, cvec;
	register int i;

	for (i=0;i<NSHM*NSHMSOFT;i++)
	 { RCI_cleari(&shmrciinfo[i]);
	 }
	RCI_init();
	if (ui->ui_flags == 0)
	 { printf("Missing vector configured on shm%d\n",ui->ui_unit);
	   return(0);
	 }
	switch (ui->ui_unit)
	 { case 0:
	    { shmintrAddr[ui->ui_unit] = 0x60000;
	      shmintrreg[ui->ui_unit] = NULL;
	      break;
	    }
	   default:
	    { printf ("No address known for shm unit %d\n", ui->ui_unit);
	      return(0);
	    }
	 }
        br = 0x17;			/* Set IPL of the device */
	cvec = ui->ui_flags;		/* This better be right! */
	return (sizeof(struct shmdevice));
}

shmattach()
{	
	return(0);	
}

shmopen(dev,flag)
dev_t dev;
int flag;
{
	int unit = RCI_REAL_UNIT(minor(dev));
	int exclusive = RCI_UNIT_P(minor(dev));
	int hardunit = unit/NSHMSOFT;
	struct uba_device *ui = shmdinfo[hardunit];

	if (unit < 0 || unit >= NSHM*NSHMSOFT || !ui || !ui->ui_alive)
	 { return(ENXIO);
	 }

	/* set up shmintrreg if necessary */

	if (!shmintrreg[hardunit])
	 { struct RCI_interface intf;

	   RCI_init();
	   RCI_get_interface(&intf);
	   shmintrreg[hardunit] = (unsigned short*)(intf.Qbus + 
						    shmintrAddr[hardunit]);
	   printf ("shmintrreg[%d] <= 0x%x for Qbus 0x%x\n",
		    hardunit, shmintrreg[hardunit], shmintrAddr[hardunit]);
	 }
	   
	if (exclusive)
	 { int s;
	   if (! RCI_alive())
	    { return(ENXIO);
	    }
	   s = splhigh();
	   if (shmrciinfo[unit].flags & RCI_OPEN)
	    { splx(s);
	      return(EBUSY);
	    }
	   shmrciinfo[unit].flags |= RCI_OPEN|RCI_BUSY;
	   shmrciinfo[unit].pid = u.u_procp->p_pid;
	   splx(s);
	 }
	else
	 { int s;
	   s = splhigh();
	   if (shmrciinfo[unit].flags & RCI_BUSY)
	    { splx(s);
	      return(EBUSY);
	    }
	   shmrciinfo[unit].flags |= RCI_OPEN;
	   splx(s);
	 }
	return(0);
}

shmclose(dev,flag)
dev_t dev;
int flag;
{
	int unit = RCI_REAL_UNIT(minor(dev));
	struct uba_device *ui = shmdinfo[unit/NSHMSOFT];

	if (unit < 0 || unit >= NSHM*NSHMSOFT || !ui || !ui->ui_alive)
	 { return(ENXIO);
	 }
	RCI_closei(&shmrciinfo[unit]);
	return (0);
}

shmioctl(dev,cmd,data,flag)
dev_t dev;
int cmd;
caddr_t data;
int flag;
{
	int unit = RCI_REAL_UNIT(minor(dev));
	int exclusive = RCI_UNIT_P(minor(dev));
	struct uba_device *ui = shmdinfo[unit/NSHMSOFT];
	int error = 0;
	int s;

	if (unit < 0 || unit >= NSHM*NSHMSOFT || !ui || !ui->ui_alive)
	 { return(ENXIO);
	 }
	switch (cmd)
	 {
	   default:
	      error = ENOTTY;
	      break;
	   case RCIIOC_CONFIRM:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      break;
	   case RCIIOC_ATTACH:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCIINFO *rcii;
		 struct RCI_fxn_arg *ap;
		 rcii = &shmrciinfo[unit];
		 ap = (struct RCI_fxn_arg *) data;
		 s = splhigh();
		 RCI_setup_switch(rcii);
		 rcii->ifxn = ap->function;
		 rcii->iarg = ap->argument;
		 /* you may want to turn interrupts on at this point */
		 splx(s);
	       }
	      break;
    /* Execute a routine */
	   case RCIIOC_EXECUTE:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCIINFO *rcii;
		 struct RCI_fxn_arg *ap;
		 rcii = &shmrciinfo[unit];
		 ap = (struct RCI_fxn_arg *) data;
		 if (RCI_kut_call(ap->function,ap->argument,rcii))
		  { RCI_signal(rcii->pid,rcii->crashsig);
		  }
	       }
	      break;
	   case RCIIOC_KRNLEXEC:
	    { struct RCI_fxn_arg4 *ap;

	      if (! RCI_UNIT_P(unit))
	       { error = ENOTTY;
	       }
	      else
	       { ap = (struct RCI_fxn_arg4 *)data;
		 ap->rval = (*ap->fxn)(ap->arg[0],ap->arg[1],
				       ap->arg[2],ap->arg[3]);
	       }
	      break;
	    }
	   case RCIIOC_KUTEXEC:
	    { struct RCI_fxn_arg4 *ap;
	      extern int RCI_kutexec();

	      if (! RCI_UNIT_P(unit))
	       { error = ENOTTY;
	       }
	      else
	       { kut_callit (RCI_kutexec, (int)data);
	       }
	      break;
	    }
    /* Detach from the device interrupt */
	    case RCIIOC_DETACH:
	       if (! exclusive)
		{ error = ENOTTY;
		} 
	       else
		{ s = splhigh();
		  shmrciinfo[unit].ifxn = 0;
		/* you may want to turn interrupts off here */
		  splx(s);
		}
	       break;
	   /* Request signals from the slave task */
	   case RCIIOC_REQSIGS:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { s = splhigh();
		 shmrciinfo[unit].flags |= RCI_SIGS;
		 splx(s);
	       }
	      break;
	  /* Request a heartbeat */
	   case RCIIOC_HEARTBEAT:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { register RCIINFO *rcii;
		 register struct RCI_heartbeat *ap;
		 int s;
		 int RCI_heartbeat();
		 rcii = &shmrciinfo[unit];
		 ap = (struct RCI_heartbeat *) data;
		 s = splhigh();
		 RCI_setup_switch(rcii);
		 untimeout(RCI_heartbeat,(caddr_t)rcii);
		 rcii->hbticks = ap->howoften;
		 rcii->hbfxn = ap->function;
		 rcii->hbarg = ap->argument;
		 if ((rcii->hbticks > 0) && rcii->hbfxn)
		  { timeout(RCI_heartbeat,(caddr_t)rcii,rcii->hbticks);
		  }
		 splx(s);
	       }
	      break;
	   /* Set up crash signal */
	   case RCIIOC_ONCRASH:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { s = splhigh();
		 shmrciinfo[RCI_REAL_UNIT(unit)].crashsig = * (int *) data;
		 shmrciinfo[unit = * (int *) data;
		 splx(s);
	       }
	      break;
	   /* Get crash info */
	   case RCIIOC_GETCRASH:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCIINFO *rcii;
		 rcii = &shmrciinfo[unit];
		 error = copyout((caddr_t)&rcii->lastcrash,
			   	 (caddr_t)*(CRASHINFO**)data, 
				  sizeof(CRASHINFO));
		 rcii->lastcrash.type = CI_NULL;
		 splx(s);
	       }
	      break;
	   /* Get interface information */
	   case RCIIOC_INTERFACE:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCI_get_interface((struct RCI_interface *)data);
	       }
	      break;
	   case RCIIOC_TIMEOUT:
	      if (! exclusive)
	       { error = ENOTTY;
	       }
	      else
	       { RCI_setup_timeout( &shmrciinfo[unit],
				    (struct RCI_timeout *)data );
	       }
	      break;
	 }
	return(error);
 }
 
shmintr(hardunit)
int hardunit;
{
	register RCIINFO *p;
	register int i;
     	struct shmdevice *shmaddr;
	int unit, s;

	shmaddr = (struct shmdevice*)shmdinfo[hardunit]->ui_addr;

	/* translate the hard unit number into a soft unit no. */

	shmaddr->remnodeCmd = SHM_CLEARPRIT;

	s = splhigh();

	if (!shmintrreg[hardunit])
	 { printf ("stray shm interrupt on unit %d (inactive)\n",hardunit);
	   splx(s);
	   return;
	 }
	unit = -1;
	for (i=0; i<NSHMSOFT; i++)
	 { if (shmintrreg[hardunit][i])
	    { 
	      shmintrreg[hardunit][i] = 0;
	      unit = hardunit*NSHMSOFT + i;

	      p = &shmrciinfo[unit];
	      if (p->flags & RCI_BUSY)
	       { if (p->ifxn)
		  { if (RCI_switch(p,p->ifxn,p->iarg))
		     { RCI_signal(p->pid,p->crashsig);
		       p->ifxn = 0;
		     }
		  }
	       }
	      else
	       { printf ("stray shm interrupt on soft unit %d\n", unit);
	       }
	    }
	 }
	if (unit == -1)
	 { printf ("stray shm interrupt on unit %d\n", hardunit);
	 }
	splx(s);
}

#endif
