#include <u.h>
#include <libc.h>

enum
{
       Linktarget = 0x13,
       Funcid = 0x21,
       End =   0xff,
};

int fd;
int pos;

void    tdevice(int, int);
void    tlonglnkmfc(int, int);
void    tfuncid(int, int);
void    tcfig(int, int);
void    tentry(int, int);
void    tvers1(int, int);

void (*parse[256])(int, int) =
{
[1]             tdevice,
[6]             tlonglnkmfc,
[0x15]          tvers1,
[0x17]          tdevice,
[0x1A]          tcfig,
[0x1B]          tentry,
[Funcid]        tfuncid,
};

int hex;

void
fatal(char *fmt, ...)
{
       va_list arg;
       char buf[512];

       va_start(arg, fmt);
       vseprint(buf, buf+sizeof(buf), fmt, arg);
       va_end(arg);
       fprint(2, "pcmcia: %s\n", buf);
       exits(buf);
}

int
readc(void *x)
{
       int rv;

       seek(fd, 2*pos, 0);
       pos++;
       rv = read(fd, x, 1);
       if(hex)
               print("%2.2ux ", *(uchar*)x);
       return rv;
}

int
tuple(int next, int expect)
{
       uchar link;
       uchar type;

       pos = next;
       if(readc(&type) != 1)
               return -1;
       if(type == 0xff)
               return -1;
print("type %.2uX\n", type & 0xff);

       if(expect && expect != type){
               print("expected %.2uX found %.2uX\n",
                       expect, type);
               return -1;
       }

       if(readc(&link) != 1)
               return -1;
       if(parse[type])
               (*parse[type])(type, link);
       if(link == 0xff)
               next = -1;
       else
               next = next+2+link;
       return next;
}

void
main(int argc, char *argv[])
{
       char *file;
       int next;

       ARGBEGIN{
       case 'x':
               hex = 1;
       }ARGEND;

       if(argc == 0)
               file = "#y/pcm0attr";
       else
               file = argv[0];

       fd = open(file, OREAD);
       if(fd < 0)
               fatal("opening %s: %r", file);

       for(next = 0; next >= 0;)
               next = tuple(next, 0);
}

ulong speedtab[16] =
{
[1]     250,
[2]     200,
[3]     150,
[4]     100,
};

ulong mantissa[16] =
{
[1]     10,
[2]     12,
[3]     13,
[4]     15,
[5]     20,
[6]     25,
[7]     30,
[8]     35,
[9]     40,
[0xa]   45,
[0xb]   50,
[0xc]   55,
[0xd]   60,
[0xe]   70,
[0xf]   80,
};

ulong exponent[8] =
{
[0]     1,
[1]     10,
[2]     100,
[3]     1000,
[4]     10000,
[5]     100000,
[6]     1000000,
[7]     10000000,
};

char *typetab[256] =
{
[1]     "Masked ROM",
[2]     "PROM",
[3]     "EPROM",
[4]     "EEPROM",
[5]     "FLASH",
[6]     "SRAM",
[7]     "DRAM",
[0xD]   "IO+MEM",
};

ulong
getlong(int size)
{
       uchar c;
       int i;
       ulong x;

       x = 0;
       for(i = 0; i < size; i++){
               if(readc(&c) != 1)
                       break;
               x |= c<<(i*8);
       }
       return x;
}

void
tdevice(int ttype, int len)
{
       uchar id;
       uchar type;
       uchar speed, aespeed;
       uchar size;
       ulong bytes, ns;
       char *tname, *ttname;

       while(len > 0){
               if(readc(&id) != 1)
                       return;
               len--;
               if(id == End)
                       return;

               /* PRISM cards have a device tuple with id = size = 0. */
               if(id == 0x00){
                       if(readc(&size) != 1)
                               return;
                       len--;
                       continue;
               }

               speed = id & 0x7;
               if(speed == 0x7){
                       if(readc(&speed) != 1)
                               return;
                       len--;
                       if(speed & 0x80){
                               if(readc(&aespeed) != 1)
                                       return;
                               ns = 0;
                       } else
                               ns = (mantissa[(speed>>3)&0xf]*exponent[speed&7])/10;
               } else
                       ns = speedtab[speed];

               type = id>>4;
               if(type == 0xE){
                       if(readc(&type) != 1)
                               return;
                       len--;
               }
               tname = typetab[type];
               if(tname == 0)
                       tname = "unknown";

               if(readc(&size) != 1)
                       return;
               len--;
               bytes = ((size>>3)+1) * 512 * (1<<(2*(size&0x7)));

               if(ttype == 1)
                       ttname = "device";
               else
                       ttname = "attr device";
               print("%s %ld bytes of %ldns %s\n", ttname, bytes, ns, tname);
       }
}

void
tlonglnkmfc(int, int)
{
       int i, opos;
       uchar nfn, space, expect;
       int addr;

       readc(&nfn);
       for(i = 0; i < nfn; i++){
               readc(&space);
               addr = getlong(4);
               opos = pos;
               expect = Linktarget;
               while(addr > 0){
                       addr = tuple(addr, expect);
                       expect = 0;
               }
               pos = opos;
       }
}

static char *funcids[] = {
       "MULTI",
       "MEMORY",
       "SERIAL",
       "PARALLEL",
       "FIXED",
       "VIDEO",
       "NETWORK",
       "AIMS",
       "SCSI",
};

void
tfuncid(int, int)
{
       uchar func;

       readc(&func);
       print("Function %s\n",
               (func >= nelem(funcids))? "unknown function": funcids[func]);
}

void
tvers1(int ttype, int len)
{
       uchar c, major, minor;
       int  i;
       char string[512];

       USED(ttype);
       if(readc(&major) != 1)
               return;
       len--;
       if(readc(&minor) != 1)
               return;
       len--;
       print("version %d.%d\n", major, minor);
       while(len > 0){
               for(i = 0; len > 0 && i < sizeof(string); i++){
                       if(readc(&string[i]) != 1)
                               return;
                       len--;
                       c = string[i];
                       if(c == 0)
                               break;
                       if(c == 0xff){
                               if(i != 0){
                                       string[i] = 0;
                                       print("\t%s<missing null>\n", string);
                               }
                               return;
                       }
               }
               string[i] = 0;
               print("\t%s\n", string);
       }
}

void
tcfig(int ttype, int len)
{
       uchar size, rasize, rmsize;
       uchar last;
       ulong caddr;
       ulong cregs;
       int i;

       USED(ttype, len);
       if(readc(&size) != 1)
               return;
       rasize = (size&0x3) + 1;
       rmsize = ((size>>2)&0xf) + 1;
       if(readc(&last) != 1)
               return;
       caddr = getlong(rasize);
       cregs = getlong(rmsize);

       print("configuration registers at");
       for(i = 0; i < 16; i++)
               if((1<<i) & cregs)
                       print(" (%d)0x%lux", i, caddr + i*2);
       print("\n");
}

char *intrname[16] =
{
[0]     "memory",
[1]     "I/O",
[4]     "Custom 0",
[5]     "Custom 1",
[6]     "Custom 2",
[7]     "Custom 3",
};

ulong vexp[8] =
{
       1, 10, 100, 1000, 10000, 100000, 1000000, 10000000
};
ulong vmant[16] =
{
       10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90,
};

void
volt(char *name)
{
       uchar c;
       ulong microv;
       ulong exp;

       if(readc(&c) != 1)
               return;
       exp = vexp[c&0x7];
       microv = vmant[(c>>3)&0xf]*exp;
       while(c & 0x80){
               if(readc(&c) != 1)
                       return;
               switch(c){
               case 0x7d:
                       break;          /* high impedence when sleeping */
               case 0x7e:
               case 0x7f:
                       microv = 0;     /* no connection */
                       break;
               default:
                       exp /= 10;
                       microv += exp*(c&0x7f);
               }
       }
       print(" V%s %lduV", name, microv);
}

void
amps(char *name)
{
       uchar c;
       ulong amps;

       if(readc(&c) != 1)
               return;
       amps = vexp[c&0x7]*vmant[(c>>3)&0xf];
       while(c & 0x80){
               if(readc(&c) != 1)
                       return;
               if(c == 0x7d || c == 0x7e || c == 0x7f)
                       amps = 0;
       }
       if(amps >= 1000000)
               print(" I%s %ldmA", name, amps/100000);
       else if(amps >= 1000)
               print(" I%s %lduA", name, amps/100);
       else
               print(" I%s %ldnA", name, amps*10);
}

void
power(char *name)
{
       uchar feature;

       print("\t%s: ", name);
       if(readc(&feature) != 1)
               return;
       if(feature & 1)
               volt("nominal");
       if(feature & 2)
               volt("min");
       if(feature & 4)
               volt("max");
       if(feature & 8)
               amps("static");
       if(feature & 0x10)
               amps("avg");
       if(feature & 0x20)
               amps("peak");
       if(feature & 0x40)
               amps("powerdown");
       print("\n");
}

void
ttiming(char *name, int scale)
{
       uchar unscaled;
       ulong scaled;

       if(readc(&unscaled) != 1)
               return;
       scaled = (mantissa[(unscaled>>3)&0xf]*exponent[unscaled&7])/10;
       scaled = scaled * vexp[scale];
       print("\t%s %ldns\n", name, scaled);
}

void
timing(void)
{
       uchar c, i;

       if(readc(&c) != 1)
               return;
       i = c&0x3;
       if(i != 3)
               ttiming("max wait", i);
       i = (c>>2)&0x7;
       if(i != 7)
               ttiming("max ready/busy wait", i);
       i = (c>>5)&0x7;
       if(i != 7)
               ttiming("reserved wait", i);
}

void
range(int asize, int lsize)
{
       ulong address, len;

       address = getlong(asize);
       len = getlong(lsize);
       print("\t\t%lux - %lux\n", address, address+len);
}

char *ioaccess[4] =
{
       " no access",
       " 8bit access only",
       " 8bit or 16bit access",
       " selectable 8bit or 8&16bit access",
};

int
iospace(uchar c)
{
       int i;

       print("\tIO space %d address lines%s\n", c&0x1f, ioaccess[(c>>5)&3]);
       if((c & 0x80) == 0)
               return -1;

       if(readc(&c) != 1)
               return -1;

       for(i = (c&0xf)+1; i; i--)
               range((c>>4)&0x3, (c>>6)&0x3);
       return 0;
}

void
iospaces(void)
{
       uchar c;

       if(readc(&c) != 1)
               return;
       iospace(c);
}

void
irq(void)
{
       uchar c;
       uchar irq1, irq2;
       ushort i, irqs;

       if(readc(&c) != 1)
               return;
       if(c & 0x10){
               if(readc(&irq1) != 1)
                       return;
               if(readc(&irq2) != 1)
                       return;
               irqs = irq1|(irq2<<8);
       } else
               irqs = 1<<(c&0xf);
       print("\tinterrupts%s%s%s", (c&0x20)?":level":"", (c&0x40)?":pulse":"",
               (c&0x80)?":shared":"");
       for(i = 0; i < 16; i++)
               if(irqs & (1<<i))
                       print(", %d", i);
       print("\n");
}

void
memspace(int asize, int lsize, int host)
{
       ulong haddress, address, len;

       len = getlong(lsize)*256;
       address = getlong(asize)*256;
       if(host){
               haddress = getlong(asize)*256;
               print("\tmemory address range 0x%lux - 0x%lux hostaddr 0x%lux\n",
                       address, address+len, haddress);
       } else
               print("\tmemory address range 0x%lux - 0x%lux\n", address, address+len);
}

void
misc(void)
{
}

void
tentry(int ttype, int len)
{
       uchar c, i, feature;
       char *tname;
       char buf[16];

       USED(ttype, len);
       if(readc(&c) != 1)
               return;
       print("configuration %d%s\n", c&0x3f, (c&0x40)?" (default)":"");
       if(c & 0x80){
               if(readc(&i) != 1)
                       return;
               tname = intrname[i & 0xf];
               if(tname == 0){
                       tname = buf;
                       sprint(buf, "type %d", i & 0xf);
               }
               print("\t%s device, %s%s%s%s\n", tname,
                       (i&0x10)?" Battery status active":"",
                       (i&0x20)?" Write Protect active":"",
                       (i&0x40)?" Ready/Busy active":"",
                       (i&0x80)?" Memory Wait required":"");
       }
       if(readc(&feature) != 1)
               return;
       switch(feature&0x3){
       case 1:
               power("Vcc");
               break;
       case 2:
               power("Vcc");
               power("Vpp");
               break;
       case 3:
               power("Vcc");
               power("Vpp1");
               power("Vpp2");
               break;
       }
       if(feature&0x4)
               timing();
       if(feature&0x8)
               iospaces();
       if(feature&0x10)
               irq();
       switch((feature>>5)&0x3){
       case 1:
               memspace(0, 2, 0);
               break;
       case 2:
               memspace(2, 2, 0);
               break;
       case 3:
               if(readc(&c) != 1)
                       return;
               for(i = 0; i <= (c&0x7); i++)
                       memspace((c>>5)&0x3, (c>>3)&0x3, c&0x80);
               break;
       }
       if(feature&0x80)
               misc();
}