/*
*      ISA PNP 1.0 support + access to PCI configuration space
*
*      TODO
*              - implement PNP card configuration (setting io bases etc)
*              - write user program to drive PNP configuration...
*              - extend PCI raw access to configuration space (writes, byte/short access?)
*              - implement PCI access to memory/io space/BIOS ROM
*              - use c->aux instead of performing lookup on each read/write?
*/
#include        "u.h"
#include        "../port/lib.h"
#include        "mem.h"
#include        "dat.h"
#include        "fns.h"
#include        "io.h"
#include        "../port/error.h"

typedef struct Pnp Pnp;
typedef struct Card Card;

struct Pnp
{
       QLock;
       int             rddata;
       int             debug;
       Card            *cards;
};

struct Card
{
       int             csn;
       ulong   id1;
       ulong   id2;
       char            *cfgstr;
       int             ncfg;
       Card*   next;
};

static Pnp      pnp;

#define DPRINT  if(pnp.debug) print
#define XPRINT  if(1) print

enum {
       Address = 0x279,
       WriteData = 0xa79,

       Qtopdir = 0,

       Qpnpdir,
       Qpnpctl,
       Qcsnctl,
       Qcsnraw,

       Qpcidir,
       Qpcictl,
       Qpciraw,
};

#define TYPE(q)         ((ulong)(q).path & 0x0F)
#define CSN(q)          (((ulong)(q).path>>4) & 0xFF)
#define QID(c, t)       (((c)<<4)|(t))

static Dirtab topdir[] = {
       ".",    { Qtopdir, 0, QTDIR },  0,      0555,
       "pnp",  { Qpnpdir, 0, QTDIR },  0,      0555,
       "pci",  { Qpcidir, 0, QTDIR },  0,      0555,
};

static Dirtab pnpdir[] = {
       ".",    { Qpnpdir, 0, QTDIR },  0,      0555,
       "ctl",  { Qpnpctl, 0, 0 },      0,      0666,
};

extern Dev pnpdevtab;
static int wrconfig(Card*, char*);

static char key[32] =
{
       0x6A, 0xB5, 0xDA, 0xED, 0xF6, 0xFB, 0x7D, 0xBE,
       0xDF, 0x6F, 0x37, 0x1B, 0x0D, 0x86, 0xC3, 0x61,
       0xB0, 0x58, 0x2C, 0x16, 0x8B, 0x45, 0xA2, 0xD1,
       0xE8, 0x74, 0x3A, 0x9D, 0xCE, 0xE7, 0x73, 0x39,
};

static void
cmd(int reg, int val)
{
       outb(Address, reg);
       outb(WriteData, val);
}

/* Send initiation key, putting each card in Sleep state */
static void
initiation(void)
{
       int i;

       /* ensure each card's LFSR is reset */
       outb(Address, 0x00);
       outb(Address, 0x00);

       /* send initiation key */
       for (i = 0; i < 32; i++)
               outb(Address, key[i]);
}

/* isolation protocol... */
static int
readbit(int rddata)
{
       int r1, r2;

       r1 = inb(rddata);
       r2 = inb(rddata);
       microdelay(250);
       return (r1 == 0x55) && (r2 == 0xaa);
}

static int
isolate(int rddata, ulong *id1, ulong *id2)
{
       int i, csum, bit;
       uchar *p, id[9];

       outb(Address, 0x01);    /* point to serial isolation register */
       delay(1);
       csum = 0x6a;
       for(i = 0; i < 64; i++){
               bit = readbit(rddata);
               csum = (csum>>1) | (((csum&1) ^ ((csum>>1)&1) ^ bit)<<7);
               p = &id[i>>3];
               *p = (*p>>1) | (bit<<7);
       }
       for(; i < 72; i++){
               p = &id[i>>3];
               *p = (*p>>1) | (readbit(rddata)<<7);
       }
       *id1 = (id[3]<<24)|(id[2]<<16)|(id[1]<<8)|id[0];
       *id2 = (id[7]<<24)|(id[6]<<16)|(id[5]<<8)|id[4];
       if(*id1 == 0)
               return 0;
       if(id[8] != csum)
               DPRINT("pnp: bad checksum id1 %lux id2 %lux csum %x != %x\n", *id1, *id2, csum, id[8]); /**/
       return id[8] == csum;
}

static int
getresbyte(int rddata)
{
       int tries = 0;

       outb(Address, 0x05);
       while ((inb(rddata) & 1) == 0)
               if (tries++ > 1000000)
                       error("pnp: timeout waiting for resource data\n");
       outb(Address, 0x04);
       return inb(rddata);
}

static char *
serial(ulong id1, ulong id2)
{
       int i1, i2, i3;
       ulong x;
       static char buf[20];

       i1 = (id1>>2)&31;
       i2 = ((id1<<3)&24)+((id1>>13)&7);
       i3 = (id1>>8)&31;
       x = (id1>>8)&0xff00|(id1>>24)&0x00ff;
       if (i1 > 0 && i1 < 27 && i2 > 0 && i2 < 27 && i3 > 0 && i3 < 27 && (id1 & (1<<7)) == 0)
               snprint(buf, sizeof(buf), "%c%c%c%.4lux.%lux", 'A'+i1-1, 'A'+i2-1, 'A'+i3-1, x, id2);
       else
               snprint(buf, sizeof(buf), "%.4lux%.4lux.%lux", (id1<<8)&0xff00|(id1>>8)&0x00ff, x, id2);
       return buf;
}

static Card *
findcsn(int csn, int create, int dolock)
{
       Card *c, *nc, **l;

       if(dolock)
               qlock(&pnp);
       l = &pnp.cards;
       for(c = *l; c != nil; c = *l) {
               if(c->csn == csn)
                       goto done;
               if(c->csn > csn)
                       break;
               l = &c->next;
       }
       if(create) {
               if((nc = malloc(sizeof(Card))) == nil)
                       panic("pnp: no memory for Card");
               *l = nc;
               nc->next = c;
               nc->csn = csn;
               c = nc;
       }
done:
       if(dolock)
               qunlock(&pnp);
       return c;
}

static int
newcsn(void)
{
       int csn;
       Card *c;

       csn = 1;
       for(c = pnp.cards; c != nil; c = c->next) {
               if(c->csn > csn)
                       break;
               csn = c->csn+1;
       }
       return csn;
}

static int
pnpncfg(int rddata)
{
       int i, n, x, ncfg, n1, n2;

       ncfg = 0;
       for (;;) {
               x = getresbyte(rddata);
               if((x & 0x80) == 0) {
                       n = (x&7)+1;
                       for(i = 1; i < n; i++)
                               getresbyte(rddata);
               }
               else {
                       n1 = getresbyte(rddata);
                       n2 = getresbyte(rddata);
                       n = (n2<<8)|n1 + 3;
                       for (i = 3; i < n; i++)
                               getresbyte(rddata);
               }
               ncfg += n;
               if((x>>3) == 0x0f)
                       break;
       }
       return ncfg;
}

/* look for cards, and assign them CSNs */
static int
pnpscan(int rddata, int dawn)
{
       Card *c;
       int csn;
       ulong id1, id2;

       initiation();                           /* upsilon sigma */
       cmd(0x02, 0x04+0x01);           /* reset CSN on all cards and reset logical devices */
       delay(1);                                       /* delay after resetting cards */

       cmd(0x03, 0);                           /* Wake all cards with a CSN of 0 */
       cmd(0x00, rddata>>2);           /* Set the READ_DATA port on all cards */
       while(isolate(rddata, &id1, &id2)) {
               for(c = pnp.cards; c != nil; c = c->next)
                       if(c->id1 == id1 && c->id2 == id2)
                               break;
               if(c == nil) {
                       csn = newcsn();
                       c = findcsn(csn, 1, 0);
                       c->id1 = id1;
                       c->id2 = id2;
               }
               else if(c->cfgstr != nil) {
                       if(!wrconfig(c, c->cfgstr))
                               print("pnp%d: bad cfg: %s\n", c->csn, c->cfgstr);
                       c->cfgstr = nil;
               }
               cmd(0x06, c->csn);              /* set the card's csn */
               if(dawn)
                       print("pnp%d: %s\n", c->csn, serial(id1, id2));
               c->ncfg = pnpncfg(rddata);
               cmd(0x03, 0);           /* Wake all cards with a CSN of 0, putting this card to sleep */
       }
       cmd(0x02, 0x02);                        /* return cards to Wait for Key state */
       if(pnp.cards != 0) {
               pnp.rddata = rddata;
               return 1;
       }
       return 0;
}

static void
pnpreset(void)
{
       Card *c;
       ulong id1, id2;
       int csn, i1, i2, i3, x;
       char *s, *p, buf[20];
       ISAConf isa;

       memset(&isa, 0, sizeof(ISAConf));
       pnp.rddata = -1;
       if (isaconfig("pnp", 0, &isa) == 0)
               return;
       if(isa.port < 0x203 || isa.port > 0x3ff)
               return;
       for(csn = 1; csn < 256; csn++) {
               snprint(buf, sizeof buf, "pnp%d", csn);
               s = getconf(buf);
               if(s == 0)
                       continue;
               if(strlen(s) < 8 || s[7] != '.' || s[0] < 'A' || s[0] > 'Z' || s[1] < 'A' || s[1] > 'Z' || s[2] < 'A' || s[2] > 'Z') {
bad:
                       print("pnp%d: bad conf string %s\n", csn, s);
                       continue;
               }
               i1 = s[0]-'A'+1;
               i2 = s[1]-'A'+1;
               i3 = s[2]-'A'+1;
               x = strtoul(&s[3], 0, 16);
               id1 = (i1<<2)|((i2>>3)&3)|((i2&7)<<13)|(i3<<8)|((x&0xff)<<24)|((x&0xff00)<<8);
               id2 = strtoul(&s[8], &p, 16);
               if(*p == ' ')
                       p++;
               else if(*p == '\0')
                       p = nil;
               else
                       goto bad;
               c = findcsn(csn, 1, 0);
               c->id1 = id1;
               c->id2 = id2;
               c->cfgstr = p;
       }
       pnpscan(isa.port, 1);
}

static int
csngen(Chan *c, int t, int csn, Card *cp, Dir *dp)
{
       Qid q;

       switch(t) {
       case Qcsnctl:
               q = (Qid){QID(csn, Qcsnctl), 0, 0};
               snprint(up->genbuf, sizeof up->genbuf, "csn%dctl", csn);
               devdir(c, q, up->genbuf, 0, eve, 0664, dp);
               return 1;
       case Qcsnraw:
               q = (Qid){QID(csn, Qcsnraw), 0, 0};
               snprint(up->genbuf, sizeof up->genbuf, "csn%draw", csn);
               devdir(c, q, up->genbuf, cp->ncfg, eve, 0444, dp);
               return 1;
       }
       return -1;
}

static int
pcigen(Chan *c, int t, int tbdf, Dir *dp)
{
       Qid q;

       q = (Qid){BUSBDF(tbdf)|t, 0, 0};
       switch(t) {
       case Qpcictl:
               snprint(up->genbuf, sizeof up->genbuf, "%d.%d.%dctl",
                       BUSBNO(tbdf), BUSDNO(tbdf), BUSFNO(tbdf));
               devdir(c, q, up->genbuf, 0, eve, 0444, dp);
               return 1;
       case Qpciraw:
               snprint(up->genbuf, sizeof up->genbuf, "%d.%d.%draw",
                       BUSBNO(tbdf), BUSDNO(tbdf), BUSFNO(tbdf));
               devdir(c, q, up->genbuf, 128, eve, 0660, dp);
               return 1;
       }
       return -1;
}

static int
pnpgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
{
       Qid q;
       Card *cp;
       Pcidev *p;
       int csn, tbdf;

       switch(TYPE(c->qid)){
       case Qtopdir:
               if(s == DEVDOTDOT){
                       q = (Qid){QID(0, Qtopdir), 0, QTDIR};
                       snprint(up->genbuf, sizeof up->genbuf, "#%C", pnpdevtab.dc);
                       devdir(c, q, up->genbuf, 0, eve, 0555, dp);
                       return 1;
               }
               return devgen(c, nil, topdir, nelem(topdir), s, dp);
       case Qpnpdir:
               if(s == DEVDOTDOT){
                       q = (Qid){QID(0, Qtopdir), 0, QTDIR};
                       snprint(up->genbuf, sizeof up->genbuf, "#%C", pnpdevtab.dc);
                       devdir(c, q, up->genbuf, 0, eve, 0555, dp);
                       return 1;
               }
               if(s < nelem(pnpdir)-1)
                       return devgen(c, nil, pnpdir, nelem(pnpdir), s, dp);
               s -= nelem(pnpdir)-1;
               qlock(&pnp);
               cp = pnp.cards;
               while(s >= 2 && cp != nil) {
                       s -= 2;
                       cp = cp->next;
               }
               qunlock(&pnp);
               if(cp == nil)
                       return -1;
               return csngen(c, s+Qcsnctl, cp->csn, cp, dp);
       case Qpnpctl:
               return devgen(c, nil, pnpdir, nelem(pnpdir), s, dp);
       case Qcsnctl:
       case Qcsnraw:
               csn = CSN(c->qid);
               cp = findcsn(csn, 0, 1);
               if(cp == nil)
                       return -1;
               return csngen(c, TYPE(c->qid), csn, cp, dp);
       case Qpcidir:
               if(s == DEVDOTDOT){
                       q = (Qid){QID(0, Qtopdir), 0, QTDIR};
                       snprint(up->genbuf, sizeof up->genbuf, "#%C", pnpdevtab.dc);
                       devdir(c, q, up->genbuf, 0, eve, 0555, dp);
                       return 1;
               }
               p = pcimatch(nil, 0, 0);
               while(s >= 2 && p != nil) {
                       p = pcimatch(p, 0, 0);
                       s -= 2;
               }
               if(p == nil)
                       return -1;
               return pcigen(c, s+Qpcictl, p->tbdf, dp);
       case Qpcictl:
       case Qpciraw:
               tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
               p = pcimatchtbdf(tbdf);
               if(p == nil)
                       return -1;
               return pcigen(c, TYPE(c->qid), tbdf, dp);
       default:
               break;
       }
       return -1;
}

static Chan*
pnpattach(char *spec)
{
       return devattach(pnpdevtab.dc, spec);
}

Walkqid*
pnpwalk(Chan* c, Chan *nc, char** name, int nname)
{
       return devwalk(c, nc, name, nname, (Dirtab *)0, 0, pnpgen);
}

static int
pnpstat(Chan* c, uchar* dp, int n)
{
       return devstat(c, dp, n, (Dirtab *)0, 0L, pnpgen);
}

static Chan*
pnpopen(Chan *c, int omode)
{
       c = devopen(c, omode, (Dirtab*)0, 0, pnpgen);
       switch(TYPE(c->qid)){
       default:
               break;
       }
       return c;
}

static void
pnpclose(Chan*)
{
}

static long
pnpread(Chan *c, void *va, long n, vlong offset)
{
       ulong x;
       Card *cp;
       Pcidev *p;
       char buf[256], *ebuf, *w;
       char *a = va;
       int csn, i, tbdf, r;

       switch(TYPE(c->qid)){
       case Qtopdir:
       case Qpnpdir:
       case Qpcidir:
               return devdirread(c, a, n, (Dirtab *)0, 0L, pnpgen);
       case Qpnpctl:
               if(pnp.rddata > 0)
                       snprint(up->genbuf, sizeof up->genbuf, "enabled %#x\n",
                               pnp.rddata);
               else
                       snprint(up->genbuf, sizeof up->genbuf, "disabled\n");
               return readstr(offset, a, n, up->genbuf);
       case Qcsnraw:
               csn = CSN(c->qid);
               cp = findcsn(csn, 0, 1);
               if(cp == nil)
                       error(Egreg);
               if(offset+n > cp->ncfg)
                       n = cp->ncfg - offset;
               qlock(&pnp);
               initiation();
               cmd(0x03, csn);                         /* Wake up the card */
               for(i = 0; i < offset+9; i++)           /* 9 == skip serial + csum */
                       getresbyte(pnp.rddata);
               for(i = 0; i < n; i++)
                       a[i] = getresbyte(pnp.rddata);
               cmd(0x03, 0);                                   /* Wake all cards with a CSN of 0, putting this card to sleep */
               cmd(0x02, 0x02);                                /* return cards to Wait for Key state */
               qunlock(&pnp);
               break;
       case Qcsnctl:
               csn = CSN(c->qid);
               cp = findcsn(csn, 0, 1);
               if(cp == nil)
                       error(Egreg);
               snprint(up->genbuf, sizeof up->genbuf, "%s\n",
                       serial(cp->id1, cp->id2));
               return readstr(offset, a, n, up->genbuf);
       case Qpcictl:
               tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
               p = pcimatchtbdf(tbdf);
               if(p == nil)
                       error(Egreg);
               ebuf = buf+sizeof buf-1;        /* -1 for newline */
               w = seprint(buf, ebuf, "%.2x.%.2x.%.2x %.4x/%.4x %3d",
                       p->ccrb, p->ccru, p->ccrp, p->vid, p->did, p->intl);
               for(i=0; i<nelem(p->mem); i++){
                       if(p->mem[i].size == 0)
                               continue;
                       w = seprint(w, ebuf, " %d:%.8lux %d", i, p->mem[i].bar, p->mem[i].size);
               }
               *w++ = '\n';
               *w = '\0';
               return readstr(offset, a, n, buf);
       case Qpciraw:
               tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
               p = pcimatchtbdf(tbdf);
               if(p == nil)
                       error(Egreg);
               if(offset > 256)
                       return 0;
               if(n+offset > 256)
                       n = 256-offset;
               r = offset;
               if(!(r & 3) && n == 4){
                       x = pcicfgr32(p, r);
                       PBIT32(a, x);
                       return 4;
               }
               if(!(r & 1) && n == 2){
                       x = pcicfgr16(p, r);
                       PBIT16(a, x);
                       return 2;
               }
               for(i = 0; i <  n; i++){
                       x = pcicfgr8(p, r);
                       PBIT8(a, x);
                       a++;
                       r++;
               }
               return i;
       default:
               error(Egreg);
       }
       return n;
}

static long
pnpwrite(Chan *c, void *va, long n, vlong offset)
{
       Card *cp;
       Pcidev *p;
       ulong port, x;
       char buf[256];
       uchar *a;
       int csn, i, r, tbdf;

       if(n >= sizeof(buf))
               n = sizeof(buf)-1;
       a = va;
       strncpy(buf, va, n);
       buf[n] = 0;

       switch(TYPE(c->qid)){
       case Qpnpctl:
               if(strncmp(buf, "port ", 5) == 0) {
                       port = strtoul(buf+5, 0, 0);
                       if(port < 0x203 || port > 0x3ff)
                               error("bad value for rddata port");
                       qlock(&pnp);
                       if(waserror()) {
                               qunlock(&pnp);
                               nexterror();
                       }
                       if(pnp.rddata > 0)
                               error("pnp port already set");
                       if(!pnpscan(port, 0))
                               error("no cards found");
                       qunlock(&pnp);
                       poperror();
               }
               else if(strncmp(buf, "debug ", 6) == 0)
                       pnp.debug = strtoul(buf+6, 0, 0);
               else
                       error(Ebadctl);
               break;
       case Qcsnctl:
               csn = CSN(c->qid);
               cp = findcsn(csn, 0, 1);
               if(cp == nil)
                       error(Egreg);
               if(!wrconfig(cp, buf))
                       error(Ebadctl);
               break;
       case Qpciraw:
               tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
               p = pcimatchtbdf(tbdf);
               if(p == nil)
                       error(Egreg);
               if(offset > 256)
                       return 0;
               if(n+offset > 256)
                       n = 256-offset;
               r = offset;
               if(!(r & 3) && n == 4){
                       x = GBIT32(a);
                       pcicfgw32(p, r, x);
                       return 4;
               }
               if(!(r & 1) && n == 2){
                       x = GBIT16(a);
                       pcicfgw16(p, r, x);
                       return 2;
               }
               for(i = 0; i <  n; i++){
                       x = GBIT8(a);
                       pcicfgw8(p, r, x);
                       a++;
                       r++;
               }
               return i;
       default:
               error(Egreg);
       }
       return n;
}

static int
wrconfig(Card *c, char *cmd)
{
       /* This should implement setting of I/O bases, etc */
       USED(c, cmd);
       return 1;
}


Dev pnpdevtab = {
       '$',
       "pnp",

       pnpreset,
       devinit,
       devshutdown,
       pnpattach,
       pnpwalk,
       pnpstat,
       pnpopen,
       devcreate,
       pnpclose,
       pnpread,
       devbread,
       pnpwrite,
       devbwrite,
       devremove,
       devwstat,
};