#include <u.h>
#include <libc.h>
#include <bio.h>
#include <mach.h>
/*
 * Mips-specific debugger interface
 */

static	char	*mipsexcep(Map*, Rgetter);
static	int	mipsfoll(Map*, uvlong, Rgetter, uvlong*);
static	int	mipsinst(Map*, uvlong, char, char*, int);
static	int	mipsdas(Map*, uvlong, char*, int);
static	int	mipsinstlen(Map*, uvlong);

/*
 *	Debugger interface
 */
Machdata mipsmach =
{
	{0, 0, 0, 0xD},		/* break point */
	4,			/* break point size */

	beswab,			/* short to local byte order */
	beswal,			/* long to local byte order */
	beswav,			/* vlong to local byte order */
	risctrace,		/* C traceback */
	riscframe,		/* Frame finder */
	mipsexcep,		/* print exception */
	0,			/* breakpoint fixup */
	beieeesftos,		/* single precision float printer */
	/*
	 * this works for doubles in memory, but FP register pairs have
	 * the words in little-endian order, so they will print as
	 * denormalised doubles.
	 */
	beieeedftos,		/* double precision float printer */
	mipsfoll,		/* following addresses */
	mipsinst,		/* print instruction */
	mipsdas,		/* dissembler */
	mipsinstlen,		/* instruction size */
};

Machdata mipsmachle =
{
	{0xD, 0, 0, 0},		/* break point */
	4,			/* break point size */

	leswab,			/* short to local byte order */
	leswal,			/* long to local byte order */
	leswav,			/* vlong to local byte order */
	risctrace,		/* C traceback */
	riscframe,		/* Frame finder */
	mipsexcep,		/* print exception */
	0,			/* breakpoint fixup */
	leieeesftos,		/* single precision float printer */
	leieeedftos,		/* double precision float printer */
	mipsfoll,		/* following addresses */
	mipsinst,		/* print instruction */
	mipsdas,		/* dissembler */
	mipsinstlen,		/* instruction size */
};

/*
 *	mips r4k little-endian
 */
Machdata mipsmach2le =
{
	{0xD, 0, 0, 0},		/* break point */
	4,			/* break point size */

	leswab,			/* short to local byte order */
	leswal,			/* long to local byte order */
	leswav,			/* vlong to local byte order */
	risctrace,		/* C traceback */
	riscframe,		/* Frame finder */
	mipsexcep,		/* print exception */
	0,			/* breakpoint fixup */
	leieeesftos,		/* single precision float printer */
	leieeedftos,		/* double precision float printer */
	mipsfoll,		/* following addresses */
	mipsinst,		/* print instruction */
	mipsdas,		/* dissembler */
	mipsinstlen,		/* instruction size */
};

/*
 *	mips r4k big-endian
 */
Machdata mipsmach2be =
{
	{0, 0, 0, 0xD},		/* break point */
	4,			/* break point size */

	beswab,			/* short to local byte order */
	beswal,			/* long to local byte order */
	beswav,			/* vlong to local byte order */
	risctrace,		/* C traceback */
	riscframe,		/* Frame finder */
	mipsexcep,		/* print exception */
	0,			/* breakpoint fixup */
	beieeesftos,		/* single precision float printer */
	beieeedftos,		/* double precision float printer */
	mipsfoll,		/* following addresses */
	mipsinst,		/* print instruction */
	mipsdas,		/* dissembler */
	mipsinstlen,		/* instruction size */
};


static char *excname[] =
{
	"external interrupt",
	"TLB modification",
	"TLB miss (load or fetch)",
	"TLB miss (store)",
	"address error (load or fetch)",
	"address error (store)",
	"bus error (fetch)",
	"bus error (data load or store)",
	"system call",
	"breakpoint",
	"reserved instruction",
	"coprocessor unusable",
	"arithmetic overflow",
	"undefined 13",
	"undefined 14",
	"system call",
	/* the following is made up */
	"floating point exception"		/* FPEXC */
};

static char*
mipsexcep(Map *map, Rgetter rget)
{
	int e;
	long c;

	c = (*rget)(map, "CAUSE");
	/* i don't think this applies to any current machines */
	if(0 && c & 0x00002000)	/* INTR3 */
		e = 16;		/* Floating point exception */
	else
		e = (c>>2)&0x0F;
	return excname[e];
}

	/* mips disassembler and related functions */

static	char FRAMENAME[] = ".frame";

typedef struct {
	uvlong addr;
	uchar op;			/* bits 31-26 */
	uchar rs;			/* bits 25-21 */
	uchar rt;			/* bits 20-16 */
	uchar rd;			/* bits 15-11 */
	uchar sa;			/* bits 10-6 */
	uchar function;			/* bits 5-0 */
	long immediate;			/* bits 15-0 */
	ulong cofun;			/* bits 24-0 */
	ulong target;			/* bits 25-0 */
	long w0;
	long w1;
	int size;			/* instruction size */
	char *curr;			/* fill point in buffer */
	char *end;			/* end of buffer */
	char *err;			/* error message */
} Instr;

static Map *mymap;

static int
decode(uvlong pc, Instr *i)
{
	ulong w;

	if (get4(mymap, pc, &w) < 0) {
		werrstr("can't read instruction: %r");
		return -1;
	}

	i->addr = pc;
	i->size = 1;
	i->op = (w >> 26) & 0x3F;
	i->rs = (w >> 21) & 0x1F;
	i->rt = (w >> 16) & 0x1F;
	i->rd = (w >> 11) & 0x1F;
	i->sa = (w >> 6) & 0x1F;
	i->function = w & 0x3F;
	i->immediate = w & 0x0000FFFF;
	if (i->immediate & 0x8000)
		i->immediate |= ~0x0000FFFF;
	i->cofun = w & 0x01FFFFFF;
	i->target = w & 0x03FFFFFF;
	i->w0 = w;
	return 1;
}

static int
mkinstr(uvlong pc, Instr *i)
{
	Instr x;

	if (decode(pc, i) < 0)
		return -1;
	/*
	 * if it's a LUI followed by an ORI,
	 * it's an immediate load of a large constant.
	 * fix the LUI immediate in any case.
	 */
	if (i->op == 0x0F) {
		if (decode(pc+4, &x) < 0)
			return 0;
		i->immediate <<= 16;
		if (x.op == 0x0D && x.rs == x.rt && x.rt == i->rt) {
			i->immediate |= (x.immediate & 0xFFFF);
			i->w1 = x.w0;
			i->size++;
			return 1;
		}
	}
	/*
	 * if it's a LWC1 followed by another LWC1
	 * into an adjacent register, it's a load of
	 * a floating point double.
	 */
	else if (i->op == 0x31 && (i->rt & 0x01)) {
		if (decode(pc+4, &x) < 0)
			return 0;
		if (x.op == 0x31 && x.rt == (i->rt - 1) && x.rs == i->rs) {
			i->rt -= 1;
			i->w1 = x.w0;
			i->size++;
			return 1;
		}
	}
	/*
	 * similarly for double stores
	 */
	else if (i->op == 0x39 && (i->rt & 0x01)) {
		if (decode(pc+4, &x) < 0)
			return 0;
		if (x.op == 0x39 && x.rt == (i->rt - 1) && x.rs == i->rs) {
			i->rt -= 1;
			i->w1 = x.w0;
			i->size++;
		}
	}
	return 1;
}

#pragma	varargck	argpos	bprint		2

static void
bprint(Instr *i, char *fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	i->curr = vseprint(i->curr, i->end, fmt, arg);
	va_end(arg);
}

typedef struct Opcode Opcode;

struct Opcode {
	char *mnemonic;
	void (*f)(Opcode *, Instr *);
	char *ken;
};

static void format(char *, Instr *, char *);

static void
branch(Opcode *o, Instr *i)
{
	if (i->rs == 0 && i->rt == 0)
		format("JMP", i, "%b");
	else if (i->rs == 0)
		format(o->mnemonic, i, "R%t,%b");
	else if (i->rt < 2)
		format(o->mnemonic, i, "R%s,%b");
	else
		format(o->mnemonic, i, "R%s,R%t,%b");
}

static void
addi(Opcode *o, Instr *i)
{
	if (i->rs == i->rt)
		format(o->mnemonic, i, "%i,R%t");
	else if (i->rs == 0)
		format("MOVW", i, "%i,R%t");
	else if (i->rs == 30) {
		bprint(i, "MOVW\t$");
		i->curr += symoff(i->curr, i->end-i->curr,
					i->immediate+mach->sb, CANY);
		bprint(i, "(SB),R%d", i->rt);
	}
	else
		format(o->mnemonic, i, o->ken);
}

static void
andi(Opcode *o, Instr *i)
{
	if (i->rs == i->rt)
		format(o->mnemonic, i, "%i,R%t");
	else
		format(o->mnemonic, i, o->ken);
}

static int
plocal(Instr *i, char *m, char r, int store)
{
	int offset;
	char *reg;
	Symbol s;

	if (!findsym(i->addr, CTEXT, &s) || !findlocal(&s, FRAMENAME, &s))
		return 0;
	if (s.value > i->immediate) {
		if(!getauto(&s, s.value-i->immediate, CAUTO, &s))
			return 0;
		reg = "(SP)";
		offset = i->immediate;
	} else {
		offset = i->immediate-s.value;
		if (!getauto(&s, offset-4, CPARAM, &s))
			return 0;
		reg = "(FP)";
	}
	if (store)
		bprint(i, "%s\t%c%d,%s+%d%s", m, r, i->rt, s.name, offset, reg);
	else
		bprint(i, "%s\t%s+%d%s,%c%d", m, s.name, offset, reg, r, i->rt);
	return 1;
}

static void
lw(Opcode *o, Instr *i, char r)
{
	char *m;

	if (r == 'F') {
		if (i->size == 2)
			m = "MOVD";
		else
			m = "MOVF";
	}
	else
		m = o->mnemonic;
	if (i->rs == 29 && plocal(i, m, r, 0))
			return;

	if (i->rs == 30 && mach->sb) {
		bprint(i, "%s\t", m);
		i->curr += symoff(i->curr, i->end-i->curr, i->immediate+mach->sb, CANY);
		bprint(i, "(SB),%c%d", r, i->rt);
		return;
	}
	if (r == 'F')
		format(m, i, "%l,F%t");
	else
		format(m, i, o->ken);
}

static void
load(Opcode *o, Instr *i)
{
	lw(o, i, 'R');
}

static void
lwc1(Opcode *o, Instr *i)
{
	lw(o, i, 'F');
}

static void
sw(Opcode *o, Instr *i, char r)
{
	char *m;

	if (r == 'F') {
		if (i->size == 2)
			m = "MOVD";
		else
			m = "MOVF";
	}
	else
		m = o->mnemonic;
	if (i->rs == 29 && plocal(i, m, r, 1))
			return;

	if (i->rs == 30 && mach->sb) {
		bprint(i, "%s\t%c%d,", m, r, i->rt);
		i->curr += symoff(i->curr, i->end-i->curr, i->immediate+mach->sb, CANY);
		bprint(i, "(SB)");
		return;
	}
	if (r == 'F')
		format(m, i, "F%t,%l");
	else
		format(m, i, o->ken);
}

static void
store(Opcode *o, Instr *i)
{
	sw(o, i, 'R');
}

static void
swc1(Opcode *o, Instr *i)
{
	sw(o, i, 'F');
}

static void
sll(Opcode *o, Instr *i)
{
	if (i->w0 == 0)
		bprint(i, "NOOP");		/* unofficial nop */
	else if (i->w0 == 0xc0)			/* 0xc0: SLL $3,R0 */
		bprint(i, "EHB");
	else if (i->rd == i->rt)
		format(o->mnemonic, i, "$%a,R%d");
	else
		format(o->mnemonic, i, o->ken);
}

static void
sl32(Opcode *o, Instr *i)
{
	i->sa += 32;
	if (i->rd == i->rt)
		format(o->mnemonic, i, "$%a,R%d");
	else
		format(o->mnemonic, i, o->ken);
}

static void
sllv(Opcode *o, Instr *i)
{
	if (i->rd == i->rt)
		format(o->mnemonic, i, "R%s,R%d");
	else
		format(o->mnemonic, i, o->ken);
}

static void
jal(Opcode *o, Instr *i)
{
	if (i->rd == 31)
		format("JAL", i, "(R%s)");
	else
		format(o->mnemonic, i, o->ken);
}

static void
add(Opcode *o, Instr *i)
{
	if (i->rd == i->rs)
		format(o->mnemonic, i, "R%t,R%d");
	else if (i->rd == i->rt)
		format(o->mnemonic, i, "R%s,R%d");
	else
		format(o->mnemonic, i, o->ken);
}

static void
sub(Opcode *o, Instr *i)
{
	if (i->rd == i->rs)
		format(o->mnemonic, i, "R%t,R%d");
	else
		format(o->mnemonic, i, o->ken);
}

static void
or(Opcode *o, Instr *i)
{
	if (i->rs == 0 && i->rt == 0)
		format("MOVW", i, "$0,R%d");
	else if (i->rs == 0)
		format("MOVW", i, "R%t,R%d");
	else if (i->rt == 0)
		format("MOVW", i, "R%s,R%d");
	else
		add(o, i);
}

static void
nor(Opcode *o, Instr *i)
{
	if (i->rs == 0 && i->rt == 0 && i->rd == 0)
		format("NOP", i, 0);
	else
		add(o, i);
}

static char mipscoload[] = "r%t,%l";
static char mipsload[] = "%l,R%t";
static char mipsstore[] = "R%t,%l";
static char mipsalui[] = "%i,R%s,R%t";
static char mipsalu3op[] = "R%t,R%s,R%d";
static char mipsrtrs[] = "R%t,R%s";
static char mipscorsrt[] = "r%s,r%t";
static char mipscorsi[] = "r%s,%i";
static char mipscoxxx[] = "%w";
static char mipscofp3[] = "f%a,f%d,f%t";	/* fd,fs,ft */
static char mipsfp3[] = "F%t,F%d,F%a";
static char mipscofp2[] = "f%a,f%d";		/* fd,fs */
static char mipsfp2[] = "F%d,F%a";
static char mipscofpc[] = "f%d,f%t";		/* fs,ft */
static char mipsfpc[] = "F%t,F%d";

static Opcode opcodes[64] = {
	0,		0,	0,
	0,		0,	0,
	"JMP",		0,	"%j",
	"JAL",		0,	"%j",
	"BEQ",	   branch,	0,
	"BNE",	   branch,	0,
	"BLEZ",	   branch,	0,
	"BGTZ",	   branch,	0,
	"ADD",	     addi,	mipsalui,
	"ADDU",	     addi,	mipsalui,
	"SGT",		0,	mipsalui,
	"SGTU",		0,	mipsalui,
	"AND",	     andi,	mipsalui,
	"OR",	     andi,	mipsalui,
	"XOR",	     andi,	mipsalui,
	"MOVW",		0,	"$%u,R%t",
	"cop0",		0,	0,
	"cop1",		0,	0,
	"cop2",		0,	0,
	"cop3",		0,	0,
	"BEQL",	   branch,	0,
	"BNEL",	   branch,	0,
	"BLEZL",   branch,	0,
	"BGTZL",   branch,	0,
	"ADDV",		 addi,	mipsalui,
	"ADDVU",	 addi,	mipsalui,
	"MOVVL",     load,	mipsload,
	"MOVVR",     load,	mipsload,
	"instr1C",	0,	mipscoxxx,
	"instr1D",	0,	mipscoxxx,
	"instr1E",	0,	mipscoxxx,
	"instr1F",	0,	mipscoxxx,
	"MOVB",	     load,	mipsload,
	"MOVH",	     load,	mipsload,
	"MOVWL",	 load,	mipsload,
	"MOVW",	     load,	mipsload,
	"MOVBU",     load,	mipsload,
	"MOVHU",     load,	mipsload,
	"MOVWR",	 load,	mipsload,
	"lwu",		0,	mipscoload,
	"MOVB",	    store,	mipsstore,
	"MOVH",	    store,	mipsstore,
	"MOVWL",	store,	mipsstore,
	"MOVW",	    store,	mipsstore,
	"MOVVL",    store,	mipsstore,
	"MOVVR",    store,	mipsstore,
	"MOVWR",	store,	mipsstore,
	"CACHE",	0,	"%C,%l",
	"ll",		0,	mipscoload,
	"MOVW",	     lwc1,	mipscoload,
	"lwc2",		0,	mipscoload,
	"lwc3",		0,	mipscoload,
	"lld",		0,	mipscoload,
	"ldc1",		0,	mipscoload,
	"ldc2",		0,	mipscoload,
	"MOVV",	    load,	mipsload,
	"sc",		0,	mipscoload,
	"swc1",	     swc1,	mipscoload,
	"swc2",		0,	mipscoload,
	"swc3",		0,	mipscoload,
	"scd",		0,	mipscoload,
	"sdc1",		0,	mipscoload,
	"sdc2",		0,	mipscoload,
	"MOVV",	    store,	mipsstore,
};

static Opcode sopcodes[64] = {
	"SLL",	      sll,	"$%a,R%t,R%d",
	"special01",	0,	mipscoxxx,
	"SRL",	      sll,	"$%a,R%t,R%d",
	"SRA",	      sll,	"$%a,R%t,R%d",
	"SLL",	     sllv,	"R%s,R%t,R%d",
	"special05",	0,	mipscoxxx,
	"SRL",	     sllv,	"R%s,R%t,R%d",
	"SRA",	     sllv,	"R%s,R%t,R%d",
	"JMP",		0,	"(R%s)",
	"jal",	      jal,	"r%d,r%s",
	"special0A",	0,	mipscoxxx,
	"special0B",	0,	mipscoxxx,
	"SYSCALL",	0,	0,
	"BREAK",	0,	0,
	"special0E",	0,	mipscoxxx,
	"SYNC",		0,	0,
	"MOVW",		0,	"HI,R%d",
	"MOVW",		0,	"R%s,HI",
	"MOVW",		0,	"LO,R%d",
	"MOVW",		0,	"R%s,LO",
	"SLLV",	     sllv,	"R%s,R%t,R%d",
	"special15",	0,	mipscoxxx,
	"SRLV",	     sllv,	"R%s,R%t,R%d",
	"SRAV",	     sllv,	"R%s,R%t,R%d",
	"MUL",		0,	mipsrtrs,
	"MULU",		0,	mipsrtrs,
	"DIV",		0,	mipsrtrs,
	"DIVU",		0,	mipsrtrs,
	"MULV",		0,	mipsrtrs,
	"MULVU",	0,	mipsrtrs,
	"DIVV",		0,	mipsrtrs,
	"DIVVU",	0,	mipsrtrs,
	"ADD",	      add,	mipsalu3op,
	"ADDU",	      add,	mipsalu3op,
	"SUB",	      sub,	mipsalu3op,
	"SUBU",	      sub,	mipsalu3op,
	"AND",	      add,	mipsalu3op,
	"OR",	       or,	mipsalu3op,
	"XOR",	      add,	mipsalu3op,
	"NOR",	      nor,	mipsalu3op,
	"special28",	0,	mipscoxxx,
	"special29",	0,	mipscoxxx,
	"SGT",		0,	mipsalu3op,
	"SGTU",		0,	mipsalu3op,
	"ADDV",		  add,	mipsalu3op,
	"ADDVU",	  add,	mipsalu3op,
	"SUBV",		  sub,	mipsalu3op,
	"SUBVU",	  sub,	mipsalu3op,
	"tge",		0,	mipscorsrt,
	"tgeu",		0,	mipscorsrt,
	"tlt",		0,	mipscorsrt,
	"tltu",		0,	mipscorsrt,
	"teq",		0,	mipscorsrt,
	"special35",	0,	mipscoxxx,
	"tne",		0,	mipscorsrt,
	"special37",	0,	mipscoxxx,
	"SLLV",	      sll,	"$%a,R%t,R%d",
	"special39",	0,	mipscoxxx,
	"SRLV",	      sll,	"$%a,R%t,R%d",
	"SRAV",	      sll,	"$%a,R%t,R%d",
	"SLLV",	     sl32,	"$%a,R%t,R%d",
	"special3D",	0,	mipscoxxx,
	"SRLV",	     sl32,	"$%a,R%t,R%d",
	"SRAV",	     sl32,	"$%a,R%t,R%d",
};

static Opcode ropcodes[32] = {
	"BLTZ",	   branch,	0,
	"BGEZ",	   branch,	0,
	"BLTZL",   branch,	0,
	"BGEZL",   branch,	0,
	"regimm04",	0,	mipscoxxx,
	"regimm05",	0,	mipscoxxx,
	"regimm06",	0,	mipscoxxx,
	"regimm07",	0,	mipscoxxx,
	"tgei",		0,	mipscorsi,
	"tgeiu",	0,	mipscorsi,
	"tlti",		0,	mipscorsi,
	"tltiu",	0,	mipscorsi,
	"teqi",		0,	mipscorsi,
	"regimm0D",	0,	mipscoxxx,
	"tnei",		0,	mipscorsi,
	"regimm0F",	0,	mipscoxxx,
	"BLTZAL",  branch,	0,
	"BGEZAL",  branch,	0,
	"BLTZALL", branch,	0,
	"BGEZALL", branch,	0,
	"regimm14",	0,	mipscoxxx,
	"regimm15",	0,	mipscoxxx,
	"regimm16",	0,	mipscoxxx,
	"regimm17",	0,	mipscoxxx,
	"regimm18",	0,	mipscoxxx,
	"regimm19",	0,	mipscoxxx,
	"regimm1A",	0,	mipscoxxx,
	"regimm1B",	0,	mipscoxxx,
	"regimm1C",	0,	mipscoxxx,
	"regimm1D",	0,	mipscoxxx,
	"regimm1E",	0,	mipscoxxx,
	"regimm1F",	0,	mipscoxxx,
};

static Opcode fopcodes[64] = {
	"ADD%f",	0,	mipsfp3,
	"SUB%f",	0,	mipsfp3,
	"MUL%f",	0,	mipsfp3,
	"DIV%f",	0,	mipsfp3,
	"sqrt.%f",	0,	mipscofp2,
	"ABS%f",	0,	mipsfp2,
	"MOV%f",	0,	mipsfp2,
	"NEG%f",	0,	mipsfp2,
	"round.l.%f",	0,	mipscofp2,
	"trunc.l.%f",	0,	mipscofp2,
	"ceil.l.%f",	0,	mipscofp2,
	"floor.l.%f",	0,	mipscofp2,
	"round.w.%f",	0,	mipscofp2,
	"trunc.w.%f",	0,	mipscofp2,
	"ceil.w.%f",	0,	mipscofp2,
	"floor.w.%f",	0,	mipscofp2,
	"finstr10",	0,	mipscoxxx,
	"finstr11",	0,	mipscoxxx,
	"finstr12",	0,	mipscoxxx,
	"finstr13",	0,	mipscoxxx,
	"finstr14",	0,	mipscoxxx,
	"finstr15",	0,	mipscoxxx,
	"finstr16",	0,	mipscoxxx,
	"finstr17",	0,	mipscoxxx,
	"finstr18",	0,	mipscoxxx,
	"finstr19",	0,	mipscoxxx,
	"finstr1A",	0,	mipscoxxx,
	"finstr1B",	0,	mipscoxxx,
	"finstr1C",	0,	mipscoxxx,
	"finstr1D",	0,	mipscoxxx,
	"finstr1E",	0,	mipscoxxx,
	"finstr1F",	0,	mipscoxxx,
	"cvt.s.%f",	0,	mipscofp2,
	"cvt.d.%f",	0,	mipscofp2,
	"cvt.e.%f",	0,	mipscofp2,
	"cvt.q.%f",	0,	mipscofp2,
	"cvt.w.%f",	0,	mipscofp2,
	"cvt.l.%f",	0,	mipscofp2,
	"finstr26",	0,	mipscoxxx,
	"finstr27",	0,	mipscoxxx,
	"finstr28",	0,	mipscoxxx,
	"finstr29",	0,	mipscoxxx,
	"finstr2A",	0,	mipscoxxx,
	"finstr2B",	0,	mipscoxxx,
	"finstr2C",	0,	mipscoxxx,
	"finstr2D",	0,	mipscoxxx,
	"finstr2E",	0,	mipscoxxx,
	"finstr2F",	0,	mipscoxxx,
	"c.f.%f",	0,	mipscofpc,
	"c.un.%f",	0,	mipscofpc,
	"CMPEQ%f",	0,	mipsfpc,
	"c.ueq.%f",	0,	mipscofpc,
	"c.olt.%f",	0,	mipscofpc,
	"c.ult.%f",	0,	mipscofpc,
	"c.ole.%f",	0,	mipscofpc,
	"c.ule.%f",	0,	mipscofpc,
	"c.sf.%f",	0,	mipscofpc,
	"c.ngle.%f",	0,	mipscofpc,
	"c.seq.%f",	0,	mipscofpc,
	"c.ngl.%f",	0,	mipscofpc,
	"CMPGT%f",	0,	mipsfpc,
	"c.nge.%f",	0,	mipscofpc,
	"CMPGE%f",	0,	mipsfpc,
	"c.ngt.%f",	0,	mipscofpc,
};

static char *cop0regs[32] = {
	"INDEX", "RANDOM", "TLBPHYS", "EntryLo0",
	"CONTEXT", "PageMask", "Wired",	"Error",
	"BADVADDR", "Count", "TLBVIRT", "Compare",
	"STATUS", "CAUSE", "EPC", "PRID",
	"Config", "LLadr", "WatchLo", "WatchHi",
	"20", "21", "22", "23",
	"24", "25", "26", "CacheErr",
	"TagLo", "TagHi", "ErrorEPC", "31"
};

static char fsub[16] = {
	'F', 'D', 'e', 'q', 'W', '?', '?', '?',
	'?', '?', '?', '?', '?', '?', '?', '?'
};

static char *cacheps[] = {
	"I", "D", "SI", "SD"
};

static char *cacheop[] = {
	"IWBI", "ILT", "IST", "CDE", "HI", "HWBI", "HWB", "HSV"
};

static void
format(char *mnemonic, Instr *i, char *f)
{
	if (mnemonic)
		format(0, i, mnemonic);
	if (f == 0)
		return;
	if (mnemonic)
		if (i->curr < i->end)
			*i->curr++ = '\t';
	for ( ; *f && i->curr < i->end; f++) {
		if (*f != '%') {
			*i->curr++ = *f;
			continue;
		}
		switch (*++f) {

		case 's':
			bprint(i, "%d", i->rs);
			break;

		case 't':
			bprint(i, "%d", i->rt);
			break;

		case 'd':
			bprint(i, "%d", i->rd);
			break;

		case 'a':
			bprint(i, "%d", i->sa);
			break;

		case 'l':
			bprint(i, "%lx(R%d)",i->immediate, i->rs);
			break;

		case 'i':
			bprint(i, "$%lx", i->immediate);
			break;

		case 'u':
			i->curr += symoff(i->curr, i->end-i->curr, i->immediate, CANY);
			bprint(i, "(SB)");
			break;

		case 'j':
			i->curr += symoff(i->curr, i->end-i->curr,
				(i->target<<2)|(i->addr & 0xF0000000), CANY);
			bprint(i, "(SB)");
			break;

		case 'b':
			i->curr += symoff(i->curr, i->end-i->curr,
				(i->immediate<<2)+i->addr+4, CANY);
			break;

		case 'c':
			bprint(i, "$%lx", i->cofun);
			break;

		case 'w':
			bprint(i, "[%lux]", i->w0);
			break;

		case 'm':
			bprint(i, "M(%s)", cop0regs[i->rd]);
			break;

		case 'f':
			*i->curr++ = fsub[i->rs & 0x0F];
			break;

		case 'C':
			bprint(i, "%s%s", cacheps[i->rt & 3], cacheop[(i->rt>>2) & 7]);
			break;

		case '\0':
			*i->curr++ = '%';
			return;

		default:
			bprint(i, "%%%c", *f);
			break;
		}
	}
	*i->curr = 0;
}

static void
copz(int cop, Instr *i)
{
	char *f, *m, buf[16];

	m = buf;
	f = "%t,%d";
	switch (i->rs) {

	case 0:
		snprint(buf, sizeof buf, "mfc%d", cop);
		break;

	case 2:
		snprint(buf, sizeof buf, "cfc%d", cop);
		break;

	case 4:
		snprint(buf, sizeof buf, "mtc%d", cop);
		break;

	case 6:
		snprint(buf, sizeof buf, "ctc%d", cop);
		break;

	case 8:
		f = "%b";
		switch (i->rt) {

		case 0:
			snprint(buf, sizeof buf, "bc%df", cop);
			break;

		case 1:
			snprint(buf, sizeof buf, "bc%dt", cop);
			break;

		case 2:
			snprint(buf, sizeof buf, "bc%dfl", cop);
			break;

		case 3:
			snprint(buf, sizeof buf, "bc%dtl", cop);
			break;

		default:
			snprint(buf, sizeof buf, "cop%d", cop);
			f = mipscoxxx;
			break;
		}
		break;

	default:
		snprint(buf, sizeof buf, "cop%d", cop);
		if (i->rs & 0x10)
			f = "function %c";
		else
			f = mipscoxxx;
		break;
	}
	format(m, i, f);
}

static void
cop0(Instr *i)
{
	char *m = 0;

	if (i->rs < 8) {
		switch (i->rs) {

		case 0:
		case 1:
			format("MOVW", i, "%m,R%t");
			return;

		case 4:
		case 5:
			format("MOVW", i, "R%t,%m");
			return;
		}
	}
	else if (i->rs >= 0x10) {
		switch (i->cofun) {
	
		case 1:
			m = "TLBR";
			break;
	
		case 2:
			m = "TLBWI";
			break;
	
		case 6:
			m = "TLBWR";
			break;
	
		case 8:
			m = "TLBP";
			break;
	
		case 16:
			m = "RFE";
			break;
	
		case 24:
			m = "ERET";
			break;

		case 32:
			m = "WAIT";
			break;
		}
		if (m) {
			format(m, i, 0);
			return;
		}
	}
	copz(0, i);
}

static void
cop1(Instr *i)
{
	char *m = "MOVW";

	switch (i->rs) {

	case 0:
		format(m, i, "F%d,R%t");
		return;

	case 2:
		format(m, i, "FCR%d,R%t");
		return;

	case 4:
		format(m, i, "R%t,F%d");
		return;

	case 6:
		format(m, i, "R%t,FCR%d");
		return;

	case 8:
		switch (i->rt) {

		case 0:
			format("BFPF", i, "%b");
			return;

		case 1:
			format("BFPT", i, "%b");
			return;
		}
		break;
	}
	copz(1, i);
}

static int
printins(Map *map, uvlong pc, char *buf, int n)
{
	Instr i;
	Opcode *o;
	uchar op;

	i.curr = buf;
	i.end = buf+n-1;
	mymap = map;
	if (mkinstr(pc, &i) < 0)
		return -1;
	switch (i.op) {

	case 0x00:					/* SPECIAL */
		o = sopcodes;
		op = i.function;
		break;

	case 0x01:					/* REGIMM */
		o = ropcodes;
		op = i.rt;
		break;

	case 0x10:					/* COP0 */
		cop0(&i);
		return i.size*4;

	case 0x11:					/* COP1 */
		if (i.rs & 0x10) {
			o = fopcodes;
			op = i.function;
			break;
		}
		cop1(&i);
		return i.size*4;

	case 0x12:					/* COP2 */
	case 0x13:					/* COP3 */
		copz(i.op-0x10, &i);
		return i.size*4;

	default:
		o = opcodes;
		op = i.op;
		break;
	}
	if (o[op].f)
		(*o[op].f)(&o[op], &i);
	else
		format(o[op].mnemonic, &i, o[op].ken);
	return i.size*4;
}

extern	int	_mipscoinst(Map *, uvlong, char*, int);

	/* modifier 'I' toggles the default disassembler type */
static int
mipsinst(Map *map, uvlong pc, char modifier, char *buf, int n)
{
	if ((asstype == AMIPSCO && modifier == 'i')
		|| (asstype == AMIPS && modifier == 'I'))
		return _mipscoinst(map, pc, buf, n);
	else
		return printins(map, pc, buf, n);
}

static int
mipsdas(Map *map, uvlong pc, char *buf, int n)
{
	Instr i;

	i.curr = buf;
	i.end = buf+n;
	mymap = map;
	if (mkinstr(pc, &i) < 0)
		return -1;
	if (i.end-i.curr > 8)
		i.curr = _hexify(buf, i.w0, 7);
	if (i.size == 2 && i.end-i.curr > 9) {
		*i.curr++ = ' ';
		i.curr = _hexify(i.curr, i.w1, 7);
	}
	*i.curr = 0;
	return i.size*4;
}

static int
mipsinstlen(Map *map, uvlong pc)
{
	Instr i;

	mymap = map;
	if (mkinstr(pc, &i) < 0)
		return -1;
	return i.size*4;
}

static int
mipsfoll(Map *map, uvlong pc, Rgetter rget, uvlong *foll)
{
	ulong w, l;
	char buf[8];
	Instr i;

	mymap = map;
	if (mkinstr(pc, &i) < 0)
		return -1;
	w = i.w0;
	if((w&0xF3600000) == 0x41000000){	/* branch on coprocessor */
    Conditional:
		foll[0] = pc+8;
		l = ((w&0xFFFF)<<2);
		if(w & 0x8000)
			l |= 0xFFFC0000;
		foll[1] = pc+4 + l;
		return 2;
	}

	l = (w&0xFC000000)>>26;
	switch(l){
	case 0:		/* SPECIAL */
		if((w&0x3E) == 0x08){	/* JR, JALR */
			snprint(buf, sizeof buf, "R%ld", (w>>21)&0x1F);
			foll[0] = (*rget)(map, buf);
			return 1;
		}
		foll[0] = pc+i.size*4;
		return 1;
	case 0x30:	/* Load-Linked followed by NOP, STC */
		foll[0] = pc+12;
		return 1;
	case 1:		/* BCOND */
	case 4:		/* BEQ */
	case 20:	/* BEQL */
	case 5:		/* BNE */
	case 21:	/* BNEL */
	case 6:		/* BLEZ */
	case 22:	/* BLEZL */
	case 7:		/* BGTZ */
	case 23:	/* BGTZL */
		goto Conditional;
	case 2:		/* J */
	case 3:		/* JAL */
		foll[0] = (pc&0xF0000000) | ((w&0x03FFFFFF)<<2);
		return 1;
	}

	foll[0] = pc+i.size*4;
	return 1;
}
