plan9fox/sys/src/9/pc/mtrr.c

346 lines
6.5 KiB
C

/*
* memory-type region registers.
*
* due to the possibility of extended addresses (for PAE)
* as large as 36 bits coming from the e820 memory map and the like,
* we'll use vlongs to hold addresses and lengths, even though we don't
* implement PAE in Plan 9.
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
enum {
/*
* MTRR Physical base/mask are indexed by
* MTRRPhys{Base|Mask}N = MTRRPhys{Base|Mask}0 + 2*N
*/
MTRRPhysBase0 = 0x200,
MTRRPhysMask0 = 0x201,
MTRRDefaultType = 0x2FF,
MTRRCap = 0xFE,
Nmtrr = 8,
/* cpuid extended function codes */
Exthighfunc = 1ul << 31,
Extprocsigamd,
Extprocname0,
Extprocname1,
Extprocname2,
Exttlbl1,
Extl2,
Extapm,
Extaddrsz,
Paerange = 1LL << 36,
};
enum {
CR4PageGlobalEnable = 1 << 7,
CR0CacheDisable = 1 << 30,
};
enum {
Uncacheable = 0,
Writecomb = 1,
Unknown1 = 2,
Unknown2 = 3,
Writethru = 4,
Writeprot = 5,
Writeback = 6,
};
enum {
Capvcnt = 0xff, /* mask: # of variable-range MTRRs we have */
Capwc = 1<<8, /* flag: have write combining? */
Capfix = 1<<10, /* flag: have fixed MTRRs? */
Deftype = 0xff, /* default MTRR type */
Deffixena = 1<<10, /* fixed-range MTRR enable */
Defena = 1<<11, /* MTRR enable */
};
typedef struct Mtrreg Mtrreg;
typedef struct Mtrrop Mtrrop;
struct Mtrreg {
vlong base;
vlong mask;
};
struct Mtrrop {
Mtrreg *reg;
int slot;
};
static char *types[] = {
[Uncacheable] "uc",
[Writecomb] "wc",
[Unknown1] "uk1",
[Unknown2] "uk2",
[Writethru] "wt",
[Writeprot] "wp",
[Writeback] "wb",
nil
};
static Mtrrop *postedop;
static Rendez oprend;
static char *
type2str(int type)
{
if(type < 0 || type >= nelem(types))
return nil;
return types[type];
}
static int
str2type(char *str)
{
char **p;
for(p = types; *p != nil; p++)
if (strcmp(str, *p) == 0)
return p - types;
return -1;
}
static uvlong
physmask(void)
{
ulong regs[4];
static vlong mask = -1;
if (mask != -1)
return mask;
cpuid(Exthighfunc, regs);
if(regs[0] >= Extaddrsz) { /* ax */
cpuid(Extaddrsz, regs);
mask = (1LL << (regs[0] & 0xFF)) - 1; /* ax */
}
mask &= Paerange - 1; /* x86 sanity */
return mask;
}
/* limit physical addresses to 36 bits on the x86 */
static void
sanity(Mtrreg *mtrr)
{
mtrr->base &= Paerange - 1;
mtrr->mask &= Paerange - 1;
}
static int
ispow2(uvlong ul)
{
return (ul & (ul - 1)) == 0;
}
/* true if mtrr is valid */
static int
mtrrdec(Mtrreg *mtrr, uvlong *ptr, uvlong *size, int *type)
{
sanity(mtrr);
*ptr = mtrr->base & ~(BY2PG-1);
*type = mtrr->base & 0xff;
*size = (physmask() ^ (mtrr->mask & ~(BY2PG-1))) + 1;
return (mtrr->mask >> 11) & 1;
}
static void
mtrrenc(Mtrreg *mtrr, uvlong ptr, uvlong size, int type, int ok)
{
mtrr->base = ptr | (type & 0xff);
mtrr->mask = (physmask() & ~(size - 1)) | (ok? 1<<11: 0);
sanity(mtrr);
}
/*
* i is the index of the MTRR, and is multiplied by 2 because
* mask and base offsets are interleaved.
*/
static void
mtrrget(Mtrreg *mtrr, uint i)
{
if (i >= Nmtrr)
error("mtrr index out of range");
rdmsr(MTRRPhysBase0 + 2*i, &mtrr->base);
rdmsr(MTRRPhysMask0 + 2*i, &mtrr->mask);
sanity(mtrr);
}
static void
mtrrput(Mtrreg *mtrr, uint i)
{
if (i >= Nmtrr)
error("mtrr index out of range");
sanity(mtrr);
wrmsr(MTRRPhysBase0 + 2*i, mtrr->base);
wrmsr(MTRRPhysMask0 + 2*i, mtrr->mask);
}
static void
mtrrop(Mtrrop **op)
{
int s;
ulong cr0, cr4;
vlong def;
static long bar1, bar2;
s = splhi(); /* avoid race with mtrrclock */
/*
* wait for all CPUs to sync here, so that the MTRR setup gets
* done at roughly the same time on all processors.
*/
_xinc(&bar1);
while(bar1 < conf.nmach)
microdelay(10);
cr4 = getcr4();
putcr4(cr4 & ~CR4PageGlobalEnable);
cr0 = getcr0();
wbinvd();
putcr0(cr0 | CR0CacheDisable);
wbinvd();
rdmsr(MTRRDefaultType, &def);
wrmsr(MTRRDefaultType, def & ~(vlong)Defena);
mtrrput((*op)->reg, (*op)->slot);
wbinvd();
wrmsr(MTRRDefaultType, def);
putcr0(cr0);
putcr4(cr4);
/*
* wait for all CPUs to sync up again, so that we don't continue
* executing while the MTRRs are still being set up.
*/
_xinc(&bar2);
while(bar2 < conf.nmach)
microdelay(10);
*op = nil;
_xdec(&bar1);
while(bar1 > 0)
microdelay(10);
_xdec(&bar2);
wakeup(&oprend);
splx(s);
}
void
mtrrclock(void) /* called from clock interrupt */
{
if(postedop != nil)
mtrrop(&postedop);
}
/* if there's an operation still pending, keep sleeping */
static int
opavail(void *)
{
return postedop == nil;
}
int
mtrr(uvlong base, uvlong size, char *tstr)
{
int i, vcnt, slot, type, mtype, mok;
vlong def, cap;
uvlong mp, msize;
Mtrreg entry, mtrr;
Mtrrop op;
static int tickreg;
static QLock mtrrlk;
if(!(m->cpuiddx & Mtrr))
error("mtrrs not supported");
if(base & (BY2PG-1) || size & (BY2PG-1) || size == 0)
error("mtrr base or size not 4k aligned or zero size");
if(base + size >= Paerange)
error("mtrr range exceeds 36 bits");
if(!ispow2(size))
error("mtrr size not power of 2");
if(base & (size - 1))
error("mtrr base not naturally aligned");
if((type = str2type(tstr)) == -1)
error("mtrr bad type");
rdmsr(MTRRCap, &cap);
rdmsr(MTRRDefaultType, &def);
switch(type){
default:
error("mtrr unknown type");
break;
case Writecomb:
if(!(cap & Capwc))
error("mtrr type wc (write combining) unsupported");
/* fallthrough */
case Uncacheable:
case Writethru:
case Writeprot:
case Writeback:
break;
}
qlock(&mtrrlk);
slot = -1;
vcnt = cap & Capvcnt;
if(vcnt > Nmtrr)
vcnt = Nmtrr;
for(i = 0; i < vcnt; i++){
mtrrget(&mtrr, i);
mok = mtrrdec(&mtrr, &mp, &msize, &mtype);
/* reuse any entry for addresses above 4GB */
if(!mok || mp == base && msize == size || mp >= (1LL<<32)){
slot = i;
break;
}
}
if(slot == -1)
error("no free mtrr slots");
while(postedop != nil)
sleep(&oprend, opavail, 0);
mtrrenc(&entry, base, size, type, 1);
op.reg = &entry;
op.slot = slot;
postedop = &op;
mtrrop(&postedop);
qunlock(&mtrrlk);
return 0;
}
int
mtrrprint(char *buf, long bufsize)
{
int i, vcnt, type;
long n;
uvlong base, size;
vlong cap, def;
Mtrreg mtrr;
n = 0;
if(!(m->cpuiddx & Mtrr))
return 0;
rdmsr(MTRRCap, &cap);
rdmsr(MTRRDefaultType, &def);
n += snprint(buf+n, bufsize-n, "cache default %s\n",
type2str(def & Deftype));
vcnt = cap & Capvcnt;
if(vcnt > Nmtrr)
vcnt = Nmtrr;
for(i = 0; i < vcnt; i++){
mtrrget(&mtrr, i);
if (mtrrdec(&mtrr, &base, &size, &type))
n += snprint(buf+n, bufsize-n,
"cache 0x%llux %llud %s\n",
base, size, type2str(type));
}
return n;
}