/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

# include "proc.h"

RCSID("$Id: dev_proc.c 472 2001-03-15 14:36:02Z hbb $")


/*
 * Processor internal registers
 */
enum {
	Reg_PSW		= 017777776,	/* processor status word */
	Reg_PIRQ	= 017777772,	/* programmed interrupt request */
	Reg_CPUErr	= 017777766,	/* cpu error register */
	Reg_LTC		= 017777546,	/* line time clock */
	Reg_Maint	= 017777750,	/* maintenance register */
	Reg_CSW		= 017777570,	/* console switch */
};

typedef struct idata idata_t;
struct idata {
	u_int	timerreq;	/* pending timer interrupts */
	u_int	maxreq;		/* maximum pending requests */
	u_int	maxpend;
	u_short	csw_switch;
	u_short	csw_led;
	int	csw_enable;
	int	pwrfail;
	u_int	clock_rate;	/* msecs per clock tick */

	u_int	cpu_options;	/* settable per option */
	int	no22;		/* pretend 18 bit only		*/
};


static void	proc_ctrl_create(iodev_t *, int, char **);
static void	proc_ctrl_complete(iodev_t *);
static void	proc_reset(iodev_t *);
static u_short	proc_fetch(iodev_t *, u_int);
static void	proc_store(iodev_t *, u_int, mmode_t, u_short);
static u_short	proc_vector(iodev_t *);
static void	proc_info(iodev_t *);

static void 	proc_setreq(iodev_t *);


static int	procc_info(iodev_t *dev, int argc, char **argv);
static int	procc_csw(iodev_t *dev, int argc, char **argv);
static int	procc_maxreq(iodev_t *dev, int argc, char **argv);

iocmd_t proc_cmds[] = {
	{ "?",		"[command ...]",	dev_help },
	{ "help",	"[command ...]",	dev_help },
	{ "info",	"",			procc_info },
	{ "csw",	"[on|off|<oct>]",	procc_csw },
	{ "maxreq",	"[num]",		procc_maxreq },
	{ NULL,		NULL,			NULL },
};

ioops_t proc_ops = {
	proc_ctrl_create,	/* ctrl_create */
	0,			/* dev_create */
	proc_ctrl_complete,	/* ctrl_complete */
	proc_reset,		/* reset */
	proc_fetch,		/* fetch */
	proc_store,		/* store */
	proc_vector,		/* vector */
	0,			/* dma */
	0,			/* async */
	proc_info,		/* info */
	0,			/* flush */

	proc_cmds,		/* cmds */
};

opt_t proc_opts[] = {
	{ "cpu_options",	OptUInt,	offsetof(idata_t, cpu_options)},
	{ "csw_enable",		OptFlag,	offsetof(idata_t, csw_enable)},
	{ "csw",		OptUInt,	offsetof(idata_t, csw_switch)},
	{ "maxreq",		OptUInt,	offsetof(idata_t, maxreq)},
	{ "no22",		OptFlag,	offsetof(idata_t, no22)},
	{ NULL, 0, 0}
};

static void	ltc(void *);
static void	pbin(u_short v);


static void
proc_ctrl_create(iodev_t *dev, int argc UNUSED, char **argv UNUSED)
{
	idata_t *ip = dev->data = xalloc(sizeof(idata_t));

	(void)memset(ip, 0, sizeof(idata_t));

	ip->cpu_options = CPU_PWUP_MON;
	ip->csw_enable = 1;
	ip->csw_switch = 0;
	ip->maxreq = 1;
}

/*
 * create processor
 */
static void
proc_ctrl_complete(iodev_t *dev)
{
	idata_t *ip = (idata_t *)dev->data;

	if(dev->instance != 0)
		conf_panic("cannot have more than one processor\n");

	/*
	 * Load power-up values (assume proc is zeroed)
	 */
	proc.pri = 7;
	proc.halt = 0;
	proc.reg[7] = startloc;

	switch(ip->cpu_options & CPU_PWUP_MASK) {

	  case CPU_PWUP_USER:	/* user boot address */
		proc.reg[7] = ip->cpu_options & CPU_USER;
		break;

	  case CPU_PWUP_AUTO:	/* start 173000 (i.e. startloc) */
		break;

	  case CPU_PWUP_MON:	/* enter monitor */
		proc.halt = 1;
		break;

	  case CPU_PWUP_T24:	/* trap to 24, handled in interp.c */
		break;
	}

	proc.reg[6] = 01000;

	/* no UNIBUS adaptor */
	proc.maint = ip->cpu_options;
	proc.maint &= ~(CPU_FPA|CPU_HALT|CPU_UNIBUS|CPU_MOD_MASK);
	proc.maint |= CPU_MOD_KDJ11A | CPU_PWOK;

	/*
	 * Install in iopage
	 */
	proc.iopage[IOP(Reg_PSW   )] = dev;
	proc.iopage[IOP(Reg_CPUErr)] = dev;
	proc.iopage[IOP(Reg_PIRQ  )] = dev;
	proc.iopage[IOP(Reg_LTC   )] = dev;
	proc.iopage[IOP(Reg_Maint )] = dev;
	proc.iopage[IOP(Reg_CSW   )] = dev;

	proc.no22 = ip->no22;

	ip->clock_rate = 1000 / globopts.clock_rate;
	if(ip->clock_rate == 0)
		conf_panic("clock_rate too high: %u", globopts.clock_rate);

	register_timer(ip->clock_rate, ltc, dev);
}


/*
 * line clock handle, called with processor 'device'
 */
static void
ltc(void *d)
{
	idata_t	*ip = ((iodev_t *)d)->data;
	static int warned;

	if(!proc.bevent && (proc.ltc & 0100)) {
		if(ip->timerreq < ip->maxreq) {
			warned = 0;
			ip->timerreq++;
			if(ip->timerreq > ip->maxpend)
				ip->maxpend = ip->timerreq;
		} else if(warn == 0) {
			warned = 1;
			printf("timer tick(s) lost\n");
		}
		proc_setreq((iodev_t *)d);
	}
}

/*
 * reset signal
 */
static void
proc_reset(iodev_t *dev)
{
	proc.pirq = 0;
	proc.ltc = 0;
	dev->reqpri = 0;
	((idata_t *)dev->data)->timerreq = 0;
	((idata_t *)dev->data)->maxpend = 0;
}

/*
 * fetch value from register
 */
static u_short
proc_fetch(iodev_t *dev, u_int e)
{
	idata_t *ip = dev->data;
	u_short v;

	switch(e) {
	case Reg_PSW:
		v = ((proc.c&1)<<0) | ((proc.v&1)<<1) | ((proc.z&1)<<2) | ((proc.n&1)<<3)
		  | (proc.t<<4) | (proc.pri << 5) | (proc.regs << 11)
		  | (proc.prevmode << 12) | (proc.curmode << 14);
		break;

	case Reg_CPUErr:	v = proc.cpuerr; 	break;
	case Reg_PIRQ:		v = proc.pirq;		break;
	case Reg_LTC:		v = proc.ltc;		break;
	case Reg_Maint:		v = proc.maint;		break; 
	case Reg_CSW:		v = ip->csw_switch;	break;

	default:
		warn("proc_fetch(%u)", e);
		Trap4(020);
	}	

	return v;
}

/*
 * load internal registers, do with care
 * Note:
 *	the T-bit in the psw can be set only by rti and vectors
 *	but no by direct moves. So don't set/clear it here but do
 *	it at the appropriate places manually.
 */
static void
proc_store(iodev_t *dev, u_int pa, mmode_t mode, u_short v)
{
	idata_t *ip = dev->data;
	u_short old;

	switch(pa) {

	case Reg_PSW:
		/*
		 * set all, expect unused and T-bit
		 */
		if(mode == M_Word || mode == M_High) {
			int newmode = (v >> 14) & 3, regs = (v >> 11) & 1;
			switchmode(newmode, regs);
			proc.curmode = newmode;
			proc.prevmode = (v >> 12) & 3;
			proc.regs = regs;
		}
		if(mode == M_Word || mode == M_Low) {
			proc.c = (v >> 0) & 1;
			proc.v = (v >> 1) & 1;
			proc.z = (v >> 2) & 1;
			proc.n = (v >> 3) & 1;
			SETPRI((v >> 5) & 07);
		}
		break;

	case Reg_CPUErr:
		/*
		 * cleared by write
		 */
		proc.cpuerr = 0; 	
		break;


	case Reg_PIRQ:
		/*
		 * high byte writeable
		 */
		if(mode == M_Low)
			break;
		proc.pirq = v & 0177000;

		proc_setreq(dev);
		break;

	case Reg_LTC:
		/*
		 * Bit 0100 only
		 */
		if(mode == M_High)
			break;
		proc.ltc = v & 0100;
		break;

	case Reg_Maint:
		/*
		 * Not writeable (this says the documentation on the -A)
		 * The -E seems to have the kernel halt bit. We implement it
		 * just because it's easy.
		 */
		if(mode == M_High)
			break;
		proc.maint = (proc.maint & ~CPU_HALT) | (v & CPU_HALT);
		break;

	case Reg_CSW:
		old = ip->csw_led;
		SETWORD(ip->csw_led, mode, v);
		if(old != ip->csw_led)
			printf("CSW: %06ho\n", ip->csw_led);
		break;

	default:
		warn("proc_store(%u)", pa);
		Trap4(020);
	}	
}

/*
 * return vector
 */
static u_short
proc_vector(iodev_t *dev)
{
	int pirq = (proc.pirq >> 1) & 7;
	idata_t *id = (idata_t *)dev->data;

	if(id->pwrfail) {
		id->pwrfail = 0;
		proc_setreq(dev);
		return 024;
	}
	if(pirq >= 6)
		return 0240;
	if(id->timerreq) {
		id->timerreq--;
		proc_setreq(dev);
		return 0100;
	}
	if(pirq > 0)
		return 0240;

	warn("bad call to proc_vector");
	return 4;
}

/*
 * set interrupt requests
 */
static void
proc_setreq(iodev_t *dev)
{
	idata_t *ip = dev->data;
	int i, req, pri;

	dev->reqpri = pri = 0;

	if(ip->pwrfail) {
		IRQ(dev, NMIPRI);
		return;
	}

	if(ip->timerreq)
		pri = 6;

	for(i = 0100000, req = 7; i >= 01000; i >>= 1, req--)
		if(proc.pirq & i) {
			proc.pirq = (proc.pirq & 0177000) | (req * 042);
			if(req > pri)
				pri = req;
			break;
		}

	if(pri)
		IRQ(dev, pri);
}

/*
 * assert DCOK
 *
 * There are two cases:
 *	1. real power fail.
 *	   We setup a call to proc_vector, which returns vector 24.
 *	   We start a timer of 1 sec and after that timer expires exit.
 *	2. Other sources of DCOK (for example DEQNA).
 *	   We setup a call to proc_vector, which returns vector 24.
 */
void
proc_pwrfail(int real)
{
	idata_t	*ip = proc.proc->data;

	if(ip->pwrfail)
		return;		/* already power off */

# if 1
	printf("DCOK = %d asserted\n", real);
# endif
	ip->pwrfail = 1;
	proc_setreq(proc.proc);

	if(real) {
		proc.maint &= ~CPU_PWOK;	/* switch off PWOK */
		register_timer(1000, (void (*)(void *))pwrfail_exit, NULL);
	}
}

/*
 * called if power is off
 */
void
pwrfail_exit(void *p UNUSED)
{
/*	printf("power fail exit\n"); */
	exit(0);
}

static int
procc_info(iodev_t *dev, int argc, char **argv UNUSED)
{
	if(argc != 0)
		return 1;
	proc_info(dev);
	return 0;
}


/*
 * enable/disable/set/print switch register
 */
static int
procc_csw(iodev_t *dev, int argc, char **argv)
{
	u_long	vv;
	char	*end;
	idata_t	*ip = dev->data;

	if(argc > 1)
		return 1;
	if(argc == 0) {
		printf("LED    %06o = ", ip->csw_led); pbin(ip->csw_led); putchar('\n');
		printf("SWITCH %06o = ", ip->csw_switch); pbin(ip->csw_switch); putchar('\n');
		printf("CSW %sabled\n", ip->csw_enable ? "en" : "dis");
		return 0;
	}
	if(strcmp(argv[0], "off") == 0) {
		ip->csw_enable = 0;
		printf("CSW disabled\n");
		proc.iopage[IOP(Reg_CSW)] = NULL;
		return 0;
	}
	if(strcmp(argv[0], "on") == 0) {
		ip->csw_enable = 1;
		printf("CSW enabled\n");
		proc.iopage[IOP(Reg_CSW)] = dev;
		return 0;
	}
	vv = strtoul(argv[0], &end, 8);
	if(*end || vv >= 0200000)
		return 1;

	ip->csw_switch = vv;

	return 0;
}

static int
procc_maxreq(iodev_t *dev, int argc, char **argv)
{
	idata_t	*ip = dev->data;

	if(argc == 0)
		printf("%d\n", ip->maxreq);
	else if(argc != 1)
		return 1;
	else {
		u_int n;
		char *end;

		n = strtoul(argv[0], &end, 10);
		if(*end != '\0') {
			printf("bad number '%s'\n", argv[0]);
			return 1;
		}
		if(n == 0) {
			printf("maxreq must be at least 1\n");
			return 1;
		}
		ip->maxreq = n;
		ip->maxpend = 0;
	}
	return 0;
}


static void
pbin(u_short v)
{
	int	i, j;

	for(i = 0; i < 6; i++) {
		printf("|");
		for(j = (i == 0) ? 2 : 0; j < 3; j++, v <<= 1)
			printf("%c", ".o"[(v & 0100000) != 0]);
	}
	printf("|");
}


static void
proc_info(iodev_t *dev)
{
	idata_t *id = dev->data;

	printf("KDJ11A - internal registers\n");
	printf("LTC     %08o: %d timer req. %d max %d max pend %s%s\n",
		Reg_LTC, 
		id->timerreq, id->maxreq, id->maxpend,
		(proc.ltc & 0100) ? "int-enabled  " : "int-disabled  ",
		id->pwrfail ? "pwrfail-init " : "");
	printf("PSW     %08o: cur=%d prev=%d reg=%d pri=%d %c%c%c%c%c\n", Reg_PSW,
		proc.curmode, proc.prevmode, proc.regs, proc.pri,
		".T"[proc.t], ".N"[proc.n], ".Z"[proc.z], ".V"[proc.v], ".C"[proc.c]);
	printf("PIRQ    %08o: %c%c%c%c%c%c%c %o %o\n", Reg_PIRQ,
		".7"[(proc.pirq & 0100000) != 0],
		".6"[(proc.pirq & 0040000) != 0],
		".5"[(proc.pirq & 0020000) != 0],
		".4"[(proc.pirq & 0010000) != 0],
		".3"[(proc.pirq & 0004000) != 0],
		".2"[(proc.pirq & 0002000) != 0],
		".1"[(proc.pirq & 0001000) != 0],
		(proc.pirq >> 5) & 7,
		(proc.pirq >> 1) & 7);
	printf("CPUErr  %08o: %s%s%s%s%s%s\n", Reg_CPUErr,
		(proc.cpuerr & 0200) ? "HALT " : "",
		(proc.cpuerr & 0100) ? "ADDR " : "",
		(proc.cpuerr & 0040) ? "NXM " : "",
		(proc.cpuerr & 0020) ? "IOTO " : "",
		(proc.cpuerr & 0010) ? "YELL " : "",
		(proc.cpuerr & 0004) ? "RED " : "");
	printf("Maint   %08o: %06o %s",
		Reg_Maint, proc.maint, (proc.maint & CPU_PWOK) ? "PWOK " : "");
	switch(proc.maint & CPU_PWUP_MASK) {

	  case CPU_PWUP_T24:
		printf("BOOT_TRAP24 ");
		break;

	  case CPU_PWUP_MON:
		printf("BOOT_MON ");
		break;

	  case CPU_PWUP_AUTO:
		printf("BOOT_173000 ");
		break;

	  case CPU_PWUP_USER:
		printf("BOOT_USER=%06o ", proc.maint & CPU_USER);
		break;
	}
	printf("%s", (proc.maint & CPU_HALT) ? "HALT_TRAP " : "");
	switch(proc.maint & CPU_MOD_MASK) {

	  case CPU_MOD_KDJ11A:
		printf("KDJ11-A ");
		break;

	  case CPU_MOD_KDJ11B:
		printf("KDJ11-B ");
		break;

	  case CPU_MOD_KXJ11C:
		printf("KXJ11-C ");
		break;

	  case CPU_MOD_KDJ11D:
		printf("KDJ11-D ");
		break;

	  case CPU_MOD_KDJ11E:
		printf("KDJ11-E ");
		break;

	  default:
		printf("unknown module!");
		break;
	}

	printf("%s", (proc.maint & CPU_FPA) ? "FPA " : "");
	printf("%s\n", (proc.maint & CPU_UNIBUS) ? "UNIBUS " : "");

	printf("CSW LED %08o: %06o %sabled\n", Reg_CSW, id->csw_led, id->csw_enable ? "en":"dis");
	printf("SWITCH  %08o: %06o\n", Reg_CSW, id->csw_switch);

	device_info();
}
