/*
* 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;
}