#include        "u.h"
#include        "../port/lib.h"
#include        "mem.h"
#include        "dat.h"
#include        "fns.h"
#include        "../port/error.h"


#define LRES    3               /* log of PC resolution */
#define SZ      4               /* sizeof of count cell; well known as 4 */

struct
{
       uintptr minpc;
       uintptr maxpc;
       int     nbuf;
       int     time;
       ulong   *buf;
}kprof;

enum{
       Kprofdirqid,
       Kprofdataqid,
       Kprofctlqid,
};
Dirtab kproftab[]={
       ".",    {Kprofdirqid, 0, QTDIR},                0,      DMDIR|0550,
       "kpdata",       {Kprofdataqid},         0,      0600,
       "kpctl",        {Kprofctlqid},          0,      0600,
};

static void
_kproftimer(uintptr pc)
{
       extern void spldone(void);

       if(kprof.time == 0)
               return;
       /*
        *  if the pc is coming out of spllo or splx,
        *  use the pc saved when we went splhi.
        */
       if(pc>=(uintptr)spllo && pc<=(uintptr)spldone)
               pc = m->splpc;

       kprof.buf[0] += TK2MS(1);
       if(kprof.minpc<=pc && pc<kprof.maxpc){
               pc -= kprof.minpc;
               pc >>= LRES;
               kprof.buf[pc] += TK2MS(1);
       }else
               kprof.buf[1] += TK2MS(1);
}

static void
kprofinit(void)
{
       if(SZ != sizeof kprof.buf[0])
               panic("kprof size");
       kproftimer = _kproftimer;
}

static Chan*
kprofattach(char *spec)
{
       ulong n;

       /* allocate when first used */
       kprof.minpc = KTZERO;
       kprof.maxpc = (uintptr)etext;
       kprof.nbuf = (kprof.maxpc-kprof.minpc) >> LRES;
       n = kprof.nbuf*SZ;
       if(kprof.buf == 0) {
               kprof.buf = xalloc(n);
               if(kprof.buf == 0)
                       error(Enomem);
       }
       kproftab[1].length = n;
       return devattach('K', spec);
}

static Walkqid*
kprofwalk(Chan *c, Chan *nc, char **name, int nname)
{
       return devwalk(c, nc, name, nname, kproftab, nelem(kproftab), devgen);
}

static int
kprofstat(Chan *c, uchar *db, int n)
{
       return devstat(c, db, n, kproftab, nelem(kproftab), devgen);
}

static Chan*
kprofopen(Chan *c, int omode)
{
       if(c->qid.type == QTDIR){
               if(omode != OREAD)
                       error(Eperm);
       }
       c->mode = openmode(omode);
       c->flag |= COPEN;
       c->offset = 0;
       return c;
}

static void
kprofclose(Chan*)
{
}

static long
kprofread(Chan *c, void *va, long n, vlong off)
{
       ulong end;
       ulong w, *bp;
       uchar *a, *ea;
       ulong offset = off;

       switch((int)c->qid.path){
       case Kprofdirqid:
               return devdirread(c, va, n, kproftab, nelem(kproftab), devgen);

       case Kprofdataqid:
               end = kprof.nbuf*SZ;
               if(offset & (SZ-1))
                       error(Ebadarg);
               if(offset >= end){
                       n = 0;
                       break;
               }
               if(offset+n > end)
                       n = end-offset;
               n &= ~(SZ-1);
               a = va;
               ea = a + n;
               bp = kprof.buf + offset/SZ;
               while(a < ea){
                       w = *bp++;
                       *a++ = w>>24;
                       *a++ = w>>16;
                       *a++ = w>>8;
                       *a++ = w>>0;
               }
               break;

       default:
               n = 0;
               break;
       }
       return n;
}

static long
kprofwrite(Chan *c, void *a, long n, vlong)
{
       switch((int)(c->qid.path)){
       case Kprofctlqid:
               if(strncmp(a, "startclr", 8) == 0){
                       memset((char *)kprof.buf, 0, kprof.nbuf*SZ);
                       kprof.time = 1;
               }else if(strncmp(a, "start", 5) == 0)
                       kprof.time = 1;
               else if(strncmp(a, "stop", 4) == 0)
                       kprof.time = 0;
               break;
       default:
               error(Ebadusefd);
       }
       return n;
}

Dev kprofdevtab = {
       'K',
       "kprof",

       devreset,
       kprofinit,
       devshutdown,
       kprofattach,
       kprofwalk,
       kprofstat,
       kprofopen,
       devcreate,
       kprofclose,
       kprofread,
       devbread,
       kprofwrite,
       devbwrite,
       devremove,
       devwstat,
};