/*
 * aoe sd bootstrap driver, copyright © 2007 coraid
 */

#include "u.h"
#include "mem.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "sd.h"
#include "aoe.h"


enum {
	Nctlr	= 4,
};

enum {
	/* sync with ahci.h */
	Dllba 	= 1<<0,
	Dsmart	= 1<<1,
	Dpower	= 1<<2,
	Dnop	= 1<<3,
	Datapi	= 1<<4,
	Datapi16= 1<<5,
};

enum {
	Tfree	= -1,
	Tmgmt,
};

typedef struct Ctlr Ctlr;
struct Ctlr{
	Ctlr	*next;
	SDunit	*unit;

	int	ctlrno;
	int	major;
	int	minor;
	uchar	ea[Eaddrlen];
	ushort	lasttag;

	ulong	vers;
	uchar	mediachange;
	uchar	flag;
	uchar	smart;
	uchar	smartrs;
	uchar	feat;

	uvlong	sectors;
	char	serial[20+1];
	char	firmware[8+1];
	char	model[40+1];
	char	ident[0x100];
};

static	Ctlr	*head;
static	Ctlr	*tail;

static	int	aoeether[10];

SDifc sdaoeifc;

static void
hnputs(uchar *p, ushort i)
{
	p[0] = i >> 8;
	p[1] = i;
}

static void
hnputl(uchar *p, ulong i)
{
	p[0] = i >> 24;
	p[1] = i >> 16;
	p[2] = i >> 8;
	p[3] = i;
}

static ushort
nhgets(uchar *p)
{
	return *p<<8 | p[1];
}

static ulong
nhgetl(uchar *p)
{
	return p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3];
}

static int
newtag(Ctlr *d)
{
	int t;

	for(;;){
		t = ++d->lasttag << 16;
		t |= m->ticks & 0xffff;
		switch(t) {
		case Tfree:
		case Tmgmt:
			break;
		default:
			return t;
		}
	}
}

static int
hset(Ctlr *d, Aoehdr *h, int cmd)
{
	int tag;

	memmove(h->dst, d->ea, Eaddrlen);
	hnputs(h->type, Aoetype);
	h->verflag = Aoever << 4;
	hnputs(h->major, d->major);
	h->minor = d->minor;
	h->cmd = cmd;
	hnputl(h->tag, tag = newtag(d));
	return tag;
}

static void
idmove(char *p, ushort *a, int n)
{
	int i;
	char *op, *e;

	op = p;
	for(i = 0; i < n / 2; i++){
		*p++ = a[i] >> 8;
		*p++ = a[i];
	}
	*p = 0;
	while(p > op && *--p == ' ')
		*p = 0;
	e = p;
	p = op;
	while(*p == ' ')
		p++;
	memmove(op, p, n - (e - p));
}

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

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

static ulong
gbit32(void *a)
{
	uchar *i;
	ulong j;

	i = a;
	j  = i[3] << 24;
	j |= i[2] << 16;
	j |= i[1] << 8;
	j |= i[0];
	return j;
}

static uvlong
gbit64(void *a)
{
	uchar *i;

	i = a;
	return (uvlong)gbit32(i+4) << 32 | gbit32(a);
}

static int
ataidentify(Ctlr *c, ushort *id)
{
	vlong s;
	int i;

	i = gbit16(id+83) | gbit16(id+86);
	if(i & (1 << 10)){
		c->feat |= Dllba;
		s = gbit64(id+100);
	}else
		s = gbit32(id+60);

	idmove(c->serial, id+10, 20);
	idmove(c->firmware, id+23, 8);
	idmove(c->model, id+27, 40);

print("aoe discovers %d.%d: %s %s\n", c->major, c->minor, c->model, c->serial);

	c->sectors = s;
	c->mediachange = 1;
	return 0;
}

static void
identifydump(Aoeata *a)
{
	print("%E %E type=%.4ux verflag=%x error=%x %d.%d cmd=%d tag=%.8lux\n",
		a->dst, a->src, nhgets(a->type), a->verflag, a->error,
		nhgets(a->major), a->minor, a->cmd, nhgetl(a->tag));
	print("   aflag=%x errfeat=%ux scnt=%d cmdstat=%ux, lba=%d? res=%.4ux\n",
		a->aflag, a->errfeat, a->scnt, a->cmdstat, 0, nhgets(a->res));
}

static int
idpkt(Ctlr *c, Aoeata *a)
{
	memset(a, 0, sizeof *a);
	a->cmdstat = Cid;
	a->scnt = 1;
	a->lba[3] = 0xa0;
	return hset(c, a, ACata);
}

static int
chktag(int *out, int nout, int tag)
{
	int j;

	for(j = 0; j <= nout; j++)
		if(out[j] == tag)
			return 0;
print("wrong tag\n");
	for(j = 0; j <= nout; j++)
		print("%.8ux != %.8ux\n", out[j], tag);
	return -1;
}

/*
 * ignore the tag for identify.  better than ignoring
 * a response to the wrong identify request
 */
static int
identify(Ctlr *c)
{
	int tag[5], i, n;
	Aoeata *a;
	Etherpkt p;

	memset(&p, 0, sizeof p);
	a = (Aoeata*)&p;
	i = 0;
	do {
		if(i == 5){
			print("aoe: identify timeout\n");
			return -1;
		}
		tag[i] = idpkt(c, a);
		ethertxpkt(c->ctlrno, &p, sizeof *a, 0);
		memset(&p, 0, sizeof p);
next:
		n = etherrxpkt(c->ctlrno, &p, 125);
		if(n == 0){
			i++;
			continue;
		}
		if(nhgets(a->type) != Aoetype)
			goto next;
		if(nhgets(a->major) != c->major || a->minor != c->minor){
			print("wrong device %d.%d want %d.%d; %d\n",
				nhgets(a->major), a->minor,
				c->major, c->minor, n);
			goto next;
		}
		if(chktag(tag, i, nhgetl(a->tag)) == -1)
			goto next;
		if(a->cmdstat & 0xa9){
			print("aoe: ata error on identify: %2ux\n", a->cmdstat);
			return -1;
		}
	} while (a->scnt != 1);

	c->feat = 0;
	ataidentify(c, (ushort*)(a+1));
	return 0;
}

static Ctlr*
ctlrlookup(int major, int minor)
{
	Ctlr *c;

	for(c = head; c; c = c->next)
		if(c->major == major && c->minor == minor)
			break;
	return c;
}

static Ctlr*
newctlr(Etherpkt *p)
{
	int major, minor;
	Aoeqc *q;
	Ctlr *c;

	q = (Aoeqc*)p;
	if(nhgets(q->type) != Aoetype)
		return 0;
	major = nhgets(q->major);
	minor = q->minor;

	if(major == 0xffff || minor == 0xff)
		return 0;

	if(ctlrlookup(major, minor)){
		print("duplicate shelf.slot\n");
		return 0;
	}

	if((c = malloc(sizeof *c)) == 0)
		return 0;
	c->major = major;
	c->minor = minor;
	memmove(c->ea, q->src, Eaddrlen);

	if(head != 0)
		tail->next = c;
	else
		head = c;
	tail = c;
	return c;
}

static void
discover(int major, int minor)
{
	int i;
	Aoehdr *h;
	Etherpkt p;

	for(i = 0; i < nelem(aoeether); i++){
		if(aoeether[i] == 0)
			continue;
		memset(&p, 0, ETHERMINTU);
		h = (Aoehdr*)&p;
		memset(h->dst, 0xff, sizeof h->dst);
		hnputs(h->type, Aoetype);
		h->verflag = Aoever << 4;
		hnputs(h->major, major);
		h->minor = minor;
		h->cmd = ACconfig;
		ethertxpkt(i, &p, ETHERMINTU, 0);
	}
}

static int
rxany(Etherpkt *p, int t)
{
	int i, n;

	for(i = 0; i < nelem(aoeether); i++){
		if(aoeether[i] == 0)
			continue;
		while ((n = etherrxpkt(i, p, t)) != 0)
			if(nhgets(p->type) == Aoetype)
				return n;
	}
	return 0;
}

static int
aoeprobe(int major, int minor, SDev *s)
{
	Ctlr *ctlr;
	Etherpkt p;
	int n, i;

	for(i = 0;; i += 200){
		if(i > 8000)
			return -1;
		discover(major, minor);
again:
		n = rxany(&p, 100);
		if(n > 0 && (ctlr = newctlr(&p)))
			break;
		if(n > 0)
			goto again;
	}

	s->ctlr = ctlr;
	s->ifc = &sdaoeifc;
	s->nunit = 1;
	return 0;
}

static char 	*probef[32];
static int 	nprobe;

int
pnpprobeid(char *s)
{
	int id;

	if(strlen(s) < 2)
		return 0;
	id = 'e';
	if(s[1] == '!')
		id = s[0];
	return id;
}

int
tokenize(char *s, char **args, int maxargs)
{
	int nargs;

	for(nargs = 0; nargs < maxargs; nargs++){
		while(*s != '\0' && strchr("\t\n ", *s) != nil)
			s++;
		if(*s == '\0')
			break;
		args[nargs] = s;
		while(*s != '\0' && strchr("\t\n ", *s) == nil)
			s++;
		if(*s != '\0')
			*s++ = 0;
	}
	return nargs;
}

int
aoepnp0(void)
{
	int i;
	char *p, c;

	if((p = getconf("aoeif")) == nil)
		return 0;
print("aoeif = %s\n", p);
	nprobe = tokenize(p, probef, nelem(probef));
	for(i = 0; i < nprobe; i++){
		if(strncmp(probef[i], "ether", 5) != 0)
			continue;
		c = probef[i][5];
		if(c > '9' || c < '0')
			continue;
		aoeether[c - '0'] = 1;
	}

	if((p = getconf("aoedev")) == nil)
		return 0;
	return nprobe = tokenize(p, probef, nelem(probef));
}

int
probeshelf(char *s, int *shelf, int *slot)
{
	int a, b;
	char *r;

	for(r = s + strlen(s) - 1; r > s; r--)
		if((*r < '0' || *r > '9') && *r != '.'){
			r++;
			break;
		}
	a = strtoul(r, &r, 10);
	if(*r++ != '.')
		return -1;
	b = strtoul(r, 0, 10);

	*shelf = a;
	*slot = b;
print("  shelf=%d.%d\n", a, b);
	return 0;
}

Ctlr*
pnpprobe(SDev *sd)
{
	int shelf, slot;
	char *p;
	static int i;

	if(i >= nprobe)
		return 0;
	p = probef[i++];
	if(strlen(p) < 2)
		return 0;
	if(p[1] == '!'){
		sd->idno = p[0];
		p += 2;
	}
	if(probeshelf(p, &shelf, &slot) == -1 ||
	    aoeprobe(shelf, slot, sd) == -1 ||
	    identify(sd->ctlr) == -1)
		return 0;
	return sd->ctlr;
}

/*
 * we may need to pretend we found something
 */

SDev*
aoepnp(void)
{
	int n, i, id;
	char *p;
	SDev *h, *t, *s;

	p = getconf("aoeif");
	if (p)
		print("aoepnp: aoeif=%s\n", p);

	if((n = aoepnp0()) == 0)
		n = 2;
	t = h = 0;
	for(i = 0; i < n; i++){
		id = 'e';
		s = malloc(sizeof *s);
		if(s == 0)
			break;
		s->ctlr = 0;
		s->idno = id;
		s->ifc = &sdaoeifc;
		s->nunit = 1;
		pnpprobe(s);

		if(h)
			t->next = s;
		else
			h = s;
		t = s;
	}
	return h;
}

static int
aoeverify(SDunit *u)
{
	Ctlr *c;
	SDev *s;

	s = u->dev;
	c = s->ctlr;
	if(c == 0){
		aoepnp0();
		if((s->ctlr = c = pnpprobe(s)) == nil)
			return 0;
	}
	c->mediachange = 1;
	return 1;
}

static int
aoeonline(SDunit *u)
{
	int r;
	Ctlr *c;

	c = u->dev->ctlr;
	if(c->mediachange){
		r = 2;
		c->mediachange = 0;
		u->sectors = c->sectors;
		u->secsize = 512;
	} else
		r = 1;
	return r;
}

static int
rio(Ctlr *c, Aoeata *a, int n, int scnt)
{
	int i, tag, cmd;

	for(i = 0; i < 5; i++){
		tag = hset(c, a, ACata);
		cmd = a->cmdstat;
		ethertxpkt(c->ctlrno, (Etherpkt*)a, n, 0);
		memset(a, 0, sizeof *a);
again:
		n = etherrxpkt(c->ctlrno, (Etherpkt*)a, 125);
		if(n == 0)
			continue;
		if(nhgets(a->type) != Aoetype || nhgetl(a->tag) != tag ||
		    nhgets(a->major) != c->major || a->minor != c->minor)
			goto again;
		if(a->cmdstat & 0xa9){
			print("aoe: ata rio error: %2ux\n", a->cmdstat);
			return 0;
		}
		switch(cmd){
		case Crd:
		case Crdext:
			if(n - sizeof *a < scnt * 512){
				print("aoe: runt expect %d got %d\n",
					scnt*512 + sizeof *a, n);
				return 0;
			}
			return n - sizeof *a;
		case Cwr:
		case Cwrext:
			return scnt * 512;
		default:
print("unknown cmd %ux\n", cmd);
			break;
		}
	}
	print("aoe: rio timeout\n");
	return 0;
}

static void
putlba(Aoeata *a, vlong lba)
{
	uchar *c;

	c = a->lba;
	c[0] = lba;
	c[1] = lba >> 8;
	c[2] = lba >> 16;
	c[3] = lba >> 24;
	c[4] = lba >> 32;
	c[5] = lba >> 40;
}

/*
 * you'll need to loop if you want to read more than 2 sectors.  for now
 * i'm cheeting and not bothering with a loop.
 */
static uchar pktbuf[1024 + sizeof(Aoeata)];

static int
aoebuild(Ctlr *c, uchar *cmd, char *data, vlong lba, int scnt)
{
	int n;
	Aoeata *a;

	memset(pktbuf, 0, sizeof pktbuf);
	a = (Aoeata*)pktbuf;
	hset(c, a, ACata);
	putlba(a, lba);

	a->cmdstat = 0x20;
	if(c->flag & Dllba){
		a->aflag |= AAFext;
		a->cmdstat |= 4;
	}else{
		a->lba[3] &= 0xf;
		a->lba[3] |= 0xe0;		/* LBA bit+obsolete 0xa0 */
	}

	n = scnt;
	if(n > 2)
		n = 2;
	a->scnt = n;

	switch(*cmd){
	case 0x2a:
		a->aflag |= AAFwrite;
		a->cmdstat |= 10;
		memmove(a+1, data, n*512);
		n = sizeof *a + n*512;
		break;
	case 0x28:
		n = sizeof *a;
		break;
	default:
		print("aoe: bad cmd 0x%.2ux\n", cmd[0]);
		return -1;
	}
	return n;
}

static int
aoerio(SDreq *r)
{
	int size, nsec, n;
	vlong lba;
	char *data;
	uchar *cmd;
	Aoeata *a;
	Ctlr *c;
	SDunit *unit;

	unit = r->unit;
	c = unit->dev->ctlr;
	if(r->data == nil)
		return SDok;
	cmd = r->cmd;

	lba = cmd[2]<<24 | cmd[3]<<16 | cmd[4]<<8 | cmd[5];	/* sic. */
	nsec = cmd[7]<<8 | cmd[8];
	a = (Aoeata*)pktbuf;
	data = r->data;
	r->rlen = 0;

	for(; nsec > 0; nsec -= n){
//		print("aoebuild(%2x, %p, %lld, %d)\n", *cmd, data, lba, nsec);
		size = aoebuild(c, cmd, data, lba, nsec);
		if(size < 0){
			r->status = SDcheck;
			return SDcheck;
		}
		n = a->scnt;
		r->rlen += rio(c, a, size, n);
		if(*cmd == 0x28)
			memmove(r->data, a + 1, n * 512);
		data += n * 512;
		lba += n;
	}

	r->status = SDok;
	return SDok;
}

SDifc sdaoeifc = {
	"aoe",

	aoepnp,
	nil,		/* legacy */
	nil,		/* id */
	nil,		/* enable */
	nil,		/* disable */

	aoeverify,
	aoeonline,
	aoerio,
	nil,
	nil,

	scsibio,
};
