#include <u.h>
#include <libc.h>
#include <fis.h>
#include "atazz.h"
#include "tabs.h"

#pragma	varargck	argpos	eprint	1
#pragma	varargck	type	"π"	char**

enum {
	Dontread	= -2,
};

int	interrupted;
int	rflag;
int	squelch;
int	scttrace;
uchar	issuetr[0x100];

Atatab	*idcmd;
Atatab	*idpktcmd;
Atatab	*sigcmd;
Atatab	*sctread;
Atatab	*sctissue;

int
πfmt(Fmt *f)
{
	char **p;

	p = va_arg(f->args, char**);
	if(p == nil)
		return fmtstrcpy(f, "<nil**>");
	for(; *p; p++){
		fmtstrcpy(f, *p);
		if(p[1] != nil)
			fmtstrcpy(f, " ");
	}
	return 0;
}

int
eprint(char *fmt, ...)
{
	int n;
	va_list args;

	if(squelch)
		return 0;
//	Bflush(&out);

	va_start(args, fmt);
	n = vfprint(2, fmt, args);
	va_end(args);
	return n;
}

void
fisset(Req *r, uint i, uint v)
{
	if(r->fisbits & 1<<i)
		return;
	r->fisbits |= 1<<i;
	r->cmd.fis[i] = v;
}

void
prreq(Req *r)
{
	uchar *u;

	print("%.2ux %.2ux\n", r->cmd.sdcmd, r->cmd.ataproto);
	u = r->cmd.fis;
	print("%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux ",
		u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7]);
	u += 8;
	print("%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux\n",
		u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7]);
}

char*
protostr(char *p, char *e, int pr)
{
	char *s;

	*p = 0;
	if(pr & P28)
		p = seprint(p, e, "28:");
	else
		p = seprint(p, e, "28:");
	switch(pr & Pprotom){
	default:
		s = "unk";
		break;
	case Ppkt:
		s = "pkt";
		break;
	case Pdiag:
		s = "dig";
		break;
	case Preset:
		s = "rst";
		break;
	case Pdmq:
		s = "dmq";
		break;
	case Pdma:
		s = "dma";
		break;
	case Ppio:
		s = "pio";
		break;
	}
	p = seprint(p, e, "%s:", s);
	switch(pr & Pdatam){
	default:
		s = "nd";
		break;
	case Pin:
		s = "in";
		break;
	case Pout:
		s = "out";
		break;
	}
	p = seprint(p, e, "%s", s);
	return p;
}

void
displaycmd(Req *r, Atatab *a)
{
	char buf[32];
	int i;

	if(a->cc > nelem(issuetr) || !issuetr[a->cc])
		return;
	protostr(buf, buf + sizeof buf, a->protocol);
	fprint(2, "cmd %s:%2ux ", buf, a->cc);
	for(i = 0; i < 16; i++)
		fprint(2, "%.2ux", r->cmd.fis[i]);
	fprint(2, "\n");
}

int
issueata(Req *r, Atatab *a, Dev *d)
{
	uchar u;
	int n, ok, pr, rv;

	r->haverfis = 0;
	pr = a->protocol & Pdatam;
	r->data = realloc(r->data, r->count);
	if(r->data == nil && r->count > 0)
		sysfatal("realloc: %r");
	if(r->data == nil && pr != Pnd)
		sysfatal("no data for cmd %.2ux", a->cc);
	if(0 && r->fisbits)
		print("fisbits %.16b\n", r->fisbits);
	r->cmd.sdcmd = 0xff;
	r->cmd.ataproto = a->protocol;
	if(a->cc & 0xff00)
		fisset(r, 0, a->cc >> 8);
	else
		fisset(r, 0, H2dev);
	fisset(r, 1, Fiscmd);
	fisset(r, 2, a->cc);
	switch(pr){
	case Pout:
		if(r->rfd != Dontread){
			n = readn(r->rfd, r->data, r->count);
			if(n != r->count){
				if(n == -1)
					eprint("!short src read %r\n");
				else
					eprint("!short src read %d wanted %lld\n", n, r->count);
				return -1;
			}
		}
	case Pnd:
	case Pin:
		if(0 && (d->feat & Dlba) == 0 && (d->c | d->h | d->s)){
			int c, h, s;
			c = r->lba / (d->s * d->h);
			h = (r->lba / d->s) % d->h;
			s = (r->lba % d->s) + 1;
print("%d %d %d\n", c, h, s);
			fisset(r, 4, s);
			fisset(r, 5, c);
			fisset(r, 6, c >> 8);
			fisset(r, 7, Ataobs | h);
		}else{
			fisset(r, 4, r->lba);
			fisset(r, 5, r->lba >> 8);
			fisset(r, 6, r->lba >> 16);
			u = Ataobs;
			if(pr == Pin || pr == Pout)
				u |= Atalba;
			if((d->feat & Dllba) == 0)
				u |= (r->lba >> 24) & 7;
			fisset(r, 7, u);
			fisset(r, 8, r->lba >> 24);
			fisset(r, 9, r->lba >> 32);
			fisset(r, 10, r->lba >> 48);
		}
		fisset(r, 12, r->nsect);
		fisset(r, 13, r->nsect >> 8);
		break;
	}
	fisset(r, 7, Ataobs);
	displaycmd(r, a);
	if(write(d->fd, &r->cmd, Cmdsz) != Cmdsz){
		eprint("fis write error: %r\n");
		return -1;
	}

	werrstr("");
	switch(pr){
	default:
		ok = read(d->fd, "", 0) == 0;
		break;
	case Pin:
		ok = read(d->fd, r->data, r->count) == r->count;
		r->lba += r->nsect;
		break;
	case Pout:
		ok = write(d->fd, r->data, r->count) == r->count;
		r->lba += r->nsect;
		break;
	}
	rv = 0;
	if(ok == 0){
		eprint("xfer error: %.2ux %r\n", a->cc);
		rv = -1;
	}
	switch(n = read(d->fd, &r->reply, Replysz)){
	case Replysz:
		r->haverfis = 1;
		return rv;
	case -1:
		eprint("status fis read error: %r\n");
		return -1;
	default:
		eprint("status fis read error: short read: %d of %d\n", n, Replysz);
		return -1;
	}
}

/*
 * cheezy code; just issue a inquiry.  use scuzz
 * for real work with atapi devices
 */
int
issuepkt(Req *r, Atatab *a, Dev *d)
{
	char *p;
	uchar *u;
	int n, rv;

	r->haverfis = 0;
	r->count = 128;
	r->data = realloc(r->data, r->count);
	if(r->data == nil && r->count > 0)
		sysfatal("realloc: %r");
	r->cmd.sdcmd = 0xff;
	r->cmd.ataproto = a->protocol;
	memset(r->cmd.fis, 0, Fissize);

	u = r->cmd.fis;
	u[0] = 0x12;
	u[4] = 128-1;
	displaycmd(r, a);

	if(write(d->fd, &r->cmd, 6 + 2) != 6 + 2){
		eprint("fis write error: %r\n");
		return -1;
	}
	n = read(d->fd, r->data, r->count);
	rv = 0;
	if(n == -1){
		eprint("xfer error: %.2ux %r\n", a->cc);
		rv = -1;
	}

	print("n is %d (%lld)\n", n, r->count);
	if(n > 32){
		p = (char*)r->data;
		print("%.8s %.16s\n", p + 8, p + 16);
	}

	u = (uchar*)&r->reply;
	n = read(d->fd, u, Replysz);
	if(n < 0){
		eprint("status fis read error (%d): %r\n", n);
		return -1;
	}
	
	if(n < Replysz)
		memset(u + n, 0, Replysz - n);
	r->haverfis = 1;
	return rv;
}

/*
 * silly protocol:
 * 1.  use write log ext 0xe0 to fill out the command
 * 2.  use write log ext 0xe1 to write or data (if any)
 * 3.  use read log ext 0xe0 to nab status.  polled
 */
void
sctreq(Req *r)
{
	memset(r, 0, sizeof *r);
	r->rfd = Dontread;
}

char*
sctrsp(Req *r)
{
	uint i;
	static char buf[32];

	if(!r->haverfis)
		return "no rfis";
	if((r->reply.fis[Frerror] & (Eidnf | Eabrt)) == 0)
		return nil;
	i = r->reply.fis[Fsc] | r->reply.fis[Flba0]<<8;
	if(i == 0xffff)
		return "in progress";
	else if(i == 0){
		snprint(buf, sizeof buf, "unknown %.2ux", r->reply.fis[Frerror]);
		return buf;
	}else if(i < nelem(sctetab))
		return sctetab[i];
	else
		return "<bad>";
}

char*
sctready(Dev *d, int sec)
{
	char *s;
	int i;
	Req r;
	static char e[ERRMAX];

	for(;;){
		if(interrupted){
			s = "interrupted";
			break;
		}
		sctreq(&r);
		fisset(&r, Fsc, 1);
		fisset(&r, Flba0, 0xe0);
		r.count = 512;
		i = issueata(&r, sctread, d);
		free(r.data);
		if(i == -1){
			rerrstr(e, ERRMAX);
			s = e;
			break;
		}
		if((r.cmd.fis[Fsc] | r.cmd.fis[Fsc8]<<8) != 0xffff){
			s = sctrsp(&r);
			break;
		}
		if(sec == 0){
			s = "timeout";
			break;
		}
		sleep(1000);
		sec--;
	}
	return s;
}

typedef struct Sttab Sttab;
struct Sttab {
	int	o;
	int	sz;
	char	*name;
};

Sttab sctt[] = {
	0,	2,	"version",
	2,	2,	"period",
	4,	2,	"intval",
	6,	1,	"max op",
	7,	1,	"max",
	8,	1,	"min op",
	9,	1,	"min",
};

void
sctttab(Req *r)
{
	char c, buf[10];
	int i, n, l, d;
	uchar *u;

	u = r->data;
	for(i = 0; i < nelem(sctt); i++){
		switch(sctt[i].sz){
		case 1:
			c = u[sctt[i].o];
			print("%s\t%d\n", sctt[i].name, c);
			break;
		case 2:
			d = w(u + sctt[i].o);
			print("%s\t%ud\n", sctt[i].name, d);
			break;
		}
	}
	n = w(u + 30);
	l = w(u + 32);
	for(i = 0; i < n; i++){
		c = u[34 + (l + i) % n];
		if((uchar)c == 0x80)
			snprint(buf, sizeof buf, "xx");
		else
			snprint(buf, sizeof buf, "%d", c);
		d = i%10;
		if(d == 0)
			print("\nt%d\t%d", i, c);
		else
			print("% .2d", c);
	}
	if(i%10)
		print("\n");
}

static struct {
	uint	code;
	char	*s;
	char	*ms;
} fxtab[] = {
	0x00010001,	"set features",	0,
	0x00010002,	"enabled",	0,
	0x00010003,	"disabled",	0,

	0x00020001,	"enabled",	0,
	0x00020002,	"disabled",	0,

	0x0003ffff,	"minute",	"minutes",
};

void
sctfcout(ushort *u, Req *r)
{
	uchar *f;
	ushort v;
	uint c, m, i;

	f = r->reply.fis;
	switch(u[1]){
	case 1:
	case 2:
		v = f[Fsc] | f[Flba0]<<8;
		c = u[2]<<16 | v;
		m = u[2]<<16 | 0xffff;
		for(i = 0; i < nelem(fxtab); i++)
			if(fxtab[i].code == c)
				print("%s\n", fxtab[i].s);
			else if(fxtab[i].code == m)
				print("%d %s\n", v, v>1? fxtab[i].ms: fxtab[i].s);
		break;
	case 3:
		v = f[Fsc] | f[Flba0]<<8;
		if(v & 1)
			print("preserve\n");
		else
			print("volatile\n");
		break;
	}
}

void
scterout(ushort *u, Req *r)
{
	uchar *f;
	uint v;

	f = r->reply.fis;
	switch(u[1]){
	case 2:
		v = f[Fsc] | f[Flba0]<<8;
		v *= 100;
		print("%dms\n", v);
	}
}

void
sctout(ushort *u, Req *r)
{
	switch(u[0]){
	case 5:
		sctttab(r);
		break;
	case 4:
		sctfcout(u, r);
		break;
	case 3:
		scterout(u, r);
		break;
	}
}

int
issuesct0(Req *r0, Atatab *a, Dev *d)
{
	char *s;
	uchar proto;
	Atatab *txa;
	Req r;

	if((d->feat & Dsct) == 0){
		eprint("sct not supported\n");
		return -1;
	}

	/* 1. issue command */
	sctreq(&r);
	r.data = malloc(r0->count);
	memcpy(r.data, r0->data, r0->count);
	r.count = r0->count;
	fisset(&r, Fsc, 1);
	fisset(&r, Flba0, 0xe0);
	if(issueata(&r, sctissue, d) == -1)
		return -1;
	if(s = sctrsp(&r)){
		eprint("sct error: %s\n", s);
		return -1;
	}

	/* 1a. check response */
	if((s = sctready(d, 1)) != nil){
		eprint("sct cmd: %s\n", s);
		return -1;
	}
	/* 2. transfer data */

	proto = a->protocol;
	if(r0->fisbits & 1 << 16){
		proto &= ~Pdatam;
		proto |= r0->cmd.ataproto;
	}
	switch(proto & Pdatam){
	default:
		txa = nil;
		break;
	case Pin:
		txa = sctread;
		break;
/*	case Pout:
		txa = sctout;
		break;
*/
	}

	if(txa != nil){
		sctreq(&r);
		r.count = 512;
		fisset(&r, Fsc, 1);
		fisset(&r, Flba0, 0xe1);
		if(issueata(&r, txa, d) == -1)
			return -1;

		/* 2a. check response */
		if((s = sctready(d, 1)) != nil){
			eprint("sct cmd: %s\n", s);
			return -1;
		}
	}

	sctout((ushort*)r0->data, &r);
	free(r.data);
	return 0;
}

static void*
pushtrace(int i)
{
	void *tr0;

	tr0 = malloc(sizeof issuetr);
	if(tr0 == 0)
		return 0;
	memcpy(tr0, issuetr, sizeof issuetr);
	memset(issuetr, i, sizeof issuetr);
	return tr0;
}

static void
poptrace(void *tr0)
{
	if(tr0 == nil)
		return;
	memcpy(issuetr, tr0, sizeof issuetr);
	free(tr0);
}

int
issuesct(Req *r0, Atatab *a, Dev *d)
{
	int r;
	void *t;

	t = nil;
	if(scttrace)
		t = pushtrace(1);
	r = issuesct0(r0, a, d);
	if(scttrace)
		poptrace(t);
	return r;
}

int
issue(Req *r, Atatab *a, Dev *d)
{
	int rv;
	int (*f)(Req*, Atatab*, Dev*);

	if(a->protocol & Psct)
		f = issuesct;
	else if((a->protocol & Pprotom) == Ppkt)
		f = issuepkt;
	else
		f = issueata;
	rv = f(r, a, d);
	if(r->haverfis)
	if(r->reply.fis[Fstatus] & ASerr){
		werrstr("ata error");
		rv = -1;
	}
	return rv;
}

void
sigfmt(Req *r)
{
	print("%.8ux\n", fistosig(r->reply.fis));
}

int
opendev(char *dev, Dev *d)
{
	char buf[128];
	int rv;
	ushort *u;
	Req r;

	if(d->fd != -1)
		close(d->fd);
	memset(d, 0, sizeof *d);
	snprint(buf, sizeof buf, "%s/raw", dev);
	d->fd = open(buf, ORDWR);
	if(d->fd == -1)
		return -1;
	memset(&r, 0, sizeof r);
	if(issue(&r, sigcmd, d) == -1){
lose:
		close(d->fd);
		return -1;
	}
	setfissig(d, fistosig(r.reply.fis));
	memset(&r, 0, sizeof r);
	r.count = 512;
	r.nsect = 1;
	if(d->sig>>16 == 0xeb14)
		rv = issue(&r, idpktcmd, d);
	else
		rv = issue(&r, idcmd, d);
	if(rv == -1)
		goto lose;
	u = (ushort*)r.data;
	d->nsect = idfeat(d, u);
	d->secsize = idss(d, u);
	d->wwn = idwwn(d, u);
	return 0;
}

void
rawout(Req *r)
{
	int n;

	n = write(r->wfd, r->data, r->count);
	if(n != r->count)
		eprint("!short write %ud wanted %lld\n", n, r->count);
}

static ushort
gbit16(void *a)
{
	ushort j;
	uchar *i;

	i = a;
	j  = i[1] << 8;
	j |= i[0];
	return j;
}

static Btab extra[] = {
	12,	"ncqpri",
	11,	"ncqunload",
	10,	"phyevent",
	9,	"hpwrctl",
	3,	"6.0gbit",
	2,	"3.0gbit",
	1,	"1.5gbit",
};

static Btab suptab[] = {
	8,	"wwn",
	5,	"mediaserial",
	1,	"smartst",
	0,	"smartlog"
};

char*
pextraid(char *p, char *e, ushort *id, uint *medserial)
{
	char *p0;
	ushort u;

	*p = 0;
	*medserial = 0;
	p0 = p;
	p = sebtab(p, e, extra, nelem(extra), gbit16(id + 76));
	if(p != p0)
		p = seprint(p, e, " ");
	u = gbit16(id + 83);
	if(u & 1<<5)
		p = seprint(p, e, "gpl ");
	p0 = p;
	p = sebtab(p, e, suptab, nelem(suptab), gbit16(id + 84));
	if(p != p0)
		p = seprint(p, e, " ");
	u = gbit16(id + 120);
	if(u & 1<<2)
		p = seprint(p, e, "wunc ");
	return p;
}

static char *patatab[] = {
	"ata8-apt",
	"ata/atapi-7",
};

static char *satatab[] = {
	"ata8-ast",
	"sata1.0a",
	"sataiiext",
	"sata2.5",
	"sata2.6",
	"sata3.0",
};

char*
ptransport(char *p, char *e, ushort *id)
{
	char *s;
	ushort u, i;

	u = gbit16(id + 222);
	if(u == 0 || u == 0xffff)
		return seprint(p, e, "unreported ");
	i = (u>>5) & 0x7f;
	switch(u & 7<<12){
	default:
		s = "unktransport";
		break;
	case 0:
		s = "unkparallel";
		if(i < nelem(patatab))
			s = patatab[i];
		break;
	case 1<<12:
		s = "unkserial";
		if(i < nelem(satatab))
			s = satatab[i];
		break;
	}
	return seprint(p, e, "%s ", s);
}

Btab entab[] = {
	10,	"hpa",
	9,	"reset",
	8,	"service",
	7,	"release",
	6,	"rdlookahd",
	5,	"vwc",
	4,	"packet",
	3,	"pm",
	2,	"security",
	1,	"smart",
};

Btab addlen[] = {
	15,	"cfast",
//	14,	"trim",	/* check 169 */
	13,	"lpsalignerr",
	12,	"iddma",
	11,	"rbufdma",
	10,	"wbufdma",
	9,	"pwddma",
	8,	"dlmcdma",
};

char*
penabled(char *p, char *e, ushort *id)
{
	char *p0;
	ushort u;

	p0 = p;
	p = sebtab(p, e, addlen, nelem(addlen), gbit16(id + 69));
	u = gbit16(id + 87);
	if(u>>14 == 1){
		if(p != p0)
			p = seprint(p, e, " ");
		p = sebtab(p, e, entab, nelem(entab), gbit16(id + 85));
	}
	return p;
}

static char *fftab[] = {
	nil,
	"5¼",
	"3½",
	"2½",
	"1.8",
	"<1.8",
};

char*
pff(char *p, char *e, ushort *id)
{
	char *p0;
	ushort u;

	p0 = p;
	u = gbit16(id + 168);
	if(u < nelem(fftab) && fftab[u] != nil)
		p = seprint(p, e, "%s ", fftab[u]);
	u = gbit16(id + 217);
	if(u == 1)
		p = seprint(p, e, "solid-state ");
	else if(u != 0 && u != 0xfffe)
		p = seprint(p, e, "%udrpm ", u);
	if(p != p0)
		p--;
	*p = 0;
	return p;
}

Btab scttab[] = {
	5,	"tables",
	4,	"feactl",
	3,	"errctl",
	2,	"wsame",
	1,	"rwlong",
	0,	"sct",
};

char*
psct(char *p, char *e, ushort *id)
{
	return sebtab(p, e, scttab, nelem(scttab), gbit16(id + 206));
}

void
idfmt(Req *r)
{
	char buf[100];
	uint ss, i;
	ushort *id;
	uvlong nsect;
	Sfis f;

	if(r->fmtrw == 0){
		rawout(r);
		return;
	}
	id = (ushort*)r->data;
	nsect = idfeat(&f, id);
	ss = idss(&f, id);

	idmove(buf, id+10, 20);
	print("serial\t%s\n", buf);
	idmove(buf, id+23, 8);
	print("firm\t%s\n", buf);
	idmove(buf, id+27, 40);
	print("model\t%s\n", buf);
	print("wwn\t%ullx\n", idwwn(&f, id));
	pflag(buf, buf + sizeof buf, &f);
	print("flags\t%s", buf);
	print("geometry %llud %ud", nsect, ss);
	if(f.c | f.h | f.s)
		print(" %ud %ud %ud", f.c, f.h, f.s);
	print("\n");
	penabled(buf, buf + sizeof buf, id);
	print("enabled\t%s\n", buf);
	pextraid(buf, buf + sizeof buf, id, &i);
	print("extra\t%s\n", buf);
	if(i){
		idmove(buf, id + 176, 60);
		if(buf[0] != 0)
			print("medias\t%s\n", buf);
	}
	psct(buf, buf + sizeof buf, id);
	if(buf[0])
		print("sct\t%s\n", buf);
	ptransport(buf, buf + sizeof buf, id);
	print("trans\t%s\n", buf);
	pff(buf, buf + sizeof buf, id);
	if(buf[0])
		print("ff\t%s\n", buf);
}

void
smfmt(Req *r)
{
	uchar *fis;

	if(r->cmd.fis[Ffeat] == 0xda){
		fis = r->reply.fis;
		if(fis[5] == 0x4f &&
		   fis[6] == 0xc2)
			eprint("normal\n");
		else
			eprint("threshold exceeded\n");
		return;
	}
}

void
iofmt(Req *r)
{
	uchar *u;
	int i;

	if(r->fmtrw == 0){
		rawout(r);
		return;
	}
	u = r->data;
	for(i = 0; i < r->count; i += 16)
		fprint(2, "%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux"
		"%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux\n",
		u[i + 0], u[i + 1], u[i + 2], u[i + 3], u[i + 4], u[i + 5], u[i + 6], u[i + 7],
		u[i + 8], u[i + 9], u[i +10], u[i +11], u[i +12], u[i +13], u[i +14], u[i +15]);
}

static char *csbyte[] = {
	"never started",
	nil,
	"competed without error",
	"in progress",
	"suspended by cmd from host",
	"aborted by cmd from host",
	"aborted by device with fatal error",
};

static char *exe[] = {
	"no error or never run",
	"aborted by host",
	"interrupted by host",
	"fatal error; unable to complete",
	"failed",
	"failed: electricial",
	"failed: servo",
	"failed: read",
	"failed: shipping damage",
[0xf]	"in progress",
};

char*
tabtr(uint u, char **tab, int ntab)
{
	char *s;

	if(u >= ntab || (s = tab[u]) == nil)
		s = "reserved";
	return s;
}

void
sdfmt(Req *r)
{
	char *s;
	uchar *b;
	ushort u;

	if(r->fmtrw == 0){
		rawout(r);
		return;
	}
	b = r->data;
	u = b[362];
	if((u & 0xf0) == 0x80 && u != 0x81 && u != 0x83)
		u &= 0xf;
	s = tabtr(u, csbyte, nelem(csbyte));
	print("col status: %.2ux %s\n", b[362], s);
	u = b[363];
	s = tabtr(u>>4, exe, nelem(exe));
	if(u & 0xf)
		print("exe status: %.2ux %s, %d0%% left\n", u, s, u & 0xf);
	else
		print("exe status: %.2ux %s\n", u, s);
	u = b[364] | b[365]<<8;
	print("time left: %uds\n", u);
	print("shrt poll: %udm\n", b[373]);
	u = b[374];
	if(u == 0xff)
		u = b[375] | b[376]<<8;
	print("ext poll: %udm\n", u);
}

void
pagemapfmt(Req *r)
{
	int i;
	ushort *u;

	u = (ushort*)r->data;
	if(u[0] != 1){
		print("unsupported\n");
		return;
	}
	for(i = 1; i < 128; i++)
		if(u[i] > 0)
			print("page %d: %d\n", i, u[i]);
}

void
slfmt(Req *r)
{
	switch(r->cmd.fis[Flba0]){
	default:
		iofmt(r);
		break;
	case 0:
		pagemapfmt(r);
		break;
	}
}

enum{
	Physz	= 7<<12,
};

static char *phyec[] = {
	"no event",
	"icrc",
	"err data",
	"err d2h data",
	"err h2d data",
[0x05]	"err nd",
	"err d2h nd",
	"err h2d nd",
	"retry d2h nd",
	"nready",
[0x0a]	"comreset",
	"h2d crc",
	nil,
	"bad h2d",
	nil,
	"err h2d data crc",
[0x10]	"err h2d data",
	nil,
	"err h2d nd crc",
	"err h2d nd",
};

void
phyfmt(Req *r)
{
	char *ec;
	uchar *p;
	ushort *u, *e, id, sz;

	u = (ushort*)r->data;
	e = u + 510/sizeof *u;
	for(u += 2; u < e; u += sz){
		id = w((uchar*)u);
		sz = (id & Physz) >> 12;
		id &= ~Physz;
		if(sz == 0)
			break;
		ec = "unk";
		if(id < nelem(phyec) && phyec[id] != nil)
			ec = phyec[id];
		print("%.4ux\t%-15s\t", id, ec);
		p = (uchar*)u + 2;
		switch(sz<<1){
		default:
			print("\n");
			break;
		case 2:
			print("%.4ux\n", w(p));
			break;
		case 4:
			print("%.8ux\n", dw(p));
			break;
		case 8:
			print("%.16llux\n", qw(p));
			break;
		}
		sz += 1;
	}
}

typedef struct Gltab Gltab;
struct Gltab{
	int	offset;
	char	*name;
};

Gltab page3[] = {
	8,	"power-on hrs",
	16,	"head flying hrs",
	24,	"head loads",
	32,	"realloc'd sec",
	40,	"read recovery att",
	48,	"start failures"
};
	
void
qpfmt(Req *r, Gltab *t, int ntab)
{
	uchar *u;
	int i;
	uvlong v;

	u = r->data;
	for(i = 0; i < ntab; i++){
		v = qw(u + t[i].offset);
		if((v & 3ll<<63) != 3ll<<63)
			continue;
		print("%lud\t%s\n", (ulong)v, t[i].name);
	}
}

static char *sctsttab[] = {
	"active waiting",
	"standby",
	"sleep",
	"dst bgnd",
	"smart bgnd",
	"sct bgnd",
};

void
sctstatfmt(Req *r)
{
	char *s;
	uchar *id, c;

	id = r->data;
	print("version\t%d\n", gbit16(id + 0));
	print("vnd ver\t%2ux\n", gbit16(id + 2));
	print("flags\t%.8ux\n", dw(id + 6));
	c = id[10];
	s = "unk";
	if(c < nelem(sctsttab))
		s = sctsttab[c];
	print("state\t%s\n", s);
	print("ext stat\t%.4ux\n", gbit16(id + 14));
	print("act code\t%.4ux\n", gbit16(id + 16));
	print("fn code\t%.4ux\n", gbit16(id + 18));
	print("lba\t%llud\n", qw(id + 40));
	print("temp\t%d\n", id[200]);
	print("min t\t%d %d\n", id[201], id[203]);
	print("max t\t%d %d\n", id[202], id[204]);
	print("ot\t%d\n", dw(id + 206));
	print("ut\t%d\n", dw(id + 210));
}


void
glfmt(Req *r)
{
	switch(r->cmd.fis[Flba0]){
	case 0:
		pagemapfmt(r);
		break;
	case 3:
		qpfmt(r, page3, nelem(page3));
		break;
	case 17:
		phyfmt(r);
		break;
	case 0xe0:
		sctstatfmt(r);
		break;
	default:
		iofmt(r);
		break;
	}
}

char*
readline(char *prompt, char *line, int len)
{
	char *p, *e, *q;
	int n, dump;

	e = line + len;
retry:
	dump = 0;
	if(interrupted)
		eprint("\n%s", prompt);
	else
		eprint("%s", prompt);
	interrupted = 0;
	for(p = line;; p += n){
		if(p == e){
			dump = 1;
			p = line;
		}
		n = read(0, p, e - p);
		if(n < 0){
			if(interrupted)
				goto retry;
			return nil;
		}
		if(n == 0)
			return nil;
		if(q = memchr(p, '\n', n)){
			if(dump){
				eprint("!line too long\n");
				goto retry;
			}
			p = q;
			break;
		}
	}
	*p = 0;
	return line;
}

void
suggesttab(char *cmd, Atatab *a, int n)
{
	int i, l;

	l = strlen(cmd);
	for(i = 0; i < n; i++)
		if(cistrncmp(cmd, a[i].name, l) == 0)
			eprint("%s\n", a[i].name);
}

Atatab*
findtab(char **cmd, Atatab *a, int n)
{
	char *p, *c;
	int i, cc, max, l;

	cc = strtoul(*cmd, &p, 0);
	if(p != *cmd && (*p == 0 || *p == ' ')){
		for(i = 0; i < n; i++)
			if(a[i].cc == cc){
				*cmd = p + 1;
				return a + cc;
			}
		return 0;
	}
	max = 0;
	cc = 0;
	c = *cmd;
	for(i = 0; i < n; i++){
		l = strlen(a[i].name);
		if(l > max && cistrncmp(*cmd, a[i].name, l) == 0)
		if(c[l] == ' ' || c[l] == 0){
			max = l + (c[l] == ' ');
			cc = i;
		}
	}
	if(max > 0){
		*cmd = *cmd + max;
		return a + cc;
	}
	return 0;
}
		
int
catch(void*, char *note)
{
	if(strstr(note, "interrupt") != nil)
		return interrupted = 1;
	return 0;
}

char**
ndargs(Atatab*, Req *, char **p)
{
	return p;
}

char**
ioargs(Atatab *, Req *r, char **p)
{
	if(r->nsect == 0)
		r->nsect = 1;
	if(p[0] == 0)
		return p;
	r->lba = strtoull(p[0], 0, 0);
	p++;
	if(p[0] == 0)
		return p;
	r->nsect = strtoul(p[0], 0, 0);
	return p + 1;
}

char**
stdargs(Atatab *, Req *r, char **p)
{
	char *s;
	Rune x;

	for(; p[0] && p[0][0] == '-' && p[0][1]; p++){
		s = p[0] + 1;
		if(*s == '-'){
			p++;
			break;
		}
		while(*s && (s += chartorune(&x, s)))
		switch(x){
		case 'r':
			r->raw = 1;
			break;
		default:
			return p;
		}
	}
	return p;
}

static void
chopoff(char *s, char *extra)
{
	char *p;
	int l, ls;

	l = strlen(extra);
	ls = strlen(s);
	if(l >= ls)
		return;
	p = s + ls - l;
	if(strcmp(p, extra) == 0)
		*p = 0;
}

char*
trim(char *s)
{
	char *p;

	while(*s && (*s == ' ' || *s == '\t'))
		s++;
	if(*s == 0)
		return nil;
	p = s + strlen(s) - 1;
	while(*p == ' ' || *p == '\t')
		p--;
	p[1] = 0;
	return s;
}

int
doredir(Req *r, char **f, int nf, int mode, int *fd1, int *fd2)
{
	int fd;

	if(nf != 1 && nf != 2){
		eprint("!args\n");
		return -1;
	}
	fd = -1;
	if(nf == 2){
		fd = open(f[1], mode);
		if(mode != OREAD){
			if(fd == -1)
				fd = create(f[1], mode, 0660);
			else
				seek(fd, 0, 2);
		}
	}
	if(fd1){
		close(*fd1);
		*fd1 = fd;
	}
	if(fd2){
		r->fmtrw = fd == -1;
		close(*fd2);
		*fd2 = fd;
	}
	return fd;
}

int
special(char *s, Dev *d, Req *r)
{
	char buf[512], path[128], *f[20], sbuf[512], s2[512], *p, *e, *t;
	uchar *u;
	int i, j, nf;
	Atatab *a;

	p = buf;
	e = buf + sizeof buf;
	if(!strcmp(s, "close")){
		r->haverfis = 0;
		close(d->fd);
		d->fd = -1;
		return 0;
	}
	if(!strcmp(s, "scttrace")){
		scttrace = 1;
		return 0;
	}
	if(!strcmp(s, "dev")){
		if(d->fd == -1){
			eprint("!bad cmd (device closed)\n");
			return 0;
		}
		if(fd2path(d->fd, path, sizeof path) == -1)
			sysfatal("fd2path: %r");
		chopoff(path, "/raw");
		p = seprint(p, e, "dev\t%s\n", path);
		p = seprint(p, e, "flags\t");
		p = pflag(p, e, d);
		p = seprint(p, e, "lsectsz\t" "%ud ptol %ud\n", d->lsectsz, 1<<d->physshift);
		p = seprint(p, e, "geometry %llud %ud\n", d->nsect, d->secsize);
		if(d->c | d->h | d->s)
			seprint(p, e, "chs\t%d %d %d\n", d->c, d->h, d->s);
		print("%s", buf);
		return 0;
	}
	if(!strcmp(s, "help")){
		suggesttab(buf, atatab, nelem(atatab));
		return 0;
	}
	if(!strcmp(s, "probe")){
		probe();
		return 0;
	}
	if(!strcmp(s, "rfis")){
		if(r->haverfis == 0){
			eprint("!no rfis\n");
			return 0;
		}
		p = seprint(p, e, "%.2x\n", r->reply.sdcmd);
		u = r->reply.fis;
		for(i = 0; i < 16; i++)
			p = seprint(p, e, "%.2ux", u[i]);
		seprint(p, e, "\n");
		print("%s", buf);
		return 0;
	}
	for(t = s; *t == '<' || *t == '>'; t++)
		;
	if(t != s)
		snprint(sbuf, sizeof buf, "%.*s %s", utfnlen(s, t - s), s, t);
	else
		snprint(sbuf, sizeof sbuf, "%s", s);
	nf = tokenize(sbuf, f, nelem(f));
	if(!strcmp(f[0], "issuetr")){
		if(nf == 1)
			for(i = 0; i < nelem(issuetr); i++)
				issuetr[i] ^= 1;
		else{
			p = s2;
			e = s2 + sizeof s2;
			for(i = 1; i < nf - 1; i++)
				p = seprint(p, e, "%s ", f[i]);
			p = seprint(p, e, "%s", f[i]);
			e = s2;
			for(i = 1; i < nf; i++){
				j = strtoul(f[i], &p, 0);
				if(*p == 0 && j < nelem(issuetr))
					issuetr[i] ^= 1;
				else if(a = findtab(&e, atatab, nelem(atatab)))
					issuetr[a->cc & 0xff] ^= 1;
			}
		}
		return 0;
	}
	if(!strcmp(f[0], "open")){
		r->lba = 0;
		if(nf == 2)
			opendev(f[1], d);
		else
			eprint("!bad args to open\n");
		return 0;
	}
	if(!strcmp(f[0], ">")){
		doredir(r, f, nf, OWRITE, 0, &r->wfd);
		return 0;
	}
	if(!strcmp(f[0], "<")){
		doredir(r, f, nf, OREAD, &r->rfd, 0);
		return 0;
	}
	if(!strcmp(f[0], "<>")){
		doredir(r, f, nf, OWRITE, &r->rfd, &r->wfd);
		return 0;
	}
	return -1;
}

void
setreg(Req *r, uint reg, uvlong v)
{
	uchar *o;
	int x;

	switch(reg & (Sbase | Pbase)){
	case 0:
		r->fisbits |= 1 << reg;
		r->cmd.fis[reg] = v;
		break;
	case Sbase:
	case Sbase | Pbase:
		x = reg & ~(Sbase | Ssz);
		o = r->data + x*2;
		assert(x < r->count);
		switch(reg & Ssz){
		default:
			print("reg & Ssz %ux\n", reg & Ssz);
			_assert("bad table");
		case Sw:
			pw(o, v);
			break;
		case Sdw:
			pdw(o, v);
			break;
		case Sqw:
			pqw(o, v);
			break;
		}
		break;
	case Pbase:
		/* fix me please: this is teh suck */
		r->fisbits |= 1 << 16;
		r->cmd.ataproto = v;
		break;
	}
}

int
setfis0(Req *r, Txtab *t, char *p)
{
	char *e;
	uvlong v;

	v = strtoull(p, &e, 0);
	setreg(r, t->val, v);
	return *e != 0;
}

char**
setfis(Atatab*, Req *r, char **p)
{
	char *s;
	int i;

loop:
	if((s = p[0]) == 0)
		return p;
	for(i = 0; i < nelem(regtx); i++)
		if(strcmp(s, regtx[i].name) == 0 && p[1] != nil){
//			print("setfis0 %s %s\n", p[0], p[1]);
			setfis0(r, regtx + i, p[1]);
			p += 2;
			goto loop;
		}
	return p;
}

char*
rname(char *buf, int n, int r)
{
	int i;

	for(i = 0; i < nelem(regtx); i++)
		if(regtx[i].val == r){
			snprint(buf, n, "%s", regtx[i].name);
			return buf;
		}
	snprint(buf, n, "%.2ux", r);
	return buf;
}

int
mwcmp(char *a, char ***l)
{
	char buf[128], *f[20], **p;
	int nf, i;

	if(*a == 0)
		return 0;
	p = *l;
	if(p[0] == 0)
		return -1;
	snprint(buf, sizeof buf, "%s", a);
	nf = tokenize(buf, f, nelem(f));
	for(i = 0; i < nf; i++)
		if(p[i] == nil || cistrcmp(p[i], f[i]) != 0)
			return -1;
	*l = p + i - 1;
	return 0;
}

char **dofetab(Fetab*, Req*, char**);

static char hexdig[] = "ABCDEFabcdef0123456789";
static char hexonly[] = "ABCDEFabcdef";
static char Enum[] = "expecting number";

int
fenum(Fetab *, int v, char ***p)
{
	char *e, *s, *r;
	int base;

	if(v >= 0)
		return v;
	s = *(*p + 1);
	e = nil;
	if(s == nil || *s == 0)
		e = Enum;
	else{
		base = 0;
		if(strspn(s, hexdig) == strlen(s) &&
		strpbrk(s, hexonly) != nil)
			base = 0x10;
		v = strtoul(s, &r, base);
		if(*r)
			e = Enum;
	}
	if(e == nil)
		(*p)++;
	else
		print("error: %s [%s]\n", e, s);
	return v;
}

char**
dofetab0(Fetab *t, Req *r, char **p)
{
	int i, v;
	Txtab *tab;

	if(t == nil)
		return p;
	tab = t->tab;
loop:
	for(i = 0; i < t->ntab; i++)
		if(mwcmp(tab[i].name, &p) == 0){
			v = fenum(t, tab[i].val, &p);
			setreg(r, t->reg, v);
			if(tab[i].name[0] != 0){
				p = dofetab(tab[i].fe, r, p + 1);
				goto loop;
			}
		}
	return p;

}

char**
dofetab(Fetab *t, Req *r, char **p)
{
	for(; t != nil && t->ntab > 0; t++)
		p = dofetab0(t, r, p);
	return p;
}

char**
dotab(Atatab *a, Req *r, char **p)
{
	if(a->tab == nil)
		return p;
	return dofetab(a->tab, r, p);
}

void
initreq(Req *r)
{
	memset(r, 0, sizeof *r);
//	r->wfd = open("/dev/null", OWRITE);
	r->wfd = dup(1, -1);
	if(rflag == 0)
		r->fmtrw = 1;
	r->rfd = open("/dev/zero", OREAD);
}

void
setup(void)
{
	int i;

	for(i = 0; i < nelem(atatab); i++)
		if(atatab[i].cc == 0x2f){
			sctread = atatab + i;
			break;
		}
	for(; i < nelem(atatab); i++)
		if(atatab[i].cc == 0x3f){
			sctissue = atatab + i;
			break;
		}
	for(; i < nelem(atatab); i++)
		if(atatab[i].cc == 0xa1){
			idpktcmd = atatab + i;
			break;
		}
	for(; i < nelem(atatab); i++)
		if(atatab[i].cc == 0xec){
			idcmd = atatab + i;
			break;
		}
	for(; i < nelem(atatab); i++)
		if(atatab[i].cc == 0xf000){
			sigcmd = atatab + i;
			break;
		}
}

typedef struct Htab Htab;
struct Htab {
	ulong	bit;
	char	*name;
};

Htab ertab[] = {
	Eicrc,	"icrc",
	Ewp,	"wp",
	Emc,	"mc",
	Eidnf,	"idnf",
	Emcr,	"mcr",
	Eabrt,	"abrt",
	Enm,	"nm",
	Emed,	"med",
	Eunc,	"unc",
};

Htab sttab[] = {
	ASbsy,	"bsy",
	ASdrdy,	"drdy",
	ASdf,	"df",
	ASdrq,	"drq",
	ASerr,	"err",
};

static char*
htabfmt(char *p, char *e, Htab *t, int n, ulong u)
{
	char *p0;
	uint i;

	p0 = p;
	for(i = 0; i < n; i++)
		if(u & t[i].bit)
			p = seprint(p, e, "%s | ", t[i].name);
	if(p - 3 >= p0)
		p -= 3;
	if(p < e)
		p[0] = 0;
	return p;
}

void
prerror(Req *r)
{
	char st[64], er[64];
	uchar *u;

	u = r->reply.fis;
	if(r->haverfis == 0 ||  (u[Fstatus] & ASerr) == 0)
		return;
	htabfmt(er, er + sizeof er, ertab, nelem(ertab), u[Frerror]);
	htabfmt(st, st + sizeof st, sttab, nelem(sttab), u[Fstatus] & ~ASobs);
	fprint(2, "err %.2ux %.2ux (%s, %s)\n", u[Frerror], u[Fstatus], er, st);
}

void
usage(void)
{
	eprint("usage: atazz dev\n");
	eprint(" or -c cmd\n");
	exits("usage");
}

void
main(int argc, char **argv)
{
	char buf[1024], *p, *f[20], **fp;
	int nf, cflag, i;
	Atatab *a;
	Req r;
	Dev d;

	cflag = 0;
	ARGBEGIN{
	case 'c':
		cflag = atoi(EARGF(usage()));
		break;
	case 'r':
		rflag = 1;
		break;
	default:
		usage();
	}ARGEND

	if(cflag){
		for(i = 0; i < nelem(atatab); i++)
			if(atatab[i].cc == cflag)
				print("%s\n", atatab[i].name);
		exits("");
	}

	setup();
	fmtinstall(L'π', πfmt);
	if(argc > 1)
		usage();
	initreq(&r);
	d.fd = -1;
	if(argc == 1 && opendev(*argv, &d) == -1)
		sysfatal("opendev: %r");
	atnotify(catch, 1);
	for(;;){
		memset(&r.cmd, 0, sizeof r.cmd);
		r.fisbits = 0;
		if(readline("az> ", buf, sizeof buf-1) == nil)
			break;
		if((p = trim(buf)) == nil)
			continue;
		if(special(buf, &d, &r) == 0)
			continue;
		if(d.fd == -1){
			eprint("!bad cmd (device closed)\n");
			continue;
		}
		a = findtab(&p, atatab, nelem(atatab));
		if(!a){
			suggesttab(buf, atatab, nelem(atatab));
			eprint("!unknown cmd\n");
			continue;
		}
		nf = tokenize(p, f, nelem(f) - 1);
		f[nf] = 0;
		fp = stdargs(a, &r, f);
		fp = setfis(a, &r, fp);
		if(a->protocol & Psct){
			r.count = 1 * 512;
			r.data = realloc(r.data, r.count);
			memset(r.data, 0, r.count);
		}
		fp = dotab(a, &r, fp);
		switch(a->protocol & Pprotom){
		default:
			eprint("!bad proto1 %.2ux\n", a->protocol & Pprotom);
			continue;
		case Pnd:
			fp = ndargs(a, &r, fp);
		case Preset:
		case Pdiag:
			r.count = 0;
			r.lba = 0;
			r.nsect = 0;
			break;
		case Ppio:
		case Pdma:
		case Pdmq:
		case Ppkt:
			if(a->flags & Cmd5sc){
				r.nsect = r.cmd.fis[Fsc];
				if(r.nsect == 0)
					r.nsect = 1;
				r.cmd.fis[Fsc] = r.nsect;
				r.count = r.nsect * 0x200;
			}else if((a->protocol & Pssm) == P512){
				r.lba = 0;
				r.nsect = 0;
				r.count = 512;
			}else{
				fp = ioargs(a, &r, fp);
				r.count = d.secsize * r.nsect;
			}
			break;
		}
		if(fp[0]){
			eprint("!extra args %π\n", fp);
			continue;
		}
		if(issue(&r, a, &d) == -1){
			prerror(&r);
			continue;
		}
		if(a->fmt)
			a->fmt(&r);
	}
	exits("");
}
