/*
* omap3530 traps, exceptions, interrupts, system calls.
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"

#include "ureg.h"
#include "arm.h"

enum {
       Nirqs = 96,
       Nvec = 8,               /* # of vectors at start of lexception.s */
       Bi2long = BI2BY * sizeof(long),
};

extern int notify(Ureg*);

extern int ldrexvalid;

/* omap35x intc (aka mpu_intc) */
typedef struct Intrregs Intrregs;
struct Intrregs {
       /*
        * the manual inserts "INTCPS_" before each register name;
        * we'll just assume the prefix.
        */
       uchar   _pad0[4*4];
       ulong   sysconfig;
       ulong   sysstatus;              /* ro */
       uchar   _pad1[0x40 - 0x18];
       ulong   sir_irq;                /* ro */
       ulong   sir_fiq;                /* ro */
       ulong   control;
       ulong   protection;
       ulong   idle;
       uchar   _pad2[0x60 - 0x54];
       ulong   irq_priority;
       ulong   fiq_priority;
       ulong   threshold;
       uchar   _pad3[0x80 - 0x6c];
       struct Bits {                   /* bitmaps */
               ulong   itr;            /* ro: pending intrs (no mask) */
               ulong   mir;            /* interrupt mask: 1 means masked */
               ulong   mir_clear;      /* wo: 1 sets the bit */
               ulong   mir_set;        /* wo: 1 clears the bit */
               ulong   isr_set;        /* software interrupts */
               ulong   isr_clear;      /* wo */
               ulong   pending_irq;    /* ro */
               ulong   pending_fiq;    /* ro */
       } bits[3];                      /* 3*32 = 96 (Nirqs) */
       ulong   ilr[Nirqs];
};

enum {
       /* sysconfig bits */
       Softreset       = 1<<1,

       /* sysstatus bits */
       Resetdone       = 1<<0,

       /* sir_irq/fiq bits */
       Activeirq       = MASK(7),

       /* control bits */
       Newirqagr       = 1<<0,

       /* protection bits */
       Protection      = 1<<0,

       /* irq/fiq_priority bits */
       Irqpriority     = MASK(6),

       /* threshold bits */
       Prioritythreshold = MASK(8),

       /* ilr bits */
       Priority        = MASK(8) - MASK(2),
};

typedef struct Vctl Vctl;
typedef struct Vctl {
       Vctl*   next;           /* handlers on this vector */
       char    *name;          /* of driver, xallocated */
       void    (*f)(Ureg*, void*);     /* handler to call */
       void*   a;              /* argument to call it with */
} Vctl;

static Lock vctllock;
static Vctl* vctl[Nirqs];

/*
*   Layout at virtual address 0.
*/
typedef struct Vpage0 {
       void    (*vectors[Nvec])(void);
       u32int  vtable[Nvec];
} Vpage0;
static Vpage0 *vpage0;

uvlong ninterrupt;
uvlong ninterruptticks;
int irqtooearly = 1;

static volatile int probing, trapped;

static int
irqinuse(uint irq)
{
       Intrregs *ip = (Intrregs *)PHYSINTC;

       /*
        * mir registers are odd: a 0 bit means intr unmasked (i.e.,
        * we've unmasked it because it's in use).
        */
       return (ip->bits[irq / Bi2long].mir & (1 << (irq % Bi2long))) == 0;
}

static void
intcmask(uint irq)
{
       Intrregs *ip = (Intrregs *)PHYSINTC;

       ip->bits[irq / Bi2long].mir_set = 1 << (irq % Bi2long);
       coherence();
}

static void
intcunmask(uint irq)
{
       Intrregs *ip = (Intrregs *)PHYSINTC;

       ip->bits[irq / Bi2long].mir_clear = 1 << (irq % Bi2long);
       coherence();
}

static void
intcmaskall(void)
{
       int i;
       Intrregs *ip = (Intrregs *)PHYSINTC;

       for (i = 0; i < 3; i++)
               ip->bits[i].mir_set = ~0;
       coherence();
}

static void
intcunmaskall(void)
{
       int i;
       Intrregs *ip = (Intrregs *)PHYSINTC;

       for (i = 0; i < 3; i++)
               ip->bits[i].mir_clear = ~0;
       coherence();
}

static void
intcinvertall(void)
{
       int i, s;
       ulong bits;
       Intrregs *ip = (Intrregs *)PHYSINTC;

       s = splhi();
       for (i = 0; i < 3; i++) {
               bits = ip->bits[i].mir;
               ip->bits[i].mir_set = ~0;       /* mask all */
               coherence();
               /* clearing enables only those intrs. that were disabled */
               ip->bits[i].mir_clear = bits;
       }
       coherence();
       splx(s);
}

static void
intrsave(ulong buf[3])
{
       int i;
       Intrregs *ip = (Intrregs *)PHYSINTC;

       for (i = 0; i < nelem(buf); i++)
               buf[i] = ip->bits[i].mir;
       coherence();
}

static void
intrrestore(ulong buf[3])
{
       int i, s;
       Intrregs *ip = (Intrregs *)PHYSINTC;

       s = splhi();
       for (i = 0; i < nelem(buf); i++) {
               ip->bits[i].mir_clear = ~0;     /* unmask all */
               coherence();
               ip->bits[i].mir_set = buf[i];   /* mask previously disabled */
       }
       coherence();
       splx(s);
}

/*
*  set up for exceptions
*/
void
trapinit(void)
{
       int i;
       Intrregs *ip = (Intrregs *)PHYSINTC;

       /* set up the exception vectors */
       vpage0 = (Vpage0*)HVECTORS;
       memmove(vpage0->vectors, vectors, sizeof(vpage0->vectors));
       memmove(vpage0->vtable, vtable, sizeof(vpage0->vtable));
       cacheuwbinv();
       l2cacheuwbinv();

       /* set up the stacks for the interrupt modes */
       setr13(PsrMfiq, m->sfiq);
       setr13(PsrMirq, m->sirq);
       setr13(PsrMabt, m->sabt);
       setr13(PsrMund, m->sund);
#ifdef HIGH_SECURITY
       setr13(PsrMmon, m->smon);
#endif
       setr13(PsrMsys, m->ssys);

       intcmaskall();
       ip->control = 0;
       ip->threshold = Prioritythreshold;      /* disable threshold */
       for (i = 0; i < Nirqs; i++)
               ip->ilr[i] = 0<<2 | 0;  /* all intrs pri 0 & to irq, not fiq */
       irqtooearly = 0;
       coherence();
}

void
intrsoff(void)
{
       Intrregs *ip = (Intrregs *)PHYSINTC;

       intcmaskall();
       ip->control = Newirqagr;        /* dismiss interrupt */
       coherence();
}

/*
*  enable an irq interrupt
*/
int
irqenable(int irq, void (*f)(Ureg*, void*), void* a, char *name)
{
       Vctl *v;

       if(irq >= nelem(vctl) || irq < 0)
               panic("irqenable irq %d", irq);

       if (irqtooearly) {
               iprint("irqenable for %d %s called too early\n", irq, name);
               return -1;
       }
       if(irqinuse(irq))
               print("irqenable: %s: irq %d already in use, chaining\n",
                       name, irq);
       v = malloc(sizeof(Vctl));
       if (v == nil)
               panic("irqenable: malloc Vctl");
       v->f = f;
       v->a = a;
       v->name = malloc(strlen(name)+1);
       if (v->name == nil)
               panic("irqenable: malloc name");
       strcpy(v->name, name);

       lock(&vctllock);
       v->next = vctl[irq];
       vctl[irq] = v;

       intcunmask(irq);
       unlock(&vctllock);
       return 0;
}

/*
*  disable an irq interrupt
*/
int
irqdisable(int irq, void (*f)(Ureg*, void*), void* a, char *name)
{
       Vctl **vp, *v;

       if(irq >= nelem(vctl) || irq < 0)
               panic("irqdisable irq %d", irq);

       lock(&vctllock);
       for(vp = &vctl[irq]; v = *vp; vp = &v->next)
               if (v->f == f && v->a == a && strcmp(v->name, name) == 0){
                       print("irqdisable: remove %s\n", name);
                       *vp = v->next;
                       free(v);
                       break;
               }

       if(v == nil)
               print("irqdisable: irq %d, name %s not enabled\n", irq, name);
       if(vctl[irq] == nil){
               print("irqdisable: clear icmr bit %d\n", irq);
               intcmask(irq);
       }
       unlock(&vctllock);

       return 0;
}

/*
*  called by trap to handle access faults
*/
static void
faultarm(Ureg *ureg, uintptr va, int user, int read)
{
       int n, insyscall;
       char buf[ERRMAX];

       if(up == nil) {
               dumpregs(ureg);
               panic("fault: nil up in faultarm, accessing %#p", va);
       }
       insyscall = up->insyscall;
       up->insyscall = 1;
       n = fault(va, ureg->pc, read);
       if(n < 0){
               if(!user){
                       dumpregs(ureg);
                       panic("fault: kernel accessing %#p", va);
               }
               /* don't dump registers; programs suicide all the time */
               snprint(buf, sizeof buf, "sys: trap: fault %s va=%#p",
                       read? "read": "write", va);
               postnote(up, 1, buf, NDebug);
       }
       up->insyscall = insyscall;
}

/*
*  called by trap to handle interrupts.
*  returns true iff a clock interrupt, thus maybe reschedule.
*/
static int
irq(Ureg* ureg)
{
       int clockintr;
       uint irqno, handled, t, ticks = perfticks();
       Intrregs *ip = (Intrregs *)PHYSINTC;
       Vctl *v;
       static int nesting, lastirq = -1;

       handled = 0;
       irqno = ip->sir_irq & Activeirq;

       if (irqno >= 37 && irqno <= 47)         /* this is a clock intr? */
               m->inclockintr++;               /* yes, count nesting */
       lastirq = irqno;

       if (irqno >= nelem(vctl)) {
               iprint("trap: irq %d >= # vectors (%d)\n", irqno, nelem(vctl));
               ip->control = Newirqagr;        /* dismiss interrupt */
               return 0;
       }

       ++nesting;
       for(v = vctl[irqno]; v != nil; v = v->next)
               if (v->f) {
                       if (islo())
                               panic("trap: pl0 before trap handler for %s",
                                       v->name);
                       v->f(ureg, v->a);
                       if (islo())
                               panic("trap: %s lowered pl", v->name);
//                      splhi();                /* in case v->f lowered pl */
                       handled++;
               }
       if(!handled) {
               iprint("unexpected interrupt: irq %d", irqno);
               switch (irqno) {
               case 56:
               case 57:
                       iprint(" (I⁲C)");
                       break;
               case 83:
               case 86:
               case 94:
                       iprint(" (MMC)");
                       break;
               }

               if(irqno < nelem(vctl)) {
                       intcmask(irqno);
                       iprint(", now masked");
               }
               iprint("\n");
       }
       t = perfticks();
       ninterrupt++;
       if(t < ticks)
               ninterruptticks += ticks-t;
       else
               ninterruptticks += t-ticks;
       ip->control = Newirqagr;        /* dismiss interrupt */
       coherence();

       --nesting;
       clockintr = m->inclockintr == 1;
       if (irqno >= 37 && irqno <= 47)
               m->inclockintr--;
       return clockintr;
}

/*
*  returns 1 if the instruction writes memory, 0 otherwise
*/
int
writetomem(ulong inst)
{
       /* swap always write memory */
       if((inst & 0x0FC00000) == 0x01000000)
               return 1;

       /* loads and stores are distinguished by bit 20 */
       if(inst & (1<<20))
               return 0;

       return 1;
}

void    prgpmcerrs(void);

/*
*  here on all exceptions other than syscall (SWI)
*/
void
trap(Ureg *ureg)
{
       int clockintr, user, x, rv, rem;
       ulong inst, fsr;
       uintptr va;
       char buf[ERRMAX];

       splhi();                        /* paranoia */
       if(up != nil)
               rem = ((char*)ureg)-up->kstack;
       else
               rem = ((char*)ureg)-((char*)m+sizeof(Mach));
       if(rem < 1024) {
               iprint("trap: %d stack bytes left, up %#p ureg %#p at pc %#lux\n",
                       rem, up, ureg, ureg->pc);
               delay(1000);
               dumpstack();
               panic("trap: %d stack bytes left, up %#p ureg %#p at pc %#lux",
                       rem, up, ureg, ureg->pc);
       }

       user = (ureg->psr & PsrMask) == PsrMusr;
       if(user){
               up->dbgreg = ureg;
               cycles(&up->kentry);
       }

       /*
        * All interrupts/exceptions should be resumed at ureg->pc-4,
        * except for Data Abort which resumes at ureg->pc-8.
        */
       if(ureg->type == (PsrMabt+1))
               ureg->pc -= 8;
       else
               ureg->pc -= 4;

       clockintr = 0;          /* if set, may call sched() before return */
       switch(ureg->type){
       default:
               panic("unknown trap; type %#lux, psr mode %#lux", ureg->type,
                       ureg->psr & PsrMask);
               break;
       case PsrMirq:
               ldrexvalid = 0;
               clockintr = irq(ureg);
               m->intr++;
               break;
       case PsrMabt:                   /* prefetch fault */
               ldrexvalid = 0;
               x = ifsrget();
               fsr = (x>>7) & 0x8 | x & 0x7;
               switch(fsr){
               case 0x02:              /* instruction debug event (BKPT) */
                       if(user){
                               snprint(buf, sizeof buf, "sys: breakpoint");
                               postnote(up, 1, buf, NDebug);
                       }else{
                               iprint("kernel bkpt: pc %#lux inst %#ux\n",
                                       ureg->pc, *(u32int*)ureg->pc);
                               panic("kernel bkpt");
                       }
                       break;
               default:
                       faultarm(ureg, ureg->pc, user, 1);
                       break;
               }
               break;
       case PsrMabt+1:                 /* data fault */
               ldrexvalid = 0;
               va = farget();
               inst = *(ulong*)(ureg->pc);
               /* bits 12 and 10 have to be concatenated with status */
               x = fsrget();
               fsr = (x>>7) & 0x20 | (x>>6) & 0x10 | x & 0xf;
               if (probing && !user) {
                       if (trapped++ > 0)
                               panic("trap: recursive probe %#lux", va);
                       ureg->pc += 4;  /* continue at next instruction */
                       break;
               }
               switch(fsr){
               default:
               case 0xa:               /* ? was under external abort */
                       panic("unknown data fault, 6b fsr %#lux", fsr);
                       break;
               case 0x0:
                       panic("vector exception at %#lux", ureg->pc);
                       break;
               case 0x1:               /* alignment fault */
               case 0x3:               /* access flag fault (section) */
                       if(user){
                               snprint(buf, sizeof buf,
                                       "sys: alignment: pc %#lux va %#p\n",
                                       ureg->pc, va);
                               postnote(up, 1, buf, NDebug);
                       } else
                               panic("kernel alignment: pc %#lux va %#p", ureg->pc, va);
                       break;
               case 0x2:
                       panic("terminal exception at %#lux", ureg->pc);
                       break;
               case 0x4:               /* icache maint fault */
               case 0x6:               /* access flag fault (page) */
               case 0x8:               /* precise external abort, non-xlat'n */
               case 0x28:
               case 0xc:               /* l1 translation, precise ext. abort */
               case 0x2c:
               case 0xe:               /* l2 translation, precise ext. abort */
               case 0x2e:
               case 0x16:              /* imprecise ext. abort, non-xlt'n */
               case 0x36:
                       panic("external abort %#lux pc %#lux addr %#p",
                               fsr, ureg->pc, va);
                       break;
               case 0x1c:              /* l1 translation, precise parity err */
               case 0x1e:              /* l2 translation, precise parity err */
               case 0x18:              /* imprecise parity or ecc err */
                       panic("translation parity error %#lux pc %#lux addr %#p",
                               fsr, ureg->pc, va);
                       break;
               case 0x5:               /* translation fault, no section entry */
               case 0x7:               /* translation fault, no page entry */
                       faultarm(ureg, va, user, !writetomem(inst));
                       break;
               case 0x9:
               case 0xb:
                       /* domain fault, accessing something we shouldn't */
                       if(user){
                               snprint(buf, sizeof buf,
                                       "sys: access violation: pc %#lux va %#p\n",
                                       ureg->pc, va);
                               postnote(up, 1, buf, NDebug);
                       } else
                               panic("kernel access violation: pc %#lux va %#p",
                                       ureg->pc, va);
                       break;
               case 0xd:
               case 0xf:
                       /* permission error, copy on write or real permission error */
                       faultarm(ureg, va, user, !writetomem(inst));
                       break;
               }
               break;
       case PsrMund:                   /* undefined instruction */
               if(user){
                       if(seg(up, ureg->pc, 0) != nil &&
                          *(u32int*)ureg->pc == 0xD1200070){
                               snprint(buf, sizeof buf, "sys: breakpoint");
                               postnote(up, 1, buf, NDebug);
                       }else{
                               /* look for floating point instructions to interpret */
                               x = spllo();
                               rv = fpiarm(ureg);
                               splx(x);
                               if(rv == 0){
                                       ldrexvalid = 0;
                                       snprint(buf, sizeof buf,
                                               "undefined instruction: pc %#lux\n",
                                               ureg->pc);
                                       postnote(up, 1, buf, NDebug);
                               }
                       }
               }else{
                       if (ureg->pc & 3) {
                               iprint("rounding fault pc %#lux down to word\n",
                                       ureg->pc);
                               ureg->pc &= ~3;
                       }
                       iprint("undefined instruction: pc %#lux inst %#ux\n",
                               ureg->pc, ((u32int*)ureg->pc)[-2]);
                       panic("undefined instruction");
               }
               break;
       }
       splhi();

       /* delaysched set because we held a lock or because our quantum ended */
       if(up && up->delaysched && clockintr){
               ldrexvalid = 0;
               sched();                /* can cause more traps */
               splhi();
       }

       if(user){
               if(up->procctl || up->nnote)
                       notify(ureg);
               kexit(ureg);
       }
}

/*
* Fill in enough of Ureg to get a stack trace, and call a function.
* Used by debugging interface rdb.
*/
void
callwithureg(void (*fn)(Ureg*))
{
       Ureg ureg;

       ureg.pc = getcallerpc(&fn);
       ureg.sp = (uintptr)&fn;
       fn(&ureg);
}

static void
dumpstackwithureg(Ureg *ureg)
{
       int x;
       uintptr l, v, i, estack;
       char *s;

       dumpregs(ureg);
       if((s = getconf("*nodumpstack")) != nil && strcmp(s, "0") != 0){
               iprint("dumpstack disabled\n");
               return;
       }
       iprint("dumpstack\n");

       x = 0;
       x += iprint("ktrace /kernel/path %#.8lux %#.8lux %#.8lux # pc, sp, link\n",
               ureg->pc, ureg->sp, ureg->r14);
       delay(20);
       i = 0;
       if(up
       && (uintptr)&l >= (uintptr)up->kstack
       && (uintptr)&l <= (uintptr)up->kstack+KSTACK)
               estack = (uintptr)up->kstack+KSTACK;
       else if((uintptr)&l >= (uintptr)m->stack
       && (uintptr)&l <= (uintptr)m+MACHSIZE)
               estack = (uintptr)m+MACHSIZE;
       else
               return;
       x += iprint("estackx %p\n", estack);

       for(l = (uintptr)&l; l < estack; l += sizeof(uintptr)){
               v = *(uintptr*)l;
               if((KTZERO < v && v < (uintptr)etext) || estack-l < 32){
                       x += iprint("%.8p ", v);
                       delay(20);
                       i++;
               }
               if(i == 8){
                       i = 0;
                       x += iprint("\n");
                       delay(20);
               }
       }
       if(i)
               iprint("\n");
}

void
dumpstack(void)
{
       callwithureg(dumpstackwithureg);
}

/*
* dump system control coprocessor registers
*/
static void
dumpscr(void)
{
       iprint("0:\t%#8.8ux id\n", cpidget());
       iprint("\t%8.8#ux ct\n", cpctget());
       iprint("1:\t%#8.8ux control\n", controlget());
       iprint("2:\t%#8.8ux ttb\n", ttbget());
       iprint("3:\t%#8.8ux dac\n", dacget());
       iprint("4:\t(reserved)\n");
       iprint("5:\t%#8.8ux fsr\n", fsrget());
       iprint("6:\t%#8.8ux far\n", farget());
       iprint("7:\twrite-only cache\n");
       iprint("8:\twrite-only tlb\n");
       iprint("13:\t%#8.8ux pid\n", pidget());
       delay(10);
}

/*
* dump general registers
*/
static void
dumpgpr(Ureg* ureg)
{
       if(up != nil)
               iprint("cpu%d: registers for %s %lud\n",
                       m->machno, up->text, up->pid);
       else
               iprint("cpu%d: registers for kernel\n", m->machno);

       delay(20);
       iprint("%#8.8lux\tr0\n", ureg->r0);
       iprint("%#8.8lux\tr1\n", ureg->r1);
       iprint("%#8.8lux\tr2\n", ureg->r2);
       delay(20);
       iprint("%#8.8lux\tr3\n", ureg->r3);
       iprint("%#8.8lux\tr4\n", ureg->r4);
       iprint("%#8.8lux\tr5\n", ureg->r5);
       delay(20);
       iprint("%#8.8lux\tr6\n", ureg->r6);
       iprint("%#8.8lux\tr7\n", ureg->r7);
       iprint("%#8.8lux\tr8\n", ureg->r8);
       delay(20);
       iprint("%#8.8lux\tr9 (up)\n", ureg->r9);
       iprint("%#8.8lux\tr10 (m)\n", ureg->r10);
       iprint("%#8.8lux\tr11 (loader temporary)\n", ureg->r11);
       iprint("%#8.8lux\tr12 (SB)\n", ureg->r12);
       delay(20);
       iprint("%#8.8lux\tr13 (sp)\n", ureg->r13);
       iprint("%#8.8lux\tr14 (link)\n", ureg->r14);
       iprint("%#8.8lux\tr15 (pc)\n", ureg->pc);
       delay(20);
       iprint("%10.10lud\ttype\n", ureg->type);
       iprint("%#8.8lux\tpsr\n", ureg->psr);
       delay(20);
}

void
dumpregs(Ureg* ureg)
{
       dumpgpr(ureg);
       dumpscr();
}

vlong
probeaddr(uintptr addr)
{
       vlong v;
       static Lock fltlck;

       ilock(&fltlck);
       trapped = 0;
       probing = 1;
       coherence();

       v = *(ulong *)addr;     /* this may cause a fault */
       USED(probing);
       coherence();

       probing = 0;
       coherence();
       if (trapped)
               v = -1;
       iunlock(&fltlck);
       return v;
}