/*
 * sata fises and sas frames
 * copyright © 2009-2010 erik quanstrom
 */
#include <u.h>
#include <libc.h>
#include <fis.h>

static char *flagname[9] = {
	"lba",
	"llba",
	"smart",
	"power",
	"nop",
	"atapi",
	"atapi16",
	"ata8",
	"sct",
};

/*
 * ata8 standard (llba) cmd layout
 *
 * feature	16 bits
 * count		16 bits
 * lba		48 bits
 * device		8 bits
 * command	8 bits
 *
 * response:
 *
 * status		8 bits
 * error		8 bits
 * reason		8 bits
 * count		8 bits
 * sstatus		8 bits
 * sactive		8 bits
*/

/*
 * sata fis layout for fistype 0x27: host-to-device:
 *
 * 0	fistype
 * 1	fis flags
 * 2	ata command
 * 3	features
 * 4	sector	lba low	7:0
 * 5	cyl low	lba mid	15:8
 * 6	cyl hi	lba hi	23:16
 * 7	device / head
 * 8	sec exp	lba	31:24
 * 9	cy low e	lba	39:32
 * 10	cy hi e	lba	48:40
 * 11	features (exp)
 * 12	sector count
 * 13	sector count (exp)
 * 14	r
 * 15	control
 */

void
setfissig(Sfis *x, uint sig)
{
	x->sig = sig;
}

void
skelfis(uchar *c)
{
	memset(c, 0, Fissize);
	c[Ftype] = H2dev;
	c[Fflags] = Fiscmd;
	c[Fdev] = Ataobs;
}

int
nopfis(Sfis*, uchar *c, int srst)
{
	skelfis(c);
	if(srst){
		c[Fflags] &= ~Fiscmd;
		c[Fcontrol] = 1<<2;
		return Preset|P28;
	}
	return Pnd|P28;
}

int
txmodefis(Sfis *f, uchar *c, uchar d)
{
	int m;

	/* hack */
	if((f->sig >> 16) == 0xeb14)
		return -1;
	m = 0x40;
	if(d == 0xff){
		d = 0;
		m = 0;
	}
	skelfis(c);
	c[Fcmd] = 0xef;
	c[Ffeat] = 3;			/* set transfer mode */
	c[Fsc] = m | d;			/* sector count */
	return Pnd|P28;
}

int
featfis(Sfis*, uchar *c, uchar f)
{
	skelfis(c);
	c[Fcmd] = 0xef;
	c[Ffeat] = f;
	return Pnd|P28;
}

int
identifyfis(Sfis *f, uchar *c)
{
	static uchar tab[] = { 0xec, 0xa1, };

	skelfis(c);
	c[Fcmd] = tab[f->sig>>16 == 0xeb14];
	return Pin|Ppio|P28|P512;
}

int
flushcachefis(Sfis *f, uchar *c)
{
	static uchar tab[2] = {0xe7, 0xea};
	static uchar ptab[2] = {Pnd|P28, Pnd|P48};
	int llba;

	llba = (f->feat & Dllba) != 0;
	skelfis(c);
	c[Fcmd] = tab[llba];
	return ptab[llba];
}

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

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

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

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

ushort
id16(ushort *id, int i)
{
	return gbit16(id+i);
}

uint
id32(ushort *id, int i)
{
	return gbit32(id+i);
}

uvlong
id64(ushort *id, int i)
{
	return gbit64(id+i);
}

/* acs-2 §7.18.7.4 */
static ushort puistab[] = {
	0x37c8,	Pspinup,
	0x738c,	Pspinup | Pidready,
	0x8c73,	0,
	0xc837,	Pidready,
};

int
idpuis(ushort *id)
{
	ushort u, i;

	u = gbit16(id + 2);
	for(i = 0; i < nelem(puistab); i += 2)
		if(u == puistab[i])
			return puistab[i + 1];
	return Pidready;	/* annoying cdroms */
}

static ushort
onesc(ushort *id)
{
	ushort u;

	u = gbit16(id);
	if(u == 0xffff)
		u = 0;
	return u;
}

enum{
	Idmasp	= 1<<8,
	Ilbasp	= 1<<9,
	Illba	= 1<<10,
};

vlong
idfeat(Sfis *f, ushort *id)
{
	int i, j;
	vlong s;

	f->feat = 0;
	if(f->sig>>16 == 0xeb14)
		f->feat |= Datapi;
	i = gbit16(id + 49);
	if((i & Ilbasp) == 0){
		if((gbit16(id + 53) & 1) == 0){
			f->c = gbit16(id + 1);
			f->h = gbit16(id + 3);
			f->s = gbit16(id + 6);
		}else{
			f->c = gbit16(id + 54);
			f->h = gbit16(id + 55);
			f->s = gbit16(id + 56);
		}
		s = f->c*f->h*f->s;
	}else{
		f->c = f->h = f->s = 0;
		f->feat |= Dlba;
		j = gbit16(id + 83) | gbit16(id + 86);
		if(j & Illba){
			f->feat |= Dllba;
			s = gbit64(id + 100);
		}else
			s = gbit32(id + 60);
	}
	f->udma = 0xff;
	if(i & Idmasp)
	if(gbit16(id + 53) & 4)
		for(i = gbit16(id + 88) & 0x7f; i; i >>= 1)
			f->udma++;

	if(f->feat & Datapi){
		i = gbit16(id + 0);
		if(i & 1)
			f->feat |= Datapi16;
	}

	i = gbit16(id+83);
	if((i>>14) == 1){
		if(i & (1<<3))
			f->feat |= Dpower;
		i = gbit16(id + 82);
		if(i & 1)
			f->feat |= Dsmart;
		if(i & (1<<14))
			f->feat |= Dnop;
	}
	i = onesc(id + 80);
	if(i & 1<<8){
		f->feat |= Data8;
		i = onesc(id + 222);			/* sata? */
		j = onesc(id + 76);
		if(i != 0 && i >> 12 == 1 && j != 0){
			j >>= 1;
			f->speeds = j & 7;
			/*
			 * not acceptable for comreset to
			 * wipe out device configuration.
			 * reject drive.
			i = gbit16(id + 78) & gbit16(id + 79);
			if((i & 1<<6) == 0)
				return -1;
			 */
		}
	}
	if(gbit16(id + 206) & 1)
		f->feat |= Dsct;
	idss(f, id);
	return s;
}

int
idss(Sfis *f, ushort *id)
{
	uint sw, i, pa;

	if(f->sig>>16 == 0xeb14)
		return 0;
	f->lsectsz = 512;
	f->physshift = 0;
	f->physalign = 0;
	i = gbit16(id + 106);
	if(i >> 14 != 1)
		return f->lsectsz;
	if((i & (1<<12)) && (sw = gbit32(id + 117)) >= 256)
		f->lsectsz = sw * 2;
	if(i & 1<<13){
		f->physshift = i & 7;
		if((pa = gbit16(id + 209)) & 0x4000)
			f->physalign = pa & 0x3fff;
	}
	return f->lsectsz;
}

uvlong
idwwn(Sfis*, ushort *id)
{
	uvlong u;

	u = 0;
	if(id[108]>>12 == 5){
		u |= (uvlong)gbit16(id + 108) << 48;
		u |= (uvlong)gbit16(id + 109) << 32;
		u |= gbit16(id + 110) << 16;
		u |= gbit16(id + 111) << 0;
	}
	return u;
}

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

	op = p;
	s = (char*)u;
	for(i = 0; i < n; i += 2){
		*p++ = s[i + 1];
		*p++ = s[i + 0];
	}
	*p = 0;
	while(p > op && *--p == ' ')
		*p = 0;
	e = p;
	p = op;
	while(*p == ' ')
		p++;
	memmove(op, p, n - (e - p));
}

char*
pflag(char *s, char *e, Sfis *f)
{
	ushort i, u;

	u = f->feat;
	for(i = 0; i < Dnflag; i++)
		if(u & (1 << i))
			s = seprint(s, e, "%s ", flagname[i]);
	return seprint(s, e, "\n");
}

int
atapirwfis(Sfis *f, uchar *c, uchar *cdb, int cdblen, int ndata)
{
	int fill, len;

	fill = f->feat&Datapi16? 16: 12;
	if((len = cdblen) > fill)
		len = fill;
	memmove(c + 0x40, cdb, len);
	memset(c + 0x40 + len, 0, fill - len);

	c[Ftype] = H2dev;
	c[Fflags] = Fiscmd;
	c[Fcmd] = Ataobs;
	if(ndata != 0)
		c[Ffeat] = 1;	/* dma */
	else
		c[Ffeat] = 0;	/* features (exp); */
	c[Flba0] = 0;	
	c[Flba8] = ndata;
	c[Flba16] = ndata >> 8;
	c[Fdev] = Ataobs;
	memset(c + 8, 0, Fissize - 8);
	return P28|Ppkt;
}

int
rwfis(Sfis *f, uchar *c, int rw, int nsect, uvlong lba)
{
	uchar acmd, llba, udma;
	static uchar tab[2][2][2] = { 0x20, 0x24, 0x30, 0x34, 0xc8, 0x25, 0xca, 0x35, };
	static uchar ptab[2][2][2] = {
		Pin|Ppio|P28,	Pin|Ppio|P48,
		Pout|Ppio|P28,	Pout|Ppio|P48,
		Pin|Pdma|P28,	Pin|Pdma|P48,
		Pout|Pdma|P28,	Pout|Pdma|P48,
	};

	udma = f->udma != 0xff;
	llba = (f->feat & Dllba) != 0;
	acmd = tab[udma][rw][llba];

	c[Ftype] = 0x27;
	c[Fflags] = 0x80;
	c[Fcmd] = acmd;
	c[Ffeat] = 0;

	c[Flba0] = lba;
	c[Flba8] = lba >> 8;
	c[Flba16] = lba >> 16;
	c[Fdev] = Ataobs | Atalba;
	if(llba == 0)
		c[Fdev] |= (lba>>24) & 0xf;

	c[Flba24] = lba >> 24;
	c[Flba32] = lba >> 32;
	c[Flba40] = lba >> 40;
	c[Ffeat8] = 0;

	c[Fsc] = nsect;
	c[Fsc8] = nsect >> 8;
	c[Ficc] = 0;
	c[Fcontrol] = 0;

	memset(c + 16, 0, Fissize - 16);
	return ptab[udma][rw][llba];
}

uvlong
fisrw(Sfis *, uchar *c, int *n)
{
	uvlong lba;

	lba = c[Flba0];
	lba |= c[Flba8] << 8;
	lba |= c[Flba16] << 16;
	lba |= c[Flba24] << 24;
	lba |= (uvlong)(c[Flba32] | c[Flba40]<<8) << 32;

	*n = c[Fsc];
	*n |= c[Fsc8] << 8;

	return lba;
}

void
sigtofis(Sfis *f, uchar *c)
{
	uint u;

	u = f->sig;
	memset(c, 0, Fissize);
	c[Ftype] = 0x34;
	c[Fflags] = 0x00;
	c[Fcmd] = 0x50;
	c[Ffeat] = 0x01;
	c[Flba0] = u >> 8;
	c[Flba8] = u >> 16;
	c[Flba16] = u >> 24;
	c[Fdev] = Ataobs;
	c[Fsc] = u;
}

uint
fistosig(uchar *u)
{
	return u[Fsc] | u[Flba0]<<8 | u[Flba8]<<16 | u[Flba16]<<24;
}


/* sas smp */
void
smpskelframe(Cfis *f, uchar *c, int m)
{
	memset(c, 0, Fissize);
	c[Ftype] = 0x40;
	c[Fflags] = m;
	if(f->phyid)
		c[Flba32] = f->phyid;
}

uint
sashash(uvlong u)
{
	uint poly, msb, l, r;
	uvlong m;

	r = 0;
	poly = 0x01db2777;
	msb = 0x01000000;
	for(m = 1ull<<63; m > 0; m >>= 1){
		l = 0;
		if(m & u)
			l = msb;
		r <<= 1;
		r ^= l;
		if(r & msb)
			r ^= poly;
	}
	return r & 0xffffff;
}

uchar*
sasbhash(uchar *t, uchar *s)
{
	uint poly, msb, l, r, i, j;

	r = 0;
	poly = 0x01db2777;
	msb = 0x01000000;
	for(i = 0; i < 8; i++)
		for(j = 0x80; j != 0; j >>= 1){
			l = 0;
			if(s[i] & j)
				l = msb;
			r <<= 1;
			r ^= l;
			if(r & msb)
				r ^= poly;
		}
	t[0] = r>>16;
	t[1] = r>>8;
	t[2] = r;
	return t;
}
