diff options
author | Matthieu Herrb <matthieu@cvs.openbsd.org> | 2002-10-14 21:01:02 +0000 |
---|---|---|
committer | Matthieu Herrb <matthieu@cvs.openbsd.org> | 2002-10-14 21:01:02 +0000 |
commit | 99fd6307b3a15281afcbf8ba8d4d4c32a0b0df90 (patch) | |
tree | d67a170ef234f9437ba4657e9d721b54699cbdb5 /sys/arch/i386 | |
parent | a68c2438ac22f2fbb5fb53e11f16a45095a38b78 (diff) |
Fix from FreeBSD for atlhon problems with mtrr and XFree86. Ok deraadt@
FreeBSD commit messages say:
Some BIOSs are using MTRR values that are only documented under NDA
to control the mapping of things like the ACPI and APM into memory.
The problem is that starting X changes these values, so if something
was using the bits of BIOS mapped into memory (say ACPI or APM),
then next time they access this memory the machine would hang.
This patch refuse to change MTRR values it doesn't understand,
unless a new "force" option is given. This means X doesn't change
them by accident but someone can override that if they really want
to.
PR: 28418
Tested by: Christopher Masto <chris at netmonger dot net>,
David Bushong <david at bushong dot net>,
Santos <casd at myrealbox dot com>
Make the MTRR code a bit more defensive - this should help people
trying to run X on some Athlon systems where the BIOS does odd things
(mines an ASUS A7A266, but it seems to also help on other systems).
Here's a description of the problem and my fix:
The problem with the old MTRR code is that it only expects
to find documented values in the bytes of MTRR registers.
To convert the MTRR byte into a FreeBSD "Memory Range Type"
(mrt) it uses the byte value and looks it up in an array.
If the value is not in range then the mrt value ends up
containing random junk.
This isn't an immediate problem. The mrt value is only used
later when rewriting the MTRR registers. When we finally
go to write a value back again, the function i686_mtrrtype()
searches for the junk value and returns -1 when it fails
to find it. This is converted to a byte (0xff) and written
back to the register, causing a GPF as 0xff is an illegal
value for a MTRR byte.
To work around this problem I've added a new mrt flag
MDF_UNKNOWN. We set this when we read a MTRR byte which
we do not understand. If we try to convert a MDF_UNKNOWN
back into a MTRR value, then the new function, i686_mrt2mtrr,
just returns the old value of the MTRR byte. This leaves
the memory range type unchanged.
I have seen one side effect of the fix, which is that ACPI calls
after X has been run seem to hang my machine. As running X would
previously panic the machine, this is still an improvement ;-)
PR: 28418, 25958
Tested by: jkh, Christopher Masto <chris at netmonger dot net>
Diffstat (limited to 'sys/arch/i386')
-rw-r--r-- | sys/arch/i386/i386/i686_mem.c | 105 |
1 files changed, 73 insertions, 32 deletions
diff --git a/sys/arch/i386/i386/i686_mem.c b/sys/arch/i386/i386/i686_mem.c index be862ff0e90..d17d8dcbb2c 100644 --- a/sys/arch/i386/i386/i686_mem.c +++ b/sys/arch/i386/i386/i686_mem.c @@ -1,4 +1,4 @@ -/* $OpenBSD: i686_mem.c,v 1.4 2002/03/14 01:26:32 millert Exp $ */ +/* $OpenBSD: i686_mem.c,v 1.5 2002/10/14 21:01:01 matthieu Exp $ */ /*- * Copyright (c) 1999 Michael Smith <msmith@freebsd.org> * All rights reserved. @@ -79,6 +79,9 @@ struct mem_range_desc *mem_range_match(struct mem_range_softc *sc, struct mem_range_desc *mrd); void i686_mrfetch(struct mem_range_softc *sc); int i686_mtrrtype(int flags); +int i686_mrt2mtrr(int flags, int oldval); +int i686_mtrr2mrt(int val); +int i686_mtrrconflict(int flag1, int flag2); void i686_mrstore(struct mem_range_softc *sc); void i686_mrstoreone(void *arg); struct mem_range_desc *i686_mtrrfixsearch(struct mem_range_softc *sc, @@ -94,29 +97,38 @@ int i686_mrsetvariable(struct mem_range_softc *sc, int i686_mtrrtomrt[] = { MDF_UNCACHEABLE, MDF_WRITECOMBINE, - 0, - 0, + MDF_UNKNOWN, + MDF_UNKNOWN, MDF_WRITETHROUGH, MDF_WRITEPROTECT, MDF_WRITEBACK }; -/* - * i686 MTRR conflict matrix for overlapping ranges - * - * Specifically, this matrix allows writeback and uncached ranges - * to overlap (the overlapped region is uncached). The array index - * is the translated i686 code for the flags (because they map well). +#define MTRRTOMRTLEN (sizeof(i686_mtrrtomrt) / sizeof(i686_mtrrtomrt[0])) + +int +i686_mtrr2mrt(int val) +{ + if (val < 0 || val >= MTRRTOMRTLEN) + return MDF_UNKNOWN; + return i686_mtrrtomrt[val]; +} + +/* + * i686 MTRR conflicts. Writeback and uncachable may overlap. */ -int i686_mtrrconflict[] = { - MDF_WRITECOMBINE | MDF_WRITETHROUGH | MDF_WRITEPROTECT, - MDF_ATTRMASK, - 0, - 0, - MDF_ATTRMASK, - MDF_ATTRMASK, - MDF_WRITECOMBINE | MDF_WRITETHROUGH | MDF_WRITEPROTECT -}; +int +i686_mtrrconflict(int flag1, int 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; +} /* * Look for an exactly-matching range. @@ -158,7 +170,7 @@ i686_mrfetch(sc) msrv = rdmsr(msr); for (j = 0; j < 8; j++, mrd++) { mrd->mr_flags = (mrd->mr_flags & ~MDF_ATTRMASK) | - i686_mtrrtomrt[msrv & 0xff] | + i686_mtrr2mrt(msrv & 0xff) | MDF_ACTIVE; if (mrd->mr_owner[0] == 0) strcpy(mrd->mr_owner, mem_owner_bios); @@ -170,7 +182,7 @@ i686_mrfetch(sc) msrv = rdmsr(msr); for (j = 0; j < 8; j++, mrd++) { mrd->mr_flags = (mrd->mr_flags & ~MDF_ATTRMASK) | - i686_mtrrtomrt[msrv & 0xff] | + i686_mtrr2mrt(msrv & 0xff) | MDF_ACTIVE; if (mrd->mr_owner[0] == 0) strcpy(mrd->mr_owner, mem_owner_bios); @@ -182,7 +194,7 @@ i686_mrfetch(sc) msrv = rdmsr(msr); for (j = 0; j < 8; j++, mrd++) { mrd->mr_flags = (mrd->mr_flags & ~MDF_ATTRMASK) | - i686_mtrrtomrt[msrv & 0xff] | + i686_mtrr2mrt(msrv & 0xff) | MDF_ACTIVE; if (mrd->mr_owner[0] == 0) strcpy(mrd->mr_owner, mem_owner_bios); @@ -196,7 +208,7 @@ i686_mrfetch(sc) for (; (mrd - sc->mr_desc) < sc->mr_ndesc; msr += 2, mrd++) { msrv = rdmsr(msr); mrd->mr_flags = (mrd->mr_flags & ~MDF_ATTRMASK) | - i686_mtrrtomrt[msrv & 0xff]; + i686_mtrr2mrt(msrv & 0xff); mrd->mr_base = msrv & 0x0000000ffffff000LL; msrv = rdmsr(msr + 1); mrd->mr_flags = (msrv & 0x800) ? @@ -223,8 +235,8 @@ i686_mtrrtype(flags) flags &= MDF_ATTRMASK; - for (i = 0; i < (sizeof(i686_mtrrtomrt) / sizeof(i686_mtrrtomrt[0])); i++) { - if (i686_mtrrtomrt[i] == 0) + for (i = 0; i < MTRRTOMRTLEN; i++) { + if (i686_mtrrtomrt[i] == MDF_UNKNOWN) continue; if (flags == i686_mtrrtomrt[i]) return(i); @@ -232,6 +244,16 @@ i686_mtrrtype(flags) return(-1); } +int +i686_mrt2mtrr(int flags, int oldval) +{ + int val; + + if ((val = i686_mtrrtype(flags)) == -1) + return oldval & 0xff; + return val & 0xff; +} + /* * Update running CPU(s) MTRRs to match the ranges in the descriptor * list. @@ -258,7 +280,7 @@ i686_mrstoreone(arg) { struct mem_range_softc *sc = (struct mem_range_softc *)arg; struct mem_range_desc *mrd; - u_int64_t msrv; + u_int64_t omsrv, msrv; int i, j, msr; u_int cr4save; @@ -276,9 +298,11 @@ i686_mrstoreone(arg) msr = MSR_MTRR64kBase; for (i = 0; i < (MTRR_N64K / 8); i++, msr++) { msrv = 0; + omsrv = rdmsr(msr); for (j = 7; j >= 0; j--) { msrv = msrv << 8; - msrv |= (i686_mtrrtype((mrd + j)->mr_flags) & 0xff); + msrv |= i686_mrt2mtrr((mrd + j)->mr_flags, + omsrv >> (j*8)); } wrmsr(msr, msrv); mrd += 8; @@ -286,9 +310,11 @@ i686_mrstoreone(arg) msr = MSR_MTRR16kBase; for (i = 0; i < (MTRR_N16K / 8); i++, msr++) { msrv = 0; + omsrv = rdmsr(msr); for (j = 7; j >= 0; j--) { msrv = msrv << 8; - msrv |= (i686_mtrrtype((mrd + j)->mr_flags) & 0xff); + msrv |= i686_mrt2mtrr((mrd + j)->mr_flags, + omsrv >> (j*8)); } wrmsr(msr, msrv); mrd += 8; @@ -296,9 +322,11 @@ i686_mrstoreone(arg) msr = MSR_MTRR4kBase; for (i = 0; i < (MTRR_N4K / 8); i++, msr++) { msrv = 0; + omsrv = rdmsr(msr); for (j = 7; j >= 0; j--) { msrv = msrv << 8; - msrv |= (i686_mtrrtype((mrd + j)->mr_flags) & 0xff); + msrv |= i686_mrt2mtrr((mrd + j)->mr_flags, + omsrv >> (j*8)); } wrmsr(msr, msrv); mrd += 8; @@ -309,9 +337,10 @@ i686_mrstoreone(arg) msr = MSR_MTRRVarBase; for (; (mrd - sc->mr_desc) < sc->mr_ndesc; msr += 2, mrd++) { /* base/type register */ + omsrv = rdmsr(msr); if (mrd->mr_flags & MDF_ACTIVE) { msrv = mrd->mr_base & 0x0000000ffffff000LL; - msrv |= (i686_mtrrtype(mrd->mr_flags) & 0xff); + msrv |= i686_mrt2mtrr(mrd->mr_flags, omsrv); } else { msrv = 0; } @@ -371,6 +400,13 @@ i686_mrsetlow(sc, mrd, arg) ((last_md = i686_mtrrfixsearch(sc, mrd->mr_base + mrd->mr_len - 1)) == NULL)) return(EINVAL); + /* check we aren't doing something risky */ + 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); + } + /* set flags, clear set-by-firmware flag */ 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); @@ -413,6 +449,11 @@ i686_mrsetvariable(sc, mrd, arg) /* whoops, owned by someone */ if (curr_md->mr_flags & MDF_BUSY) return(EBUSY); + /* check we aren't doing something risky */ + if (!(mrd->mr_flags & MDF_FORCE) && + ((curr_md->mr_flags & MDF_ATTRMASK) + == MDF_UNKNOWN)) + return (EACCES); /* Ok, just hijack this entry */ free_md = curr_md; break; @@ -420,8 +461,8 @@ i686_mrsetvariable(sc, mrd, arg) /* non-exact overlap ? */ if (mroverlap(curr_md, mrd)) { /* between conflicting region types? */ - if ((i686_mtrrconflict[i686_mtrrtype(curr_md->mr_flags)] & mrd->mr_flags) || - (i686_mtrrconflict[i686_mtrrtype(mrd->mr_flags)] & curr_md->mr_flags)) + if (i686_mtrrconflict(curr_md->mr_flags, + mrd->mr_flags)) return(EINVAL); } } else if (free_md == NULL) { @@ -457,7 +498,7 @@ i686_mrset(sc, mrd, arg) case MEMRANGE_SET_UPDATE: /* make sure that what's being asked for is even possible at all */ if (!mrvalid(mrd->mr_base, mrd->mr_len) || - (i686_mtrrtype(mrd->mr_flags & MDF_ATTRMASK) == -1)) + i686_mtrrtype(mrd->mr_flags) == -1) return(EINVAL); #define FIXTOP ((MTRR_N64K * 0x10000) + (MTRR_N16K * 0x4000) + (MTRR_N4K * 0x1000)) |