#include <u.h>
#include <libc.h>
#include <bio.h>
#include <mach.h>

	/* mips native disassembler */

typedef struct {
	uvlong addr;			/* pc of instr */
	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;
	char *curr;			/* current fill point */
	char *end;			/* end of buffer */
	char *err;
} Instr;

typedef struct {
	char *mnemonic;
	char *mipsco;
} Opcode;

static char mipscoload[] = "r%t,%l";
static char mipscoalui[] = "r%t,r%s,%i";
static char mipscoalu3op[] = "r%d,r%s,r%t";
static char mipscoboc[] = "r%s,r%t,%b";
static char mipscoboc0[] = "r%s,%b";
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 mipscofp2[] = "f%a,f%d";		/* fd,fs */
static char mipscofpc[] = "f%d,f%t";		/* fs,ft */

static Opcode opcodes[64] = {
	0,		0,
	0,		0,
	"j",		"%j",
	"jal",		"%j",
	"beq",		mipscoboc,
	"bne",		mipscoboc,
	"blez",		mipscoboc0,
	"bgtz",		mipscoboc0,
	"addi",		mipscoalui,
	"addiu",	mipscoalui,
	"slti",		mipscoalui,
	"sltiu",	mipscoalui,
	"andi",		mipscoalui,
	"ori",		mipscoalui,
	"xori",		mipscoalui,
	"lui",		"r%t,%u",
	"cop0",		0,
	"cop1",		0,
	"cop2",		0,
	"cop3",		0,
	"beql",		mipscoboc,
	"bnel",		mipscoboc,
	"blezl",	mipscoboc0,
	"bgtzl",	mipscoboc0,
	"instr18",	mipscoxxx,
	"instr19",	mipscoxxx,
	"instr1A",	mipscoxxx,
	"instr1B",	mipscoxxx,
	"instr1C",	mipscoxxx,
	"instr1D",	mipscoxxx,
	"instr1E",	mipscoxxx,
	"instr1F",	mipscoxxx,
	"lb",		mipscoload,
	"lh",		mipscoload,
	"lwl",		mipscoload,
	"lw",		mipscoload,
	"lbu",		mipscoload,
	"lhu",		mipscoload,
	"lwr",		mipscoload,
	"instr27",	mipscoxxx,
	"sb",		mipscoload,
	"sh",		mipscoload,
	"swl",		mipscoload,
	"sw",		mipscoload,
	"instr2C",	mipscoxxx,
	"instr2D",	mipscoxxx,
	"swr",		mipscoload,
	"cache",	"",
	"ll",		mipscoload,
	"lwc1",		mipscoload,
	"lwc2",		mipscoload,
	"lwc3",		mipscoload,
	"instr34",	mipscoxxx,
	"ld",		mipscoload,
	"ld",		mipscoload,
	"ld",		mipscoload,
	"sc",		mipscoload,
	"swc1",		mipscoload,
	"swc2",		mipscoload,
	"swc3",		mipscoload,
	"instr3C",	mipscoxxx,
	"sd",		mipscoload,
	"sd",		mipscoload,
	"sd",		mipscoload,
};

static Opcode sopcodes[64] = {
	"sll",		"r%d,r%t,$%a",
	"special01",	mipscoxxx,
	"srl",		"r%d,r%t,$%a",
	"sra",		"r%d,r%t,$%a",
	"sllv",		"r%d,r%t,R%s",
	"special05",	mipscoxxx,
	"srlv",		"r%d,r%t,r%s",
	"srav",		"r%d,r%t,r%s",
	"jr",		"r%s",
	"jalr",		"r%d,r%s",
	"special0A",	mipscoxxx,
	"special0B",	mipscoxxx,
	"syscall",	"",
	"break",	"",
	"special0E",	mipscoxxx,
	"sync",		"",
	"mfhi",		"r%d",
	"mthi",		"r%s",
	"mflo",		"r%d",
	"mtlo",		"r%s",
	"special14",	mipscoxxx,
	"special15",	mipscoxxx,
	"special16",	mipscoxxx,
	"special17",	mipscoxxx,
	"mult",		mipscorsrt,
	"multu",	mipscorsrt,
	"div",		mipscorsrt,
	"divu",		mipscorsrt,
	"special1C",	mipscoxxx,
	"special1D",	mipscoxxx,
	"special1E",	mipscoxxx,
	"special1F",	mipscoxxx,
	"add",		mipscoalu3op,
	"addu",		mipscoalu3op,
	"sub",		mipscoalu3op,
	"subu",		mipscoalu3op,
	"and",		mipscoalu3op,
	"or",		mipscoalu3op,
	"xor",		mipscoalu3op,
	"nor",		mipscoalu3op,
	"special28",	mipscoxxx,
	"special29",	mipscoxxx,
	"slt",		mipscoalu3op,
	"sltu",		mipscoalu3op,
	"special2C",	mipscoxxx,
	"special2D",	mipscoxxx,
	"special2E",	mipscoxxx,
	"special2F",	mipscoxxx,
	"tge",		mipscorsrt,
	"tgeu",		mipscorsrt,
	"tlt",		mipscorsrt,
	"tltu",		mipscorsrt,
	"teq",		mipscorsrt,
	"special35",	mipscoxxx,
	"tne",		mipscorsrt,
	"special37",	mipscoxxx,
	"special38",	mipscoxxx,
	"special39",	mipscoxxx,
	"special3A",	mipscoxxx,
	"special3B",	mipscoxxx,
	"special3C",	mipscoxxx,
	"special3D",	mipscoxxx,
	"special3E",	mipscoxxx,
	"special3F",	mipscoxxx,
};

static Opcode ropcodes[32] = {
	"bltz",		mipscoboc0,
	"bgez",		mipscoboc0,
	"bltzl",	mipscoboc0,
	"bgezl",	mipscoboc0,
	"regimm04",	mipscoxxx,
	"regimm05",	mipscoxxx,
	"regimm06",	mipscoxxx,
	"regimm07",	mipscoxxx,
	"tgei",		mipscorsi,
	"tgeiu",	mipscorsi,
	"tlti",		mipscorsi,
	"tltiu",	mipscorsi,
	"teqi",		mipscorsi,
	"regimm0D",	mipscoxxx,
	"tnei",		mipscorsi,
	"regimm0F",	mipscoxxx,
	"bltzal",	mipscoboc0,
	"bgezal",	mipscoboc0,
	"bltzall",	mipscoboc0,
	"bgezall",	mipscoboc0,
	"regimm14",	mipscoxxx,
	"regimm15",	mipscoxxx,
	"regimm16",	mipscoxxx,
	"regimm17",	mipscoxxx,
	"regimm18",	mipscoxxx,
	"regimm19",	mipscoxxx,
	"regimm1A",	mipscoxxx,
	"regimm1B",	mipscoxxx,
	"regimm1C",	mipscoxxx,
	"regimm1D",	mipscoxxx,
	"regimm1E",	mipscoxxx,
	"regimm1F",	mipscoxxx,
};

static Opcode fopcodes[64] = {
	"add.%f",	mipscofp3,
	"sub.%f",	mipscofp3,
	"mul.%f",	mipscofp3,
	"div.%f",	mipscofp3,
	"sqrt.%f",	mipscofp2,
	"abs.%f",	mipscofp2,
	"mov.%f",	mipscofp2,
	"neg.%f",	mipscofp2,
	"finstr08",	mipscoxxx,
	"finstr09",	mipscoxxx,
	"finstr0A",	mipscoxxx,
	"finstr0B",	mipscoxxx,
	"round.w.%f",	mipscofp2,
	"trunc.w%f",	mipscofp2,
	"ceil.w%f",	mipscofp2,
	"floor.w%f",	mipscofp2,
	"finstr10",	mipscoxxx,
	"finstr11",	mipscoxxx,
	"finstr12",	mipscoxxx,
	"finstr13",	mipscoxxx,
	"finstr14",	mipscoxxx,
	"finstr15",	mipscoxxx,
	"finstr16",	mipscoxxx,
	"finstr17",	mipscoxxx,
	"finstr18",	mipscoxxx,
	"finstr19",	mipscoxxx,
	"finstr1A",	mipscoxxx,
	"finstr1B",	mipscoxxx,
	"finstr1C",	mipscoxxx,
	"finstr1D",	mipscoxxx,
	"finstr1E",	mipscoxxx,
	"finstr1F",	mipscoxxx,
	"cvt.s.%f",	mipscofp2,
	"cvt.d.%f",	mipscofp2,
	"cvt.e.%f",	mipscofp2,
	"cvt.q.%f",	mipscofp2,
	"cvt.w.%f",	mipscofp2,
	"finstr25",	mipscoxxx,
	"finstr26",	mipscoxxx,
	"finstr27",	mipscoxxx,
	"finstr28",	mipscoxxx,
	"finstr29",	mipscoxxx,
	"finstr2A",	mipscoxxx,
	"finstr2B",	mipscoxxx,
	"finstr2C",	mipscoxxx,
	"finstr2D",	mipscoxxx,
	"finstr2E",	mipscoxxx,
	"finstr2F",	mipscoxxx,
	"c.f.%f",	mipscofpc,
	"c.un.%f",	mipscofpc,
	"c.eq.%f",	mipscofpc,
	"c.ueq.%f",	mipscofpc,
	"c.olt.%f",	mipscofpc,
	"c.ult.%f",	mipscofpc,
	"c.ole.%f",	mipscofpc,
	"c.ule.%f",	mipscofpc,
	"c.sf.%f",	mipscofpc,
	"c.ngle.%f",	mipscofpc,
	"c.seq.%f",	mipscofpc,
	"c.ngl.%f",	mipscofpc,
	"c.lt.%f",	mipscofpc,
	"c.nge.%f",	mipscofpc,
	"c.le.%f",	mipscofpc,
	"c.ngt.%f",	mipscofpc,
};

static char fsub[16] = {
	's', 'd', 'e', 'q', 'w', '?', '?', '?',
	'?', '?', '?', '?', '?', '?', '?', '?'
};


static int
mkinstr(Instr *i, Map *map, uvlong pc)
{
	ulong w;

	if (get4(map, pc, &w) < 0) {
		werrstr("can't read instruction: %r");
		return -1;
	}
	i->addr = pc;
	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;
}

#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);
}

static void
format(char *mnemonic, Instr *i, char *f)
{
	if (mnemonic)
		format(0, i, mnemonic);
	if (f == 0)
		return;
	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':
			if (i->rs == 30) {
				i->curr += symoff(i->curr, i->end-i->curr, i->immediate+mach->sb, CANY);
				bprint(i, "(SB)");
			} else 
				bprint(i, "%lx(r%d)", i->immediate, i->rs);
			break;

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

		case 'u':
			*i->curr++ = '$';
			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, "%lux", i->cofun);
			break;

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

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

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

		default:
			bprint(i, "%%%c", *f);
			break;
		}
	}
}

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

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

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

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

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

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

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

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

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

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

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

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

	default:
		sprint(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 >= 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);
			if (i->curr < i->end)
				*i->curr++ = 0;
			return;
		}
	}
	copz(0, i);
}

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

	i.curr = buf;
	i.end = buf+n-1;
	if (mkinstr(&i, map, pc) < 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 4;

	case 0x11:					/* COP1 */
		if (i.rs & 0x10) {
			o = fopcodes;
			op = i.function;
			break;
		}
		/*FALLTHROUGH*/
	case 0x12:					/* COP2 */
	case 0x13:					/* COP3 */
		copz(i.op-0x10, &i);
		return 4;

	default:
		o = opcodes;
		op = i.op;
		break;
	}
	format(o[op].mnemonic, &i, o[op].mipsco);
	return 4;
}
