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

# include "proc.h"
# include <sys/ioctl.h>
# include <netinet/in.h>

RCSID("$Id: mon.c 501 2002-09-04 13:03:28Z hbb $")

# ifndef TCSASOFT
#  define TCSASOFT 0
# endif

/*
 * magic addresses for register access
 */
enum {
	MAXADDR	= 017777777,
	R0, R1, R2, R3, R4, R5,
	R0s, R1s, R2s, R3s, R4s, R5s,
	KSP, USP, SSP, PC,
	MAXREG
};

typedef enum {
	FMT_OCT,
	FMT_ASCII,
	FMT_R50,
} fmt_t;

	
# define LINES	24

static void	raw(void);
static void	noraw(void);
static int	gch(void);
static void	crlf(void);
static void	save_regs(void);
static void	restore_regs(void);
static void	out_registers(void);
static void	out_mmg(void);
static void	out_word(u_int, fmt_t);
static void	out_byte(u_int, fmt_t);
static void	out_space(void);
static void	open_loc(void);
static int	mon_fetch(u_int, int);
static int	mon_store(u_int, int, u_short);
static int	get8(void);
static int	getreg(void);
static void	doinfo(void);
static void	find(int);
static int	pins(FILE *, u_int, int);
static void	preqs(void);
static void	device_command(void);
static void	escape(void);
static void	dump_ins(void);
static void	open_monfile(void);
static int	read_monfile(void);
static int	getstring(char *, int);
static void	save_tty(void);
static iodev_t	*devcomplete(char *);
static size_t	strsub(char *, char *);
static void r50cvt(u_short *val, u_int cnt, char *out);



static int	loc;		/* current location, -1 means none */
static int	monstate;
static FILE	*monfile;
static char	chr;		/* current character */

static int	f_utrace = 0;
static int	first_call = 1;

typedef enum ret_t {
	RET_EXIT,
	RET_ERR,
	RET_SW,
	RET_AGAIN,
} ret_t;

static ret_t func_dollar(void);
static ret_t func_octal(void);
static ret_t func_regs(void);
static ret_t func_fregs(void);
static ret_t func_mmg(void);
static ret_t func_pwrfail(void);
static ret_t func_exit(void);
static ret_t func_start(void);
static ret_t func_proceed(void);
static ret_t func_step(void);
static ret_t func_open(void);
static ret_t func_break(void);
static ret_t func_info(void);
static ret_t func_dcmd(void);
static ret_t func_bg(void);
static ret_t func_bgcont(void);
static ret_t func_find(void);
static ret_t func_flag(void);
static ret_t func_shell(void);
static ret_t func_disass(void);
static ret_t func_help(void);
static ret_t func_copyright(void);
static ret_t func_input(void);
static ret_t func_dump(void);

static struct {
	char	key;
	ret_t	(*func)(void);
} ftab[] = {
	{ '!',	func_shell },
	{ '"',	func_open },
	{ '$',	func_dollar },
	{ '%',	func_open },
	{ '\'',	func_open },
	{ '/',	func_open },
	{ '0',	func_octal }, { '1',	func_octal },
	{ '2',	func_octal }, { '3',	func_octal },
	{ '4',	func_octal }, { '5',	func_octal },
	{ '6',	func_octal }, { '7',	func_octal },
	{ '<',	func_input },
	{ '?',	func_help },

	{ 'D',	func_dump },
	{ 'I',	func_dcmd },
	{ 'R',	func_fregs },
	{ 'T',	func_flag },
	{ 'X',	func_exit },
	{ 'Y',	func_copyright },
	{ 'Z',	func_bgcont },

	{ '\\',	func_open },

	{ 'b',	func_break },
	{ 'd',	func_disass },
	{ 'f',	func_find },
	{ 'g',	func_start },
	{ 'i',	func_info },
	{ 'm',	func_mmg },
	{ 'p',	func_proceed },
	{ 'r',	func_regs },
	{ 's',	func_step },
	{ 't',	func_flag },
	{ 'x',	func_pwrfail },
	{ 'z',	func_bg },
};

# define NFTAB (sizeof(ftab)/sizeof(ftab[0]))

void
monitor(void)
{
	int t = 0;		/* timer flag */
	u_int i;

	if(first_call) {
		save_tty();
		first_call = 0;
	}
	if(!(proc.maint & CPU_PWOK))
		pwrfail_exit(&proc);
	if(nomon) {
		/*
		 * we are running in the background so we have no
		 * monitor. monstate counts how often we were in the
		 * monitor.
		 */
		if(monstate == 0) {
			/*
			 * simulate 173000g
			 */
			Reset();
			proc.reg[7] = 0173000;
			monstate = 1;
		} else if(monstate == 1) {
			/*
			 * back in monitor - exit with powerfail
			 */
			proc_pwrfail(1);
			monstate = 2;
		} else
			exit(0);
		proc.halt = 0;
		return;
	}

	Flush();

	t = stop_timer();
	proc.halt = 0;
	save_regs();

	pins(stdout, proc.reg[7], proc.curmode);
	preqs();

	raw();
	loc = -1;
again:
	putchar('@');
	chr = gch();
sw:
	for(i = 0; i < NFTAB; i++)
		if(ftab[i].key == chr) {
			switch((*ftab[i].func)()) {
			  case RET_AGAIN:
				goto again;
			  case RET_SW:
				goto sw;
			  case RET_ERR:
				goto err;
			  case RET_EXIT:
				goto exit;
			}
		}
  err:
	loc = -1;
	putchar('?');
	crlf();
	goto again;
  exit:
	crlf();
	restore_regs();
	noraw();
	if(t)
		start_timer();
}


static ret_t
func_dollar()
{
	if((loc = getreg()) < 0)
		return RET_ERR;
	return RET_SW;
}

static ret_t
func_octal()
{
	loc = get8();
	return RET_SW;
}

static ret_t
func_regs()
{
	crlf();
	out_registers();
	return RET_AGAIN;
}

static ret_t
func_fregs()
{
# ifdef FPP
	crlf();
	noraw();
	mon_fpp();
	raw();
# else
	noraw();
	printf("no fpp\n");
	raw();
# endif
	return RET_AGAIN;
}

static ret_t
func_mmg()
{
	crlf();
	out_mmg();
	return RET_AGAIN;
}

static ret_t
func_pwrfail()
{
	proc_pwrfail(1);
	return RET_EXIT;
}

static ret_t
func_exit()
{
	crlf();
	noraw();
	exit(0);
	return RET_EXIT;
}

static ret_t
func_start()
{
	Reset();
	proc.reg[7] = (loc >= 0 && loc <= MAXADDR) ? (loc & ~1) : 0;
	return RET_EXIT;
}

static ret_t
func_proceed()
{
	return RET_EXIT;
}

static ret_t
func_step()
{
	proc.halt = 1;
	return RET_EXIT;
}

static ret_t
func_open()
{
	if(loc < 0)
		return RET_ERR;
	open_loc();
	return RET_AGAIN;
}

static ret_t
func_break()
{
	if(loc >= 0200000 || loc < 0)
		return RET_ERR;
	crlf();
	proc.bp = loc;
	return RET_AGAIN;
}

static ret_t
func_info()
{
	doinfo();
	return RET_AGAIN;
}

static ret_t
func_dcmd()
{
	device_command();
	return RET_AGAIN;
}

static ret_t
func_bg()
{
	fflush(stdout);
	noraw();
	kill(getpid(), SIGTSTP);
	raw();
	return RET_AGAIN;
}

static ret_t
func_bgcont()
{
	nomon = 1;
	monstate = 1;
	return RET_EXIT;
}

static ret_t
func_find()
{
	if(loc > MAXADDR || loc < 0)
		return RET_ERR;
	crlf();
	find(loc);
	return RET_AGAIN;
}

static ret_t
func_flag()
{
	char flg;

	switch(flg = gch()) {

	  case 't':
		if(chr == 'T')
			f_utrace = !f_utrace;
		printf("utrace: %s", f_utrace ? "on" : "off");
		break;

	  case 'f':
# ifdef FPP
		if(chr == 'T')
			proc.fpd = !proc.fpd;
		printf("fpp: %sabled", proc.fpd ? "dis" : "en");
# else
		printf("no fpp");
# endif
		break;

	  case 'q':
		if(chr == 'T')
			proc.bevent = !proc.bevent;
		printf("timer %s", proc.bevent ? "on" :  "off");
		break;

	  case 'i':
		if(chr == 'T')
			ill_trace = !ill_trace;
		printf("ill-trace %s", ill_trace ? "on" : "off");
		break;

	  case 'p':
		if(chr == 'T')
			proc.stop10 = !proc.stop10;
		printf("stop on trap 10 %s", proc.stop10 ? "on" : "off");
		break;

	  case 'm':
		if(chr == 'T')
			proc.stop4 = !proc.stop4;
		printf("stop on trap 4 %s", proc.stop4 ? "on" : "off");
		break;

	default:
		putchar('?');
	}
	crlf();
	return RET_AGAIN;
}

static ret_t
func_shell()
{
	crlf();
	noraw();
	escape();
	printf("!\n");
	raw();
	return RET_AGAIN;
}

static ret_t
func_disass()
{
	if(loc > MAXADDR || loc < 0)
		return RET_ERR;
	dump_ins();
	return RET_AGAIN;
}

static ret_t
func_help()
{
	crlf();
	noraw();
	type_help_file(_PATH_MON_HELP);
	raw();
	return RET_AGAIN;
}

static ret_t
func_copyright()
{
	crlf();
	noraw();
	type_help_file(_PATH_COPYING);
	raw();
	return RET_AGAIN;
}

static ret_t
func_input(void)
{
	open_monfile();
	return RET_AGAIN;
}

/*
 * Dump registers and memory
 */
static ret_t
func_dump(void)
{
	char fname[100];
	FILE *fp;
	u_short buf[1000], s;
	u_int i, n, a;

	if(!getstring(fname, sizeof(fname)))
		return RET_ERR;
	if((fp = fopen(fname, "w")) == NULL) {
		printf("%s: %s", fname, strerror(errno));
		crlf();
		return RET_ERR;
	}

# define SAVE(P,N) for(n = 0; n < (N); n++) buf[i++] = P[n];

	i = 0;

	buf[i++] = 1;		/* dump version number */

	SAVE(proc.reg, 8);
	SAVE(proc.ureg, 6);
	SAVE(proc.kreg, 6);
	SAVE(proc.sp, 4);
	buf[i++] = iofetch(017777776);

	buf[i++] = (PHYSMEM >> 16) & 0xffff;
	buf[i++] = PHYSMEM & 0xffff;

	buf[i++] = iofetch(MMG_0);
	buf[i++] = iofetch(MMG_1);
	buf[i++] = iofetch(MMG_2);
	buf[i++] = iofetch(MMG_3);
	buf[i++] = iofetch(MEM_ERROR);
	buf[i++] = iofetch(MEM_CACHE);
	buf[i++] = iofetch(MEM_HITMISS);

	for(n = 0; n < 0100; n += 2)
		buf[i++] = iofetch(MMG_KIPDR0 + n);
	for(n = 0; n < 0100; n += 2)
		buf[i++] = iofetch(MMG_SIPDR0 + n);
	for(n = 0; n < 0100; n += 2)
		buf[i++] = iofetch(MMG_UIPDR0 + n);

# undef SAVE

	for(n = 0; n < i; n++)
		buf[n] = htons(buf[n]);

	if(fwrite(buf, sizeof(u_short), i, fp) != i)
		goto err;

	for(a = 0; a < PHYSMEM; a += 2) {
		s = *proc.memops->addrphys(proc.mmg, a);
		s = GETW(s);
		s = htons(s);
		if(fwrite(&s, sizeof(u_short), 1, fp) != 1)
			goto err;
	}

	fclose(fp);
	return RET_AGAIN;
  err:
	printf("write error on %s: %s", fname, strerror(errno));
	crlf();
	fclose(fp);
	return RET_AGAIN;
}

/*
 * open locations and let the user enter new contents
 */
static void
open_loc(void)
{
	int	val;	/* new value (-1 means none) */
	int	bmode;	/* byte mode if true */
	int	v;	/* current value */
	fmt_t	fmt;	/* output format */

	bmode = (chr == '\\' || chr == '\'');
	if(chr == '/' || chr == '\\')
		fmt = FMT_OCT;
	else if(chr == '\'' || chr == '"')
		fmt = FMT_ASCII;
	else if(chr == '%')
		fmt = FMT_R50;
	else {
		printf("bad char");
		return;
	}

stage1:
	/*
	 * print contents and wait for input
	 */
	if(loc <= MAXADDR && (loc & 1))
		bmode = 1;
	if(loc > MAXADDR)
		bmode = 0;
	if((v = mon_fetch(loc, bmode)) == -1) {
		putchar('?');
		crlf();
		return;
	}
	if(bmode)
		out_byte(v, fmt);
	else
		out_word(v, fmt);
	out_space();

	val = -1;

/* stage2: */
	chr = gch();	

/* stage3: */
	/*
	 * decode user response
	 */
	if(chr >= '0' && chr <= '7')
		val = get8();

	switch(chr) {

	  case '/':	
		if(val >= 0) loc = val; 
		bmode = 0; 
		fmt = FMT_OCT;
		break;

	  case '\\':	
		if(val >= 0) loc = val; 
		if(loc > 0 && loc <= MAXADDR)
			bmode = 1;
		else
			bmode = 0;
		fmt = FMT_OCT;
		break;

	  case '"':	
		if(val >= 0) loc = val; 
		bmode = 0; 
		fmt = FMT_ASCII;
		break;

	  case '%':
		if(val >= 0) loc = val; 
		bmode = 0; 
		fmt = FMT_R50;
		break;


	  case '\'':
		if(val >= 0) loc = val;
		if(loc > 0 && loc <= MAXADDR)
			bmode = 1;
		else
			bmode = 0;
		fmt = FMT_ASCII;
		break;

	  case '\r':	
		if(val >= 0)
			mon_store(loc, bmode, val);
		crlf();
		return;

	  case '\n':
		if(val >= 0)
			mon_store(loc, bmode, val);
		if(loc >= MAXADDR) {
			/*
			 * wrap back to R0
			 */
			if(++loc >= MAXREG)
				loc = R0;
		} else {
			/*
			 * wrap to phys. 0
			 */
			if((loc += bmode ? 1 : 2) >= MAXADDR)
				loc = 0;
		}
		break;

	  case '^':
		if(val >= 0)
			mon_store(loc, bmode, val);
		if(loc >= MAXADDR) {
			/*
			 * wrap to last register (PC)
			 */
			if(--loc == MAXADDR)
				loc = MAXREG-1;
		} else {
			/*
			 * wrap to highest address
			 */
			if((int)(loc -= bmode ? 1 : 2) < 0)
				loc = bmode ? MAXADDR : (MAXADDR & ~1);
		}
		break;

	  case '>':
		if(loc > MAXADDR) {
			putchar('?');
			return;
		}
		if(val >= 0) v = val;
		loc = (int)((loc & ~1) + 2) + ((signed char)(v & 0377) << 1);
		if(loc > MAXADDR || loc < 0) {
			putchar('?');
			return;
		}
		break;

	  default:
		putchar('?');
		return;
	}

	crlf();

	/*
	 * print new location
	 */
	if(loc >= R0 && loc <= R5)
		printf("R%d", loc - R0);
	else if(loc >= R0s && loc <= R5s)
		printf("R%d'", loc - R0s);
	else if(loc >= KSP && loc <= SSP)
		printf("%cSP", "KUS"[loc - KSP]);
	else if(loc == PC)
		printf("PC");
	else
		printf("%08o", loc);

	switch(fmt) {

	  case FMT_OCT:
		putchar("/\\"[bmode != 0]);
		break;

	  case FMT_ASCII:
		putchar("\"'"[bmode != 0]);
		break;

	  case FMT_R50:
		putchar('%');
		break;
	}

	goto stage1;
}

/*
 * fetch value from register or memory.
 * avoid traps.
 * return -1 on trap.
 */
static int
mon_fetch(u_int a, int bmode)
{
	u_short	v;

	if(a >= R0 && a <= R5)
		return proc.ureg[a - R0];
	if(a >= R0s && a <= R5s)
		return proc.kreg[a - R0s];
	if(a == KSP)
		return proc.sp[0];
	if(a == SSP)
		return proc.sp[1];
	if(a == USP)
		return proc.sp[3];
	if(a == PC)
		return proc.reg[7];

	if(a >= 017760000 && a <= 017777776) {
		iodev_t *dev = proc.iopage[IOP(a & ~1)];
		if(!dev || !dev->ioops->fetch)
			return -1;
		v = iofetch(a & ~1);
	} else if(a < proc.physmem) {
		v = *proc.memops->addrphys(proc.mmg, a & ~1);
		v = GETW(v);
	} else
		return -1;

	if(!bmode)
		return v;
	else if(a & 1)
		return (v >> 8) & 0377;
	else
		return v & 0377;
}

/*
 * store value into register or memory.
 * avoid traps
 */
static int
mon_store(u_int a, int bmode, u_short v)
{
	if(a >= R0 && a <= R5)
		proc.ureg[a - R0] = v;
	else if(a >= R0s && a <= R5s)
		proc.kreg[a - R0s] = v;
	else if(a == KSP)
		proc.sp[0] = v;
	else if(a == SSP)
		proc.sp[1] = v;
	else if(a == USP)
		proc.sp[3] = v;
	else if(a == PC)
		proc.reg[7] = v;
	else {
		if(a >= 017760000 && a <= 017777776) {
			iodev_t *dev = proc.iopage[IOP(a & ~1)];
			if(!dev || !dev->ioops->store)
				return -1;
			if(bmode)
				iostoreb(a, v);
			else
				iostore(a & ~1, v);
		} else if(a < proc.physmem) {
			u_short *ov = proc.memops->addrphys(proc.mmg, a & ~1);
			if(bmode) {
				if(a & 1)
					SETHB(*ov, v);
				else
					SETLB(*ov, v);
			} else
				SETW(*ov, v);
		} else {
			return -1;
		}
	}
	return 0;
}


/*
 * switch input to file.
 */
static void
open_monfile()
{
	char	buf[80];

	printf(" ");
	if(!getstring(buf, sizeof(buf)))
		return;
	if((monfile = fopen(buf, "r")) == NULL)
		fprintf(stderr, "p11: %s: %s\n", buf, strerror(errno));
}

/*
 * print processor registers
 */
static void
out_registers(void)
{
	int	i;

	for(i = 0; i < 6; i++)
		printf("R%d =%06ho  ", i, proc.ureg[i]);
	crlf();
	for(i = 0; i < 6; i++)
		printf("R%d'=%06ho  ", i, proc.kreg[i]);
	crlf();
	printf("PC =%06ho  ", proc.reg[7]);
	printf("USP=%06ho  ", proc.sp[3]);
	printf("KSP=%06ho  ", proc.sp[0]);
	printf("SSP=%06ho  ", proc.sp[1]);
	printf("PSW=%06ho  ", iofetch(017777776));
	if(proc.n) putchar('N');
	if(proc.z) putchar('Z');
	if(proc.v) putchar('V');
	if(proc.c) putchar('C');
	crlf();
}

/*
 * print memory management registers
 */
static void
out_mmg(void)
{
	int i;

	printf("MMR: %06ho, %06ho, %06ho, %06ho", 
		iofetch(017777572),
		iofetch(017777574),
		iofetch(017777576),
		iofetch(017772516));
	crlf();

	printf("KERNEL-I     KERNEL-D      ");
	printf("USER-I       USER-D        ");
	printf("SUPERV-I     SUPERV-D"); crlf();
	for(i = 0; i < 8; i++) {
		printf("%05ho %06ho %05ho %06ho  "
		       "%05ho %06ho %05ho %06ho  "
		       "%05ho %06ho %05ho %06ho",
			iofetch(017772300 + 2*i),
			iofetch(017772340 + 2*i),
			iofetch(017772320 + 2*i),
			iofetch(017772360 + 2*i),
			iofetch(017777600 + 2*i),
			iofetch(017777640 + 2*i),
			iofetch(017777620 + 2*i),
			iofetch(017777660 + 2*i),
			iofetch(017772200 + 2*i),
			iofetch(017772240 + 2*i),
			iofetch(017772220 + 2*i),
			iofetch(017772260 + 2*i));
		crlf();
	}
}

static void
out_word(u_int v, fmt_t fmt)
{
	char buf[10];
	u_short s[1];

	switch(fmt) {

	  case FMT_OCT:
		printf("%06o", v);
		break;

	  case FMT_ASCII:
		out_byte(v & 0377, fmt);
		out_byte((v >> 8) & 0377, fmt);
		break;

	  case FMT_R50:
		s[0] = v;
		r50cvt(s, 1, buf);
		printf("%s", buf);
		break;
	}
}

static void
r50cvt(u_short *val, u_int cnt, char *out)
{
	static char r50[050] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.?0123456789";
	u_short v;

	while(cnt--) {
		if((v = *val) > 050 * ((050 * 047) + 047) + 047)
			v = 050 * ((050 * 035) + 035) + 035;

		out[2] = r50[v % 050]; v /= 050;
		out[1] = r50[v % 050]; v /= 050;
		out[0] = r50[v % 050];

		val++;
		out += 3;
	}
	*out = '\0';
}

static void
out_byte(u_int v, fmt_t fmt)
{
	switch(fmt) {

	  case FMT_OCT:
		printf("%03o", v & 0377);
		break;

	  case FMT_ASCII:
		/*
		 * Don't use is... functions because they depend on th
		 * locale
		 */
		v &= 0177;
		if(v == 0177)
			printf("^?");
		else if(v >= 040)
			printf("%c", v);
		else
			printf("^%c", v + 0100);
		break;

	  case FMT_R50:
		printf("bad FMT_R50\n");
		break;
	}
}

static void
out_space(void)
{
	putchar(' ');
}

/*
 * save current registers
 */
static void
save_regs(void)
{
	if(proc.regs)
		memcpy(proc.kreg, proc.reg, sizeof(proc.kreg));
	else
		memcpy(proc.ureg, proc.reg, sizeof(proc.ureg));
	proc.sp[proc.curmode] = proc.reg[6];
}

static void
restore_regs(void)
{
	if(proc.regs)
		memcpy(proc.reg, proc.kreg, sizeof(proc.kreg));
	else
		memcpy(proc.reg, proc.ureg, sizeof(proc.ureg));
	proc.reg[6] = proc.sp[proc.curmode];
}


/*
 * I/O 
 */
static struct termios tty_saved;

static void
save_tty()
{
	tcgetattr(0, &tty_saved);
}

static void
raw(void)
{
	struct termios t;

	t = tty_saved;
	cfmakeraw(&t);
	t.c_iflag |= IXON;
	tcsetattr(0, TCSANOW | TCSASOFT, &t);
}

static void
noraw(void)
{
	tcsetattr(0, TCSANOW | TCSASOFT, &tty_saved);
}

static int
gch(void)
{
	char 	c;
	int	cc;
	int	ret;

	fflush(stdout);

	if(monfile == NULL || (cc = read_monfile()) == EOF) {
		/*
		 * read from standard input
		 */
		if((ret = read(0, &c, 1)) != 1) {
			noraw();
			panic("tty read error: %s\n", strerror(errno));
		}
		cc = c;
	}

	if(cc == 3) {
		printf("^C"); crlf();
		return -1;
	}
	cc &= 0177;
	if(cc == '\r')
		putchar(cc);
	else if(cc < ' ')
		printf("^%c", cc + 0100);
	else if(cc == 0177)
		printf("^?");
	else
		putchar(cc);
	return cc;
}

/*
 * read next token from input file. If EOF close file and return EOF.
 */
static int
read_monfile()
{
	int	backs = 0;
	int	c;

	for(;;) {
		if((c = getc(monfile)) == EOF) {
			if(ferror(monfile))
				panic("monitor input: %s", strerror(errno));
			fclose(monfile);
			monfile = NULL;
			return EOF;
		}
		if(c == '\\' && !backs)
			backs = 1;
		else if(c != '\n')
			break;
	}
	if(!backs)
		return c;

	switch(c) {

	case 'r':
		return '\r';

	case 'n':
		return '\n';

	case 'e':
		return '[' - 0100;

	case '\\':
		return '\\';
	}
	ungetc(c, monfile);
	return '\\';
}


static void
crlf(void)
{
	putchar('\r');
	putchar('\n');
}

/*
 * read an octal number. The first character is already in `c'.
 * leave terminating one in c.
 */
static int
get8(void)
{
	u_int v = 0;

	do {
		v = 8 * v + (chr - '0');
	} while((chr = gch()) >= '0' && chr <= '7');
	return v;
}

static int
getstring(char *buf, int len)
{
	char	*p = buf, *end = buf + len;

	while((chr = gch()) != '\n' && chr != '\r') {
		if(chr == '\b' || chr == 0177) {
			if(p > buf) {
				p--;
				printf("\b\b\b   \b\b\b");
			} else
				printf("\b\b  \b\b");
		} else if(p < end - 1) {
			*p++ = chr;
		}
	}
	*p = 0;
	crlf();
	return p - buf;
}

/*
 * read register name
 * 1st character is already in c.
 * leave next character in c.
 * returns -1 on failure.
 * "$0" - "$5" means 1st register set
 * "$0'" - "$5'" means 2nd register set
 * "$6u", "$6s", "$6k", "$6" means user, supervisor, kernal stack pointer
 * "$7" means PC
 */
static int
getreg()
{
	int a;

	switch(chr = gch()) {
	
	case '0': 
	case '1': 
	case '2': 
	case '3': 
	case '4': 
	case '5': 
		a = R0 + chr - '0';
		if((chr = gch()) == '\'')
			a += R0s - R0;
		else
			return a;
		break;

	case '6': 
		if((chr = gch()) == 'u')
			a = USP;
		else if(chr == 's')
			a = SSP;
		else if(chr == 'k')
			a = KSP;
		else
			return KSP;
		break;

	case '7': 
		a = PC;
		break;

	default:
		return -1;
	}
	chr = gch();
	return a;
}

/*
 * Get a controller name from the user.
 */
static iodev_t *
get_device(char ch)
{
	char buf[MAXCTRLNAME+1];
	char *p = buf, c;
	iodev_t *dev;

	putchar(' ');
	while((c = gch()) != '\r' && c != '\n' && c != ' ') {
		if(p == &buf[sizeof(buf)-1]) {
			putchar('?');
			crlf();
			return 0;
		}
		if(c == '\b' || c == '\177') {
			if(p > buf) {
				p--;
				printf("\b\b\b   \b\b\b");
			} else
				printf("\b\b  \b\b");
		} else if(c == '\t') {
			*p = 0;
			if((dev = devcomplete(buf)) != NULL)
				return dev;
			crlf();
			printf("@%c %s", ch, buf);
			p = buf + strlen(buf);
		} else
			*p++ = c;
	}
	*p = 0;
	crlf();

	for(dev = proc.devices; dev; dev = dev->next) {
		if(buf[0] == 0) {
			printf("%s", ctrlname(dev, 0));
			crlf();
		} else if(!strcmp(ctrlname(dev, 0), buf))
			return dev;
	}

	return 0;
}

static iodev_t *
devcomplete(char *buf)
{
	iodev_t *dev, *found;
	int matches;
	size_t len, sublen;
	char sub[MAXCTRLNAME+1];

	/*
	 * Count the number of prefix matches
	 */
	len = strlen(buf);
	matches = 0;
	found = NULL;
	for(dev = proc.devices; dev != NULL; dev = dev->next) {
		if(strncmp(buf, ctrlname(dev, 0), len) == 0 ||
		   strncmp(buf, ctrlname(dev, 1), len) == 0) {
			found = dev;
			matches++;
		}
	}

	if(matches < 2)
		return found;

	/*
	 * More than one match. Print out the variants and compute
	 * the longest common subterm.
	 */
	sub[0] = '\0';
	for(dev = proc.devices; dev != NULL; dev = dev->next) {
		if(strncmp(buf, ctrlname(dev, 0), len) == 0 ||
		   strncmp(buf, ctrlname(dev, 1), len) == 0) {
			crlf();
			printf("%s", ctrlname(dev, 0));
			if(sub[0] == '\0')
				strcpy(sub, ctrlname(dev, 0));
			else {
				sublen = strsub(sub, ctrlname(dev, 0));
				if(sublen < strlen(sub))
					sub[sublen] = '\0';
			}
		}
	}

	/*
	 * Copy longest subterm to buffer
	 */
	strcpy(buf, sub);

	return NULL;
}

static size_t
strsub(char *s1, char *s2)
{
	char *o = s1;

	while(*s1 == *s2 && *s1 != '\0')
		s1++, s2++;
	return s1 - o;
}

static void
doinfo(void)
{
	iodev_t *dev = get_device('i');

	if(dev) {
		noraw();
		(*dev->ioops->info)(dev);
		raw();
	}
}

static void
device_command(void)
{
	iodev_t *dev = get_device('i');
	char	buf[70];
	char	*argv[20];
	int	argc;

	if(!dev)
		return;
	for(;;) {
		printf("%s >", ctrlname(dev, 0));
		if(!getstring(buf, 70))
			return;
		if((argc = getmfields(buf, argv, sizeof(argv)/sizeof(argv[0]))) == 0)
			return;
		noraw();
		dev_command(dev, argc, argv);
		raw();
	}
}

static void
find(int iv)
{
	u_int a;
	u_short v = iv, vv;

	for(a = 0; a < PHYSMEM; a += 2) {
		vv = *proc.memops->addrphys(proc.mmg, a);
		vv = GETW(vv);
		if(vv == v) 
			printf("%08o/%06o\r\n", a, v);
	}
}

/*
 * Type a help file onto stdout.
 *
 * If stdout is not a tty (as indicated by isatty) the file is simply
 * copied to stdout. If stdout is a tty and enviroment variable PAGER
 * exists, the pager is called with the appropriate full filename. If PAGER
 * is not set, the window size is obtained trough an ioctl (or a default
 * of 24 used) and the file is typed one page at a time. The user may
 * quit by typing 'q' or ESC.
 */
void
type_help_file(char *help)
{
	struct winsize ws;
	int	lines;
	int	lno = 0;
	char	buf[200];
	FILE	*hf;
	char	*fname;
	char	*command;
	char	*pager;

	if ((hf = lib_fopen(help, &fname)) == NULL) {
		printf("p11: %s: %s\n", help, strerror(errno));
		return;
	}

	/*
	 * now look what we must do
	 */
	if (isatty(fileno(stdout))) {
		if ((pager = getenv("PAGER")) != 0) {
			fclose(hf);
			command = xalloc(strlen(pager) + strlen(fname) + 2);
			strcpy(command, pager);
			strcat(command, " ");
			strcat(command, fname);
			system(command);
			free(command);
			free(fname);
			return;
		} 
		if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) || (lines = ws.ws_row) <= 0)
			lines = LINES;
	} else
		lines = 0;

	while (fgets(buf, sizeof(buf), hf) != NULL) {
		if (lines != 0 && lno % (lines - 1) == (lines - 2)) {
			printf("continue?");
			raw();
			chr = gch();
			noraw();
			printf("\n");
			if (chr == 'q' || chr == '\033')
				break;
		}
		fputs(buf, stdout);
		lno++;
	}
	if (ferror(hf))
		printf("p11: read error: %s\n", strerror(errno));

	free(fname);
	fclose(hf);
}

# define B		0100000
# define TNONE		1
# define TONE		2
# define TREG		3
# define TSPL		4
# define TCC		5
# define TBRANCH	6
# define TRONE		7
# define TDBL		8
# define TSOB		9
# define TEMT		10
# define TFONE		11
# define TFDBL		12


static struct ins {
	int	type;
	int	val;
	int	mask;
	char	*ins;
} instab[] = {
	{ TNONE,	0000000, 0000000, "halt" },
	{ TNONE,	0000001, 0000000, "wait" },
	{ TNONE,	0000002, 0000000, "rti" },
	{ TNONE,	0000003, 0000000, "bpt" },
	{ TNONE,	0000004, 0000000, "iot" },
	{ TNONE,	0000005, 0000000, "reset" },
	{ TNONE,	0000006, 0000000, "rtt" },
	{ TNONE,	0000007, 0000000, "mfpt" },
	{ TONE,		0000100, 0000077, "jmp" },
	{ TREG,		0000200, 0000007, "rts" },
	{ TSPL,		0000230, 0000007, "spl" },
	{ TNONE,	0000240, 0000000, "nop" },
	{ TCC,		0000240, 0000017, "cflg" },
	{ TCC,		0000260, 0000017, "sflg" },
	{ TONE,		0000300, 0000077, "swab" },
	{ TBRANCH,	0000400, 0000377, "br" },
	{ TBRANCH,	0001000, 0000377, "bne" },
	{ TBRANCH,	0001400, 0000377, "beq" },
	{ TBRANCH,	0002000, 0000377, "bge" },
	{ TBRANCH,	0002400, 0000377, "blt" },
	{ TBRANCH,	0003000, 0000377, "bgt" },
	{ TBRANCH,	0003400, 0000377, "ble" },
	{ TBRANCH,	0100000, 0000377, "bpl" },
	{ TBRANCH,	0100400, 0000377, "bmi" },
	{ TBRANCH,	0101000, 0000377, "bhi" },
	{ TBRANCH,	0101400, 0000377, "blos" },
	{ TBRANCH,	0102000, 0000377, "bvc" },
	{ TBRANCH,	0102400, 0000377, "bvs" },
	{ TBRANCH,	0103000, 0000377, "bcc" },
	{ TBRANCH,	0103400, 0000377, "bcs" },
	{ TRONE,	0004000, 0000777, "jsr" },
	{ TONE | B,	0005000, 0100077, "clr" },
	{ TONE | B,	0005100, 0100077, "com" },
	{ TONE | B,	0005200, 0100077, "inc" },
	{ TONE | B,	0005300, 0100077, "dec" },
	{ TONE | B,	0005400, 0100077, "neg" },
	{ TONE | B,	0005500, 0100077, "adc" },
	{ TONE | B,	0005600, 0100077, "sbc" },
	{ TONE | B,	0005700, 0100077, "tst" },
	{ TONE | B,	0006000, 0100077, "ror" },
	{ TONE | B,	0006100, 0100077, "rol" },
	{ TONE | B,	0006200, 0100077, "asr" },
	{ TONE | B,	0006300, 0100077, "asl" },
	{ TONE,		0006500, 0000077, "mfpi" },
	{ TONE,		0006600, 0000077, "mtpi" },
	{ TONE,		0006700, 0000077, "sxt" },
	{ TDBL | B,	0010000, 0107777, "mov" },
	{ TDBL | B,	0020000, 0107777, "cmp" },
	{ TDBL | B,	0030000, 0107777, "bit" },
	{ TDBL | B,	0040000, 0107777, "bic" },
	{ TDBL | B,	0050000, 0107777, "bis" },
	{ TDBL,		0060000, 0007777, "add" },
	{ TRONE,	0070000, 0000777, "mul" },
	{ TRONE,	0071000, 0000777, "div" },
	{ TRONE,	0072000, 0000777, "ash" },
	{ TRONE,	0073000, 0000777, "ashc" },
	{ TRONE,	0074000, 0000777, "xor" },
	{ TSOB,		0077000, 0000777, "sob" },
	{ TEMT,		0104000, 0000377, "emt" },
	{ TEMT,		0104400, 0000377, "trap" },
	{ TONE,		0106400, 0000077, "mtps" },
	{ TONE,		0106500, 0000077, "mfpd" },
	{ TONE,		0106600, 0000077, "mtpd" },
	{ TONE,		0106700, 0000077, "mfps" },
	{ TDBL,		0160000, 0007777, "sub" },

	{ TNONE,	0170000, 0000000, "cfcc" },
	{ TNONE,	0170001, 0000000, "setf" },
	{ TNONE,	0170002, 0000000, "seti" },
	{ TNONE,	0170011, 0000000, "setd" },
	{ TNONE,	0170012, 0000000, "setl" },
	{ TONE,		0170100, 0000077, "ldfps" },
	{ TONE,		0170200, 0000077, "stfps" },
	{ TONE,		0170300, 0000077, "stst" },
	{ TFONE,	0170400, 0000077, "clrf" },
	{ TFONE,	0170500, 0000077, "tstf" },
	{ TFONE,	0170600, 0000077, "absf" },
	{ TFONE,	0170700, 0000077, "negf" },
	{ TFDBL,	0171000, 0000377, "mulf" },
	{ TFDBL,	0171400, 0000377, "modf" },
	{ TFDBL,	0172000, 0000377, "addf" },
	{ TFDBL,	0172400, 0000377, "ldf" },
	{ TFDBL,	0173000, 0000377, "subf" },
	{ TFDBL,	0173400, 0000377, "cmpf" },
	{ TFDBL,	0174000, 0000377, "std" },
	{ TFDBL,	0174400, 0000377, "divf" },
	{ TFDBL,	0175000, 0000377, "stexp" },
	{ TFDBL,	0175400, 0000377, "stci" },
	{ TFDBL,	0176000, 0000377, "stcf" },
	{ TFDBL,	0176400, 0000377, "ldexp" },
	{ TFDBL,	0177000, 0000377, "ldci" },
	{ TFDBL,	0177400, 0000377, "ldcf" },

	{ 0, 0, 0, 0 },
};

/*
 * dump an instruction
 */
static u_short
pins_fetch(u_int addr, int mode, u_int *pa, u_short *pv)
{
	if(mode >= 0) {
		u_short par = proc.par[mode][0][(addr >> 13) & 7];
		u_short off = addr & 017777;

		if(!(proc.mmr0 & 1)) {
			*pa = addr & 0177776;
			if(addr >= 0160000)
				*pa += 017600000;
		} else if(!(proc.mmr3 & 020)) {
			*pa = (((int)par << 6) + off) & 0777776;
			if(*pa >= 0760000)
				*pa += 017000000;
		} else {
			*pa = (((int)par << 6) + off) & 017777776;
		}
	} else {
		*pa = addr;
	}
	
	if(*pa >= 017760000 && *pa <= 017777776) {
		iodev_t *dev = proc.iopage[IOP(*pa)];
		if(!dev || !dev->ioops->fetch)
			return 1;
		*pv = iofetch(*pa);
	} else if(*pa >= proc.physmem)
		return 1;
	else {
		*pv = *proc.memops->addrphys(proc.mmg, *pa);
		*pv = GETW(*pv);
	}
	return 0;
}

static void pins_addr(FILE *, u_int *, int, int);

static int
pins(FILE *fp, u_int addr, int mode)
{
	u_int a;
	u_short v;
	int i;
	u_int old_addr = addr;

	i = pins_fetch(addr, mode, &a, &v);
	fprintf(fp, "%06ho %c -> %08o", addr, "PKS?U"[mode+1], a);
	addr += 2;
	if(i) {
		crlf();
		return -1;;
	}
	fprintf(fp, " = %06ho", v);

	for(i = 0; instab[i].ins; i++)
		if((v & ~instab[i].mask) == instab[i].val) {
			fprintf(fp, ":\t%s", instab[i].ins);
			if((instab[i].type & B) && (v & 0100000))
				putc('b', fp);
			putc('\t', fp);

			switch(instab[i].type & ~B) {

			case TNONE:
				break;

			case TREG:
				fprintf(fp, "r%d", v & 7);
				break;

			case TSPL:
				fprintf(fp, "%d", v & 7);
				break;

			case TCC:
				if(!(v & 017))
					putc('0', fp);
				else {
					int j = 4;
					while(--j >= 0)
						if(v & (1 << j))
							putc("cvzn"[j], fp);
				}
				break;

			case TBRANCH:
				if((v & 0377) >= 0200)
					fprintf(fp, "%o", (addr - ((-v & 0377) << 1)) & 0177777);
				else 
					fprintf(fp, "%o", (addr + ((v & 0377) << 1)) & 0177777);
				break;

			case TSOB:
				fprintf(fp, "r%d,%o", (v >> 6) & 7,
					addr - ((v & 077) << 1));
				break;

			case TEMT:
				fprintf(fp, "%o", v & 0377);
				break;

			case TONE:
				pins_addr(fp, &addr, mode, (v & 077));
				break;

			case TRONE:
				fprintf(fp, "r%d,", (v >> 6) & 7);
				pins_addr(fp, &addr, mode, (v & 077));
				break;
				
			case TDBL:
				pins_addr(fp, &addr, mode, ((v >> 6) & 077));
				putc(',', fp);
				pins_addr(fp, &addr, mode, (v & 077));
				break;

			case TFDBL:
				fprintf(fp, "ac%c,", "0123"[(v >> 6) & 3]);

			case TFONE:
				if((v & 070) == 0)
					fprintf(fp, "ac%c", "012345??"[v & 7]);
				else
					pins_addr(fp, &addr, mode, (v & 077));
				break;

			default:
				fprintf(fp, "bad type %d", instab[i].type);
			}
			break;
		}
	putc('\n', fp);
	return addr - old_addr;
}

static void
pins_addr(FILE *fp, u_int *addr, int mode, int t)
{
	u_int a;
	u_short v;

	if(t == 027 || t == 037 || (t & 060) == 060) {
		if(pins_fetch(*addr, mode, &a, &v)) {
			*addr += 2;
			putc('?', fp);
			return;
		}
		*addr += 2;
	}
	if(t & 010) {
		putc('@', fp);
		t &= ~010;
	}
	if(t == 027) {
		fprintf(fp, "#%o", v);
		return;
	}
	if(t == 067) {
		fprintf(fp, "%o", (*addr + v) & 0177777);
		return;
	}

	switch(t & 060) {

	case 0:
		fprintf(fp, "r%d", t & 7);
		break;

	case 020:
		fprintf(fp, "(r%d)+", t & 7);
		break;

	case 040:
		fprintf(fp, "-(r%d)", t & 7);
		break;

	case 060:
		fprintf(fp, "%o(r%d)", v, t & 7);
		break;
	}
}

/*
 * user process tracing
 */
FILE	*fputrace;
static void
close_utrace(void)
{
	fclose(fputrace);
}

void
utrace(void)
{
	if(!f_utrace)
		return;
	if(!fputrace) {
		if(!(fputrace = fopen("utrace", "w"))) {
			printf("utrace: %s\n", strerror(errno));
			f_utrace = 0;
			return;
		}
		atexit(close_utrace);
	}
	pins(fputrace, proc.reg[7], proc.curmode);
}

/*
 * print outstanding interrupt requests
 */
static void
preqs(void)
{
	iodev_t *d;

	for(d = proc.devices; d; d = d->next) {
		if(d->reqpri == DMAPRI)
			printf("[DMA - %s]\n", ctrlname(d, 0));
		else if(d->reqpri)
			printf("[%-3d - %s]\n", d->reqpri, ctrlname(d, 0));
	}
}

static void
escape(void)
{
	char	*shell = getenv("SHELL");
	char	buf[1000];

	if(!shell)
		shell = _PATH_BSHELL;
	sprintf(buf, "exec %s", shell);
	system(buf);
}

/*
 * dump instructions
 */
static void
dump_ins(void)
{
	int inc, cnt = 1;

	crlf();
	for(;;) {
		inc = pins(stdout, loc, -1);
		putchar('\r');
		if(inc <= 0)
			break;
		loc += inc;
		if(--cnt <= 0) {
			switch(gch()) {

			case '\033':
				return;

			case ' ':
				cnt = 10;
				break;

			default:
				cnt = 1;
			}
		}
	}
}
