Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

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

/*
 *  tlb entry 0 is used only by mmuswitch() to set the current tlb pid.
 *
 *  It is apparently assumed that user tlb entries are not
 *  overwritten during start-up, so ...
 *  During system start-up (before up first becomes non-nil),
 *  Kmap entries start at tlb index 1 and work their way up until
 *  kmapinval() removes them.  They then restart at 1.  As long as there
 *  are few kmap entries they will not pass tlbroff (the WIRED tlb entry
 *  limit) and interfere with user tlb entries.
 *  Once start-up is over, we combine the kernel and user tlb pools into one,
 *  in the hope of making better use of the tlb on systems with small ones.
 *
 *  All invalidations of the tlb are via indexed entries.  The virtual
 *  address used is always 'KZERO | (x<<(PGSHIFT+1) | currentpid' where
 *  'x' is the index into the tlb.  This ensures that the current pid doesn't
 *  change and that no two invalidated entries have matching virtual
 *  addresses just in case SGI/MIPS ever makes a chip that cares (as
 *  they keep threatening).  These entries should never be used in
 *  lookups since accesses to KZERO addresses don't go through the tlb
 *  (actually only true of KSEG0 and KSEG1; KSEG2 and KSEG3 do go
 *  through the tlb).
 */

#define TLBINVAL(x, pid) puttlbx(x, KZERO|((x)<<(PGSHIFT+1))|(pid), 0, 0, PGSZ)

enum {
        Debugswitch     = 0,
        Debughash       = 0,
};

static ulong ktime[8];          /* only for first 8 cpus */

void
tlbinit(void)
{
        int i;

        for(i=0; i<NTLB; i++)
                TLBINVAL(i, 0);
}

Lock    kmaplock;
KMap    kpte[KPTESIZE];
KMap*   kmapfree;

static int minfree = KPTESIZE;
static int lastfree;
static int tlbroff = TLBROFF;

static void
nfree(void)
{
        int i;
        KMap *k;

        i = 0;
        for(k=kmapfree; k; k=k->next)
                i++;
        if(i<minfree){
                iprint("%d free\n", i);
                minfree = i;
        }
        lastfree = i;
}

void
kmapinit(void)
{
        KMap *k, *klast;

        lock(&kmaplock);
        kmapfree = kpte;
        klast = &kpte[KPTESIZE-1];
        for(k=kpte; k<klast; k++)
                k->next = k+1;
        k->next = 0;
        unlock(&kmaplock);

        m->ktlbnext = TLBOFF;
}

void
kmapdump(void)
{
        int i;

        for(i=0; i<KPTESIZE; i++)
                iprint("%d: %lud pc=%#lux\n", i, kpte[i].ref, kpte[i].pc);
}

static int
putktlb(KMap *k)
{
        int x;
        ulong virt;
        ulong tlbent[3];

        virt = k->virt & ~BY2PG | TLBPID(tlbvirt());
        x = gettlbp(virt, tlbent);
        if (!m->paststartup)
                if (up) {                       /* startup just ended? */
                        tlbroff = 1;
                        setwired(tlbroff);      /* share all-but-one entries */
                        m->paststartup = 1;
                } else if (x < 0) {             /* no such entry? use next */
                        x = m->ktlbnext++;
                        if(m->ktlbnext >= tlbroff)
                                m->ktlbnext = TLBOFF;
                }
        if (x < 0)              /* no entry for va? overwrite random one */
                x = puttlb(virt, k->phys0, k->phys1);
        else
                puttlbx(x, virt, k->phys0, k->phys1, PGSZ);
        m->ktlbx[x] = 1;
        return x;
}

/*
 *  Arrange that the KMap'd virtual address will hit the same
 *  primary cache line as pg->va by making bits 14...12 of the
 *  tag the same as virtual address.  These bits are the index
 *  into the primary cache and are checked whenever accessing
 *  the secondary cache through the primary.  Violation causes
 *  a VCE trap.
 */
KMap *
kmap(Page *pg)
{
        int s, printed = 0;
        ulong pte, virt;
        KMap *k;

        s = splhi();
        lock(&kmaplock);

        if(kmapfree == 0) {
retry:
                unlock(&kmaplock);
                kmapinval();            /* try and free some */
                lock(&kmaplock);
                if(kmapfree == 0){
                        unlock(&kmaplock);
                        splx(s);
                        if(printed++ == 0){
                        /* using iprint here we get mixed up with other prints */
                                print("%d KMAP RETRY %#lux ktime %ld %ld %ld %ld %ld %ld %ld %ld\n",
                                        m->machno, getcallerpc(&pg),
                                        ktime[0], ktime[1], ktime[2], ktime[3],
                                        ktime[4], ktime[5], ktime[6], ktime[7]);
                                delay(200);
                        }
                        splhi();
                        lock(&kmaplock);
                        goto retry;
                }
        }

        k = kmapfree;
        kmapfree = k->next;

        k->pg = pg;
        /*
         * One for the allocation,
         * One for kactive
         */
        k->pc = getcallerpc(&pg);
        k->ref = 2;
        k->konmach[m->machno] = m->kactive;
        m->kactive = k;

        virt = pg->va;
        /* bits 14..12 form the secondary-cache virtual index */
        virt &= PIDX;
        virt |= KMAPADDR | ((k-kpte)<<KMAPSHIFT);

        k->virt = virt;
        pte = PPN(pg->pa)|PTECACHABILITY|PTEGLOBL|PTEWRITE|PTEVALID;
        if(virt & BY2PG) {
                k->phys0 = PTEGLOBL | PTECACHABILITY;
                k->phys1 = pte;
        }
        else {
                k->phys0 = pte;
                k->phys1 = PTEGLOBL | PTECACHABILITY;
        }

        putktlb(k);
        unlock(&kmaplock);

        splx(s);
        return k;
}

void
kunmap(KMap *k)
{
        int s;

        s = splhi();
        if(decref(k) == 0) {
                k->virt = 0;
                k->phys0 = 0;
                k->phys1 = 0;
                k->pg = 0;

                lock(&kmaplock);
                k->next = kmapfree;
                kmapfree = k;
//nfree();
                unlock(&kmaplock);
        }
        splx(s);
}

void
kfault(Ureg *ur)                        /* called from trap() */
{
        ulong index, addr;
        KMap *k, *f;

        addr = ur->badvaddr;
        index = (addr & ~KSEGM) >> KMAPSHIFT;
        if(index >= KPTESIZE)
                panic("kmapfault: %#lux", addr);

        k = &kpte[index];
        if(k->virt == 0)
                panic("kmapfault: unmapped %#lux", addr);

        for(f = m->kactive; f; f = f->konmach[m->machno])
                if(f == k)
                        break;
        if(f == 0) {
                incref(k);
                k->konmach[m->machno] = m->kactive;
                m->kactive = k;
        }
        putktlb(k);
}

void
kmapinval(void)
{
        int mno, i, curpid;
        KMap *k, *next;
        uchar *ktlbx;

        if(m->machno < nelem(ktime))
                ktime[m->machno] = MACHP(0)->ticks;
        if(m->kactive == 0)
                return;

        curpid = PTEPID(TLBPID(tlbvirt()));
        ktlbx = m->ktlbx;
        for(i = 0; i < NTLB; i++, ktlbx++){
                if(*ktlbx == 0)
                        continue;
                TLBINVAL(i, curpid);
                *ktlbx = 0;
        }

        mno = m->machno;
        for(k = m->kactive; k; k = next) {
                next = k->konmach[mno];
                kunmap(k);
        }

        m->kactive = 0;
        m->ktlbnext = TLBOFF;
}

struct
{
        ulong   va;
        ulong   pl;
        ulong   ph;
} wired[NWTLB+1];               /* +1 to avoid zero size if NWTLB==0 */
// = {
//      PCIMEM,
//       (PCIMEM>>6) | PTEUNCACHED|PTEGLOBL|PTEWRITE|PTEVALID,
//      ((PCIMEM>>6) | PTEUNCACHED|PTEGLOBL|PTEWRITE|PTEVALID)+(1<<(PGSHIFT-6)),
//};

/*
 * allocate a virtual address corresponding to physical addr and map them.
 * run on cpu 0.
 */
ulong
wiredpte(vlong addr)
{
        int i;
        ulong va;

        for(i = 0; i < NWTLB; i++)
                if(wired[i].va == 0)
                        break;
        if(i >= NWTLB)
                panic("wiredpte: not enough wired TLB entries");

        va = WIREDADDR + i*256*MB;
        wired[i].va = va;
        wired[i].pl = (addr >> 6) | PTEUNCACHED|PTEGLOBL|PTEWRITE|PTEVALID;
        wired[i].ph = wired[i].pl + (1<<(PGSHIFT-6));

        puttlbx(i+WTLBOFF, va, wired[i].pl, wired[i].ph, PGSZ256M);
        return va;
}

void
machwire(void)
{
        int i;

        if(m->machno == 0)
                return;
        for(i = 0; i < NWTLB; i++)
                if(wired[i].va)
                        puttlbx(i+WTLBOFF, wired[i].va, wired[i].pl,
                                wired[i].ph, PGSZ256M);
}

void
mmuswitch(Proc *p)
{
        int tp;
        static char lasttext[32];

        if(Debugswitch && !p->kp){
                if(strncmp(lasttext, p->text, sizeof lasttext) != 0)
                        iprint("[%s]", p->text);
                strncpy(lasttext, p->text, sizeof lasttext);
        }

        if(p->newtlb) {
                memset(p->pidonmach, 0, sizeof p->pidonmach);
                p->newtlb = 0;
        }
        tp = p->pidonmach[m->machno];
        if(tp == 0)
                tp = newtlbpid(p);
        puttlbx(0, KZERO|PTEPID(tp), 0, 0, PGSZ);
}

void
mmurelease(Proc *p)
{
        memset(p->pidonmach, 0, sizeof p->pidonmach);
}

/*
 * Process must be splhi
 */
int
newtlbpid(Proc *p)
{
        int i, s;
        Proc **h;

        i = m->lastpid;
        h = m->pidproc;
        for(s = 0; s < NTLBPID; s++) {
                i++;
                if(i >= NTLBPID)
                        i = 1;
                if(h[i] == 0)
                        break;
        }

        if(h[i])
                purgetlb(i);
        if(h[i] != 0)
                panic("newtlb");

        m->pidproc[i] = p;
        p->pidonmach[m->machno] = i;
        m->lastpid = i;

        return i;
}

void
putmmu(ulong tlbvirt, ulong tlbphys, Page *pg)
{
        short tp;
        char *ctl;
        Softtlb *entry;
        int s;

        s = splhi();
        tp = up->pidonmach[m->machno];
        if(tp == 0)
                tp = newtlbpid(up);

        tlbvirt |= PTEPID(tp);
        if((tlbphys & PTEALGMASK) != PTEUNCACHED) {
                tlbphys &= ~PTEALGMASK;
                tlbphys |= PTECACHABILITY;
        }

        entry = putstlb(tlbvirt, tlbphys);
        puttlb(entry->virt, entry->phys0, entry->phys1);

        ctl = &pg->cachectl[m->machno];
        switch(*ctl) {
        case PG_TXTFLUSH:
                icflush((void*)pg->va, BY2PG);
                *ctl = PG_NOFLUSH;
                break;
        case PG_DATFLUSH:
                dcflush((void*)pg->va, BY2PG);
                *ctl = PG_NOFLUSH;
                break;
        case PG_NEWCOL:
                cleancache();           /* Too expensive */
                *ctl = PG_NOFLUSH;
                break;
        }
        splx(s);
}

void
purgetlb(int pid)
{
        int i, mno;
        Proc *sp, **pidproc;
        Softtlb *entry, *etab;

        m->tlbpurge++;

        /*
         * find all pid entries that are no longer used by processes
         */
        mno = m->machno;
        pidproc = m->pidproc;
        for(i=1; i<NTLBPID; i++) {
                sp = pidproc[i];
                if(sp && sp->pidonmach[mno] != i)
                        pidproc[i] = 0;
        }

        /*
         * shoot down the one we want
         */
        sp = pidproc[pid];
        if(sp != 0)
                sp->pidonmach[mno] = 0;
        pidproc[pid] = 0;

        /*
         * clean out all dead pids from the stlb;
         */
        entry = m->stb;
        for(etab = &entry[STLBSIZE]; entry < etab; entry++)
                if(pidproc[TLBPID(entry->virt)] == 0)
                        entry->virt = 0;

        /*
         * clean up the hardware
         */
        for(i=tlbroff; i<NTLB; i++)
                if(pidproc[TLBPID(gettlbvirt(i))] == 0)
                        TLBINVAL(i, pid);
}

void
flushmmu(void)
{
        int s;

        s = splhi();
        up->newtlb = 1;
        mmuswitch(up);
        splx(s);
}

/* tlbvirt also has TLBPID() in its low byte as the asid */
Softtlb*
putstlb(ulong tlbvirt, ulong tlbphys)
{
        int odd;
        Softtlb *entry;

        /* identical calculation in l.s/utlbmiss */
        entry = &m->stb[stlbhash(tlbvirt)];
        odd = tlbvirt & BY2PG;          /* even/odd bit */
        tlbvirt &= ~BY2PG;              /* zero even/odd bit */
        if(entry->virt != tlbvirt) {    /* not my entry? overwrite it */
                if(entry->virt != 0) {
                        m->hashcoll++;
                        if (Debughash)
                                iprint("putstlb: hash collision: %#lx old virt "
                                        "%#lux new virt %#lux page %#lux\n",
                                        entry - m->stb, entry->virt, tlbvirt,
                                        tlbvirt >> (PGSHIFT+1));
                }
                entry->virt = tlbvirt;
                entry->phys0 = 0;
                entry->phys1 = 0;
        }

        if(odd)
                entry->phys1 = tlbphys;
        else
                entry->phys0 = tlbphys;

        if(entry->phys0 == 0 && entry->phys1 == 0)
                entry->virt = 0;

        return entry;
}

void
checkmmu(ulong, ulong)
{
}

void
countpagerefs(ulong*, int)
{
}

/*
 * Return the number of bytes that can be accessed via KADDR(pa).
 * If pa is not a valid argument to KADDR, return 0.
 */
ulong
cankaddr(ulong pa)
{
        if(pa >= KZERO || pa >= MEMSIZE)
                return 0;
        return MEMSIZE - pa;
}