#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/memrange.h>
#include <machine/cpufunc.h>
#include <machine/intr.h>
#include <machine/specialreg.h>
char *mem_owner_bios = "BIOS";
#define MR_FIXMTRR (1<<0)
#define mrwithin(mr, a) \
(((a) >= (mr)->mr_base) && ((a) < ((mr)->mr_base + (mr)->mr_len)))
#define mroverlap(mra, mrb) \
(mrwithin(mra, mrb->mr_base) || mrwithin(mrb, mra->mr_base))
#define mrvalid(base, len) \
((!(base & ((1 << 12) - 1))) && \
((len) >= (1 << 12)) && \
powerof2((len)) && \
!((base) & ((len) - 1)))
#define mrcopyflags(curr, new) (((curr) & ~MDF_ATTRMASK) | \
((new) & MDF_ATTRMASK))
#define FIXTOP ((MTRR_N64K * 0x10000) + (MTRR_N16K * 0x4000) + \
(MTRR_N4K * 0x1000))
void mrinit(struct mem_range_softc *sc);
int mrset(struct mem_range_softc *sc,
struct mem_range_desc *mrd, int *arg);
void mrinit_cpu(struct mem_range_softc *sc);
void mrreload_cpu(struct mem_range_softc *sc);
struct mem_range_ops mrops = {
mrinit,
mrset,
mrinit_cpu,
mrreload_cpu
};
u_int64_t mtrrcap, mtrrdef;
u_int64_t mtrrmask = 0x0000000ffffff000ULL;
struct mem_range_desc *mem_range_match(struct mem_range_softc *sc,
struct mem_range_desc *mrd);
void mrfetch(struct mem_range_softc *sc);
int mtrrtype(u_int64_t flags);
int mrt2mtrr(u_int64_t flags);
int mtrr2mrt(int val);
int mtrrconflict(u_int64_t flag1, u_int64_t flag2);
void mrstore(struct mem_range_softc *sc);
void mrstoreone(struct mem_range_softc *sc);
struct mem_range_desc *mtrrfixsearch(struct mem_range_softc *sc,
u_int64_t addr);
int mrsetlow(struct mem_range_softc *sc,
struct mem_range_desc *mrd, int *arg);
int mrsetvariable(struct mem_range_softc *sc,
struct mem_range_desc *mrd, int *arg);
int mtrrtomrt[] = {
MDF_UNCACHEABLE,
MDF_WRITECOMBINE,
MDF_UNKNOWN,
MDF_UNKNOWN,
MDF_WRITETHROUGH,
MDF_WRITEPROTECT,
MDF_WRITEBACK
};
int
mtrr2mrt(int val)
{
if (val < 0 || val >= nitems(mtrrtomrt))
return MDF_UNKNOWN;
return mtrrtomrt[val];
}
int
mtrrconflict(u_int64_t flag1, u_int64_t flag2)
{
flag1 &= MDF_ATTRMASK;
flag2 &= MDF_ATTRMASK;
if (flag1 == flag2 ||
(flag1 == MDF_WRITEBACK && flag2 == MDF_UNCACHEABLE) ||
(flag2 == MDF_WRITEBACK && flag1 == MDF_UNCACHEABLE))
return 0;
return 1;
}
struct mem_range_desc *
mem_range_match(struct mem_range_softc *sc, struct mem_range_desc *mrd)
{
struct mem_range_desc *cand;
int i;
for (i = 0, cand = sc->mr_desc; i < sc->mr_ndesc; i++, cand++)
if ((cand->mr_base == mrd->mr_base) &&
(cand->mr_len == mrd->mr_len))
return(cand);
return(NULL);
}
void
mrfetch(struct mem_range_softc *sc)
{
struct mem_range_desc *mrd;
u_int64_t msrv;
int i, j, msr, mrt;
mrd = sc->mr_desc;
KASSERT(CPU_IS_PRIMARY(curcpu()));
if (sc->mr_cap & MR_FIXMTRR) {
msr = MSR_MTRRfix64K_00000;
for (i = 0; i < (MTRR_N64K / 8); i++, msr++) {
msrv = rdmsr(msr);
for (j = 0; j < 8; j++, mrd++) {
mrt = mtrr2mrt(msrv & 0xff);
if (mrt == MDF_UNKNOWN)
mrt = MDF_UNCACHEABLE;
mrd->mr_flags = (mrd->mr_flags & ~MDF_ATTRMASK) |
mrt | MDF_ACTIVE;
if (mrd->mr_owner[0] == 0)
strlcpy(mrd->mr_owner, mem_owner_bios,
sizeof(mrd->mr_owner));
msrv = msrv >> 8;
}
}
msr = MSR_MTRRfix16K_80000;
for (i = 0; i < (MTRR_N16K / 8); i++, msr++) {
msrv = rdmsr(msr);
for (j = 0; j < 8; j++, mrd++) {
mrt = mtrr2mrt(msrv & 0xff);
if (mrt == MDF_UNKNOWN)
mrt = MDF_UNCACHEABLE;
mrd->mr_flags = (mrd->mr_flags & ~MDF_ATTRMASK) |
mrt | MDF_ACTIVE;
if (mrd->mr_owner[0] == 0)
strlcpy(mrd->mr_owner, mem_owner_bios,
sizeof(mrd->mr_owner));
msrv = msrv >> 8;
}
}
msr = MSR_MTRRfix4K_C0000;
for (i = 0; i < (MTRR_N4K / 8); i++, msr++) {
msrv = rdmsr(msr);
for (j = 0; j < 8; j++, mrd++) {
mrt = mtrr2mrt(msrv & 0xff);
if (mrt == MDF_UNKNOWN)
mrt = MDF_UNCACHEABLE;
mrd->mr_flags = (mrd->mr_flags & ~MDF_ATTRMASK) |
mrt | MDF_ACTIVE;
if (mrd->mr_owner[0] == 0)
strlcpy(mrd->mr_owner, mem_owner_bios,
sizeof(mrd->mr_owner));
msrv = msrv >> 8;
}
}
}
msr = MSR_MTRRvarBase;
for (; (mrd - sc->mr_desc) < sc->mr_ndesc; msr += 2, mrd++) {
msrv = rdmsr(msr);
mrt = mtrr2mrt(msrv & 0xff);
if (mrt == MDF_UNKNOWN)
mrt = MDF_UNCACHEABLE;
mrd->mr_flags = (mrd->mr_flags & ~MDF_ATTRMASK) | mrt;
mrd->mr_base = msrv & mtrrmask;
msrv = rdmsr(msr + 1);
mrd->mr_flags = (msrv & 0x800) ?
(mrd->mr_flags | MDF_ACTIVE) :
(mrd->mr_flags & ~MDF_ACTIVE);
mrd->mr_len = (~(msrv & mtrrmask) & mtrrmask) + 0x1000;
if (!mrvalid(mrd->mr_base, mrd->mr_len))
mrd->mr_flags |= MDF_BOGUS;
if ((mrd->mr_flags & MDF_ACTIVE) && (mrd->mr_owner[0] == 0))
strlcpy(mrd->mr_owner, mem_owner_bios,
sizeof(mrd->mr_owner));
}
}
int
mtrrtype(u_int64_t flags)
{
int i;
flags &= MDF_ATTRMASK;
for (i = 0; i < nitems(mtrrtomrt); i++) {
if (mtrrtomrt[i] == MDF_UNKNOWN)
continue;
if (flags == mtrrtomrt[i])
return(i);
}
return MDF_UNCACHEABLE;
}
int
mrt2mtrr(u_int64_t flags)
{
int val;
val = mtrrtype(flags);
return val & 0xff;
}
void
mrstore(struct mem_range_softc *sc)
{
u_long s;
s = intr_disable();
#ifdef MULTIPROCESSOR
x86_broadcast_ipi(X86_IPI_MTRR);
#endif
mrstoreone(sc);
intr_restore(s);
}
void
mrstoreone(struct mem_range_softc *sc)
{
struct mem_range_desc *mrd;
u_int64_t msrv;
int i, j, msr;
u_int cr4save;
mrd = sc->mr_desc;
cr4save = rcr4();
if (cr4save & CR4_PGE)
lcr4(cr4save & ~CR4_PGE);
wbinvd();
lcr0((rcr0() & ~CR0_NW) | CR0_CD);
wrmsr(MSR_MTRRdefType, rdmsr(MSR_MTRRdefType) & ~0x800);
if (sc->mr_cap & MR_FIXMTRR) {
msr = MSR_MTRRfix64K_00000;
for (i = 0; i < (MTRR_N64K / 8); i++, msr++) {
msrv = 0;
for (j = 7; j >= 0; j--) {
msrv = msrv << 8;
msrv |= mrt2mtrr((mrd + j)->mr_flags);
}
wrmsr(msr, msrv);
mrd += 8;
}
msr = MSR_MTRRfix16K_80000;
for (i = 0, msrv = 0; i < (MTRR_N16K / 8); i++, msr++) {
for (j = 7; j >= 0; j--) {
msrv = msrv << 8;
msrv |= mrt2mtrr((mrd + j)->mr_flags);
}
wrmsr(msr, msrv);
mrd += 8;
}
msr = MSR_MTRRfix4K_C0000;
for (i = 0, msrv = 0; i < (MTRR_N4K / 8); i++, msr++) {
for (j = 7; j >= 0; j--) {
msrv = msrv << 8;
msrv |= mrt2mtrr((mrd + j)->mr_flags);
}
wrmsr(msr, msrv);
mrd += 8;
}
}
msr = MSR_MTRRvarBase;
for (; (mrd - sc->mr_desc) < sc->mr_ndesc; msr += 2, mrd++) {
if (mrd->mr_flags & MDF_ACTIVE) {
msrv = mrd->mr_base & mtrrmask;
msrv |= mrt2mtrr(mrd->mr_flags);
} else
msrv = 0;
wrmsr(msr, msrv);
if (mrd->mr_flags & MDF_ACTIVE) {
msrv = 0x800 | (~(mrd->mr_len - 1) & mtrrmask);
} else
msrv = 0;
wrmsr(msr + 1, msrv);
}
wrmsr(MSR_MTRRdefType, mtrrdef | 0x800);
lcr0(rcr0() & ~(CR0_CD | CR0_NW));
lcr4(cr4save);
}
struct mem_range_desc *
mtrrfixsearch(struct mem_range_softc *sc, u_int64_t addr)
{
struct mem_range_desc *mrd;
int i;
for (i = 0, mrd = sc->mr_desc; i < (MTRR_N64K + MTRR_N16K + MTRR_N4K); i++, mrd++)
if ((addr >= mrd->mr_base) && (addr < (mrd->mr_base + mrd->mr_len)))
return(mrd);
return(NULL);
}
int
mrsetlow(struct mem_range_softc *sc, struct mem_range_desc *mrd, int *arg)
{
struct mem_range_desc *first_md, *last_md, *curr_md;
if (((first_md = mtrrfixsearch(sc, mrd->mr_base)) == NULL) ||
((last_md = mtrrfixsearch(sc, mrd->mr_base + mrd->mr_len - 1)) == NULL))
return(EINVAL);
if (!(mrd->mr_flags & MDF_FORCE))
for (curr_md = first_md; curr_md <= last_md; curr_md++) {
if ((curr_md->mr_flags & MDF_ATTRMASK) == MDF_UNKNOWN)
return (EACCES);
}
for (curr_md = first_md; curr_md <= last_md; curr_md++) {
curr_md->mr_flags = mrcopyflags(curr_md->mr_flags & ~MDF_FIRMWARE, mrd->mr_flags);
memcpy(curr_md->mr_owner, mrd->mr_owner, sizeof(mrd->mr_owner));
}
return(0);
}
int
mrsetvariable(struct mem_range_softc *sc, struct mem_range_desc *mrd, int *arg)
{
struct mem_range_desc *curr_md, *free_md;
int i;
i = (sc->mr_cap & MR_FIXMTRR) ? MTRR_N64K + MTRR_N16K + MTRR_N4K : 0;
curr_md = sc->mr_desc + i;
free_md = NULL;
for (; i < sc->mr_ndesc; i++, curr_md++) {
if (curr_md->mr_flags & MDF_ACTIVE) {
if ((curr_md->mr_base == mrd->mr_base) &&
(curr_md->mr_len == mrd->mr_len)) {
if (!(mrd->mr_flags & MDF_FORCE) &&
((curr_md->mr_flags & MDF_ATTRMASK)
== MDF_UNKNOWN))
return (EACCES);
free_md = curr_md;
break;
}
if (mroverlap(curr_md, mrd)) {
if (mtrrconflict(curr_md->mr_flags,
mrd->mr_flags))
return(EINVAL);
}
} else if (free_md == NULL) {
free_md = curr_md;
}
}
if (free_md == NULL)
return(ENOSPC);
free_md->mr_base = mrd->mr_base;
free_md->mr_len = mrd->mr_len;
free_md->mr_flags = mrcopyflags(MDF_ACTIVE, mrd->mr_flags);
memcpy(free_md->mr_owner, mrd->mr_owner, sizeof(mrd->mr_owner));
return(0);
}
int
mrset(struct mem_range_softc *sc, struct mem_range_desc *mrd, int *arg)
{
struct mem_range_desc *targ;
int error = 0;
switch(*arg) {
case MEMRANGE_SET_UPDATE:
if (!mrvalid(mrd->mr_base, mrd->mr_len) ||
mtrrtype(mrd->mr_flags) == -1)
return(EINVAL);
if ((sc->mr_cap & MR_FIXMTRR) &&
((mrd->mr_base + mrd->mr_len) <= FIXTOP)) {
if ((error = mrsetlow(sc, mrd, arg)) != 0)
return(error);
} else {
if ((error = mrsetvariable(sc, mrd, arg)) != 0)
return(error);
}
break;
case MEMRANGE_SET_REMOVE:
if ((targ = mem_range_match(sc, mrd)) == NULL)
return(ENOENT);
if (targ->mr_flags & MDF_FIXACTIVE)
return(EPERM);
targ->mr_flags &= ~MDF_ACTIVE;
targ->mr_owner[0] = 0;
break;
default:
return(EOPNOTSUPP);
}
mrstore(sc);
return(0);
}
void
mrinit(struct mem_range_softc *sc)
{
struct mem_range_desc *mrd;
uint32_t regs[4];
int nmdesc = 0;
int i;
mtrrcap = rdmsr(MSR_MTRRcap);
mtrrdef = rdmsr(MSR_MTRRdefType);
if (!(mtrrdef & MTRRdefType_ENABLE)) {
printf("mtrr: CPU supports MTRRs but not enabled by BIOS\n");
return;
}
nmdesc = mtrrcap & 0xff;
printf("mtrr: Pentium Pro MTRR support, %d var ranges", nmdesc);
if ((mtrrcap & MTRRcap_FIXED) &&
(mtrrdef & MTRRdefType_FIXED_ENABLE)) {
sc->mr_cap = MR_FIXMTRR;
nmdesc += MTRR_N64K + MTRR_N16K + MTRR_N4K;
printf(", %d fixed ranges", MTRR_N64K + MTRR_N16K + MTRR_N4K);
}
printf("\n");
sc->mr_desc = mallocarray(nmdesc, sizeof(struct mem_range_desc),
M_MEMDESC, M_WAITOK|M_ZERO);
sc->mr_ndesc = nmdesc;
mrd = sc->mr_desc;
if (sc->mr_cap & MR_FIXMTRR) {
for (i = 0; i < MTRR_N64K; i++, mrd++) {
mrd->mr_base = i * 0x10000;
mrd->mr_len = 0x10000;
mrd->mr_flags = MDF_FIXBASE | MDF_FIXLEN | MDF_FIXACTIVE;
}
for (i = 0; i < MTRR_N16K; i++, mrd++) {
mrd->mr_base = i * 0x4000 + 0x80000;
mrd->mr_len = 0x4000;
mrd->mr_flags = MDF_FIXBASE | MDF_FIXLEN | MDF_FIXACTIVE;
}
for (i = 0; i < MTRR_N4K; i++, mrd++) {
mrd->mr_base = i * 0x1000 + 0xc0000;
mrd->mr_len = 0x1000;
mrd->mr_flags = MDF_FIXBASE | MDF_FIXLEN | MDF_FIXACTIVE;
}
}
if (curcpu()->ci_pnfeatset >= 0x80000008) {
CPUID(0x80000008, regs[0], regs[1], regs[2], regs[3]);
if (regs[0] & 0xff) {
mtrrmask = (1ULL << (regs[0] & 0xff)) - 1;
mtrrmask &= ~0x0000000000000fffULL;
}
}
mrfetch(sc);
mrd = sc->mr_desc;
for (i = 0; i < sc->mr_ndesc; i++, mrd++) {
if (mrd->mr_flags & MDF_ACTIVE)
mrd->mr_flags |= MDF_FIRMWARE;
}
}
void
mrinit_cpu(struct mem_range_softc *sc)
{
mrstoreone(sc);
}
void
mrreload_cpu(struct mem_range_softc *sc)
{
u_long s;
s = intr_disable();
mrstoreone(sc);
intr_restore(s);
}