|
|
#include "ki.h"
#define STATIC
#define IDBG 0
#if DBG
#define DBGMSG(a) DbgPrint(a)
#else
#define DBGMSG(a)
#endif
//
// Externals.
//
NTSTATUS KiLoadMTRR ( PVOID Context );
// --- AMD Structure definitions ---
// K6 MTRR hardware register layout.
// Single MTRR control register.
typedef struct _AMDK6_MTRR { ULONG type:2; ULONG mask:15; ULONG base:15; } AMDK6_MTRR, *PAMDK6_MTRR;
// MSR image, contains two control regs.
typedef struct _AMDK6_MTRR_MSR_IMAGE { union { struct { AMDK6_MTRR mtrr0; AMDK6_MTRR mtrr1; } hw; ULONGLONG QuadPart; } u; } AMDK6_MTRR_MSR_IMAGE, *PAMDK6_MTRR_MSR_IMAGE;
// MTRR reg type field values.
#define AMDK6_MTRR_TYPE_DISABLED 0
#define AMDK6_MTRR_TYPE_UC 1
#define AMDK6_MTRR_TYPE_WC 2
#define AMDK6_MTRR_TYPE_MASK 3
// AMD K6 MTRR MSR Index number
#define AMDK6_MTRR_MSR 0xC0000085
//
// Region table entry - used to track all write combined regions.
//
// Set BaseAddress to AMDK6_REGION_UNUSED for unused entries.
//
typedef struct _AMDK6_MTRR_REGION { ULONG BaseAddress; ULONG Size; MEMORY_CACHING_TYPE RegionType; ULONG RegionFlags; } AMDK6_MTRR_REGION, *PAMDK6_MTRR_REGION;
#define MAX_K6_REGIONS 2 // Limit the write combined regions to 2 since that's how many MTRRs we have available.
//
// Value to set base address to for unused indication.
//
#define AMDK6_REGION_UNUSED 0xFFFFFFFF
//
// Flag to indicate that this region was set up by the BIOS.
//
#define AMDK6_REGION_FLAGS_BIOS 0x00000001
//
// Usage count for hardware MTRR registers.
//
#define AMDK6_MAX_MTRR 2
//
// AMD Function Prototypes.
//
VOID KiAmdK6InitializeMTRR ( VOID );
NTSTATUS KiAmdK6RestoreMTRR ( );
NTSTATUS KiAmdK6MtrrSetMemoryType ( ULONG BaseAddress, ULONG Size, MEMORY_CACHING_TYPE Type );
BOOLEAN KiAmdK6AddRegion ( ULONG BaseAddress, ULONG Size, MEMORY_CACHING_TYPE Type, ULONG Flags );
NTSTATUS KiAmdK6MtrrCommitChanges ( VOID );
NTSTATUS KiAmdK6HandleWcRegionRequest ( ULONG BaseAddress, ULONG Size );
VOID KiAmdK6MTRRAddRegionFromHW ( AMDK6_MTRR RegImage );
PAMDK6_MTRR_REGION KiAmdK6FindFreeRegion ( MEMORY_CACHING_TYPE Type );
#pragma alloc_text(INIT,KiAmdK6InitializeMTRR)
#pragma alloc_text(PAGELK,KiAmdK6RestoreMTRR)
#pragma alloc_text(PAGELK,KiAmdK6MtrrSetMemoryType)
#pragma alloc_text(PAGELK,KiAmdK6AddRegion)
#pragma alloc_text(PAGELK,KiAmdK6MtrrCommitChanges)
#pragma alloc_text(PAGELK,KiAmdK6HandleWcRegionRequest)
#pragma alloc_text(PAGELK,KiAmdK6MTRRAddRegionFromHW)
#pragma alloc_text(PAGELK,KiAmdK6FindFreeRegion)
// --- AMD Global Variables ---
extern KSPIN_LOCK KiRangeLock;
// AmdK6Regions - Table to track wc regions.
AMDK6_MTRR_REGION AmdK6Regions[MAX_K6_REGIONS]; ULONG AmdK6RegionCount;
// Usage counter for hardware MTRRs.
ULONG AmdMtrrHwUsageCount;
// Global variable image of MTRR MSR.
AMDK6_MTRR_MSR_IMAGE KiAmdK6Mtrr;
// --- AMD Start of code ---
VOID KiAmdK6InitializeMTRR ( VOID ) { ULONG i; KIRQL OldIrql;
DBGMSG("KiAmdK6InitializeMTRR: Initializing K6 MTRR support\n");
KiAmdK6Mtrr.u.hw.mtrr0.type = AMDK6_MTRR_TYPE_DISABLED; KiAmdK6Mtrr.u.hw.mtrr1.type = AMDK6_MTRR_TYPE_DISABLED; AmdK6RegionCount = MAX_K6_REGIONS; AmdMtrrHwUsageCount = 0;
//
// Set all regions to free.
//
for (i = 0; i < AmdK6RegionCount; i++) { AmdK6Regions[i].BaseAddress = AMDK6_REGION_UNUSED; AmdK6Regions[i].RegionFlags = 0; }
//
// Initialize the spin lock.
//
// N.B. Normally this is done by KiInitializeMTRR but that
// routine is not called in the AMD K6 case.
//
KeInitializeSpinLock (&KiRangeLock);
//
// Read the MTRR registers to see if the BIOS has set them up.
// If so, add entries to the region table and adjust the usage
// count. Serialize the region table.
//
KeAcquireSpinLock (&KiRangeLock, &OldIrql); KiAmdK6Mtrr.u.QuadPart = RDMSR (AMDK6_MTRR_MSR);
//
// Check MTRR0 first.
//
KiAmdK6MTRRAddRegionFromHW(KiAmdK6Mtrr.u.hw.mtrr0);
//
// Now check MTRR1.
//
KiAmdK6MTRRAddRegionFromHW(KiAmdK6Mtrr.u.hw.mtrr1);
//
// Release the locks.
//
KeReleaseSpinLock (&KiRangeLock, OldIrql); }
VOID KiAmdK6MTRRAddRegionFromHW ( AMDK6_MTRR RegImage ) { ULONG BaseAddress, Size, TempMask;
//
// Check to see if this MTRR is enabled.
//
if (RegImage.type != AMDK6_MTRR_TYPE_DISABLED) {
//
// If this is a write combined region then add an entry to
// the region table.
//
if ((RegImage.type & AMDK6_MTRR_TYPE_UC) == 0) {
//
// Create a new resion table entry.
//
BaseAddress = RegImage.base << 17;
//
// Calculate the size base on the mask value.
//
TempMask = RegImage.mask; //
// There should never be 4GB WC region!
//
ASSERT (TempMask != 0);
//
// Start with 128 size and search upward.
//
Size = 0x00020000;
while ((TempMask & 0x00000001) == 0) { TempMask >>= 1; Size <<= 1; }
//
// Add the region to the table.
//
KiAmdK6AddRegion(BaseAddress, Size, MmWriteCombined, AMDK6_REGION_FLAGS_BIOS);
AmdMtrrHwUsageCount++; } } }
NTSTATUS KiAmdK6MtrrSetMemoryType ( ULONG BaseAddress, ULONG Size, MEMORY_CACHING_TYPE Type ) { NTSTATUS Status = STATUS_SUCCESS; KIRQL OldIrql;
switch(Type) { case MmWriteCombined:
//
// H/W needs updating, lock down the code required to effect
// the change.
//
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
//
// Code can not be locked down. Supplying a new range type
// requires that the caller calls at irql < dispatch_level.
//
DBGMSG ("KeAmdK6SetPhysicalCacheTypeRange failed due to calling IRQL == DISPATCH_LEVEL\n"); return STATUS_UNSUCCESSFUL; }
//
// Lock the code.
//
MmLockPagableSectionByHandle(ExPageLockHandle); //
// Serialize the region table.
//
KeAcquireSpinLock (&KiRangeLock, &OldIrql);
Status = KiAmdK6HandleWcRegionRequest(BaseAddress, Size); //
// Release the locks.
//
KeReleaseSpinLock (&KiRangeLock, OldIrql); MmUnlockPagableImageSection(ExPageLockHandle); break; // End of WriteCombined case.
case MmNonCached:
//
// Add an entry to the region table.
//
// Don't need to add these to the region table. Non-cached regions are
// accessed using a non-caching virtual pointer set up in the page tables.
break;
case MmCached:
//
// Redundant. These should be filtered out in
// KeAmdK6SetPhysicalCacheTypeRange();
//
Status = STATUS_NOT_SUPPORTED; break;
default: DBGMSG ("KeAmdK6SetPhysicalCacheTypeRange: no such cache type\n"); Status = STATUS_INVALID_PARAMETER; break; } return Status; }
NTSTATUS KiAmdK6HandleWcRegionRequest ( ULONG BaseAddress, ULONG Size ) { ULONG i; ULONG AdjustedSize, AdjustedEndAddress, AlignmentMask; ULONG CombinedBase, CombinedSize, CombinedAdjustedSize; PAMDK6_MTRR_REGION pRegion; BOOLEAN bCanCombine, bValidRange;
//
// Try and find a region that overlaps or is adjacent to the new one and
// check to see if the combined region would be a legal mapping.
//
for (i = 0; i < AmdK6RegionCount; i++) { pRegion = &AmdK6Regions[i]; if ((pRegion->BaseAddress != AMDK6_REGION_UNUSED) && (pRegion->RegionType == MmWriteCombined)) {
//
// Does the new start address overlap or adjoin an
// existing WC region?
//
if (((pRegion->BaseAddress >= BaseAddress) && (pRegion->BaseAddress <= (BaseAddress + Size))) || ((BaseAddress <= (pRegion->BaseAddress + pRegion->Size)) && (BaseAddress >= pRegion->BaseAddress))) {
//
// Combine the two regions into one.
//
AdjustedEndAddress = BaseAddress + Size;
if (pRegion->BaseAddress < BaseAddress) { CombinedBase = pRegion->BaseAddress; } else { CombinedBase = BaseAddress; }
if ((pRegion->BaseAddress + pRegion->Size) > AdjustedEndAddress) { CombinedSize = (pRegion->BaseAddress + pRegion->Size) - CombinedBase; } else { CombinedSize = AdjustedEndAddress - CombinedBase; }
//
// See if the new region would be a legal mapping.
//
//
// Find the smallest legal size that is equal to the requested range. Scan
// all ranges from 128k - 2G. (Start at 2G and work down).
//
CombinedAdjustedSize = 0x80000000; AlignmentMask = 0x7fffffff; bCanCombine = FALSE; while (CombinedAdjustedSize > 0x00010000) {
//
// Check the size to see if it matches the requested limit.
//
if (CombinedAdjustedSize == CombinedSize) {
//
// This one works.
// Check to see if the base address conforms to the MTRR restrictions.
//
if ((CombinedBase & AlignmentMask) == 0) { bCanCombine = TRUE; }
break;
} else {
//
// Bump it down to the next range size and try again.
//
CombinedAdjustedSize >>= 1; AlignmentMask >>= 1; } }
if (bCanCombine) { //
// If the resized range is OK, record the change in the region
// table and commit the changes to hardware.
//
pRegion->BaseAddress = CombinedBase; pRegion->Size = CombinedAdjustedSize; //
// Reset the BIOS flag since we now "own" this region (if we didn't already).
//
pRegion->RegionFlags &= ~AMDK6_REGION_FLAGS_BIOS;
return KiAmdK6MtrrCommitChanges(); } } } }
// A valid combination could not be found, so try to create a new range for this request.
//
// Find the smallest legal size that is less than or equal to the requested range. Scan
// all ranges from 128k - 2G. (Start at 2G and work down).
//
AdjustedSize = 0x80000000; AlignmentMask = 0x7fffffff; bValidRange = FALSE;
while (AdjustedSize > 0x00010000) {
//
// Check the size to see if it matches the requested limit.
//
if (AdjustedSize == Size) {
//
// This one works.
//
// Check to see if the base address conforms to the MTRR restrictions.
//
if ((BaseAddress & AlignmentMask) == 0) { bValidRange = TRUE; } //
// Stop looking.
//
break;
} else {
//
// Bump it down to the next range size and try again.
//
AdjustedSize >>= 1; AlignmentMask >>= 1; } }
//
// Couldn't find a legal region that fit.
//
if (!bValidRange) { return STATUS_NOT_SUPPORTED; } //
// If we got this far then this is a new WC region.
// Create a new region entry for this request.
//
if (!KiAmdK6AddRegion(BaseAddress, AdjustedSize, MmWriteCombined, 0)) { return STATUS_UNSUCCESSFUL; }
//
// Commit the changes to hardware.
//
return KiAmdK6MtrrCommitChanges(); }
BOOLEAN KiAmdK6AddRegion ( ULONG BaseAddress, ULONG Size, MEMORY_CACHING_TYPE Type, ULONG Flags ) { PAMDK6_MTRR_REGION pRegion;
if ((pRegion = KiAmdK6FindFreeRegion(Type)) == NULL) { return FALSE; } pRegion->BaseAddress = BaseAddress; pRegion->Size = Size; pRegion->RegionType = Type; pRegion->RegionFlags = Flags; return TRUE; }
PAMDK6_MTRR_REGION KiAmdK6FindFreeRegion ( MEMORY_CACHING_TYPE Type ) { ULONG i;
//
// If this is a MmWriteCombined request, limit the number of
// regions to match the actual hardware support.
//
if (Type == MmWriteCombined) { if (AmdMtrrHwUsageCount >= AMDK6_MAX_MTRR) {
//
// Search the table to see if there are any BIOS entries
// we can replace.
//
for (i = 0; i < AmdK6RegionCount; i++) { if (AmdK6Regions[i].RegionFlags & AMDK6_REGION_FLAGS_BIOS) { return &AmdK6Regions[i]; } }
//
// No free HW MTRRs and no reusable entries.
//
return FALSE; } }
//
// Find the next free region in the table.
//
for (i = 0; i < AmdK6RegionCount; i++) { if (AmdK6Regions[i].BaseAddress == AMDK6_REGION_UNUSED) {
if (Type == MmWriteCombined) { AmdMtrrHwUsageCount++; } return &AmdK6Regions[i]; } }
DBGMSG("AmdK6FindFreeRegion: Region Table is Full!\n");
return NULL; }
NTSTATUS KiAmdK6MtrrCommitChanges ( VOID )
/*++
Routine Description:
Commits the values in the table to hardware.
This procedure builds the MTRR images into the KiAmdK6Mtrr variable and calls KiLoadMTRR to actually load the register.
Arguments:
None.
Return Value:
None.
--*/
{ ULONG i, dwWcRangeCount = 0; ULONG RangeTemp, RangeMask;
//
// Reset the MTRR image for both MTRRs disabled.
//
KiAmdK6Mtrr.u.hw.mtrr0.type = AMDK6_MTRR_TYPE_DISABLED; KiAmdK6Mtrr.u.hw.mtrr1.type = AMDK6_MTRR_TYPE_DISABLED;
//
// Find the Write Combining Regions, if any and set up the MTRR register.
//
for (i = 0; i < AmdK6RegionCount; i++) {
//
// Is this a valid region, and is it a write combined type?
//
if ((AmdK6Regions[i].BaseAddress != AMDK6_REGION_UNUSED) && (AmdK6Regions[i].RegionType == MmWriteCombined)) { //
// Calculate the correct mask for this range size. The
// BaseAddress and size were validated and adjusted in
// AmdK6MtrrSetMemoryType().
//
// Start with 128K and scan for all legal range values and
// build the appropriate range mask at the same time.
//
RangeTemp = 0x00020000; RangeMask = 0xfffe0000;
while (RangeTemp != 0) { if (RangeTemp == AmdK6Regions[i].Size) { break; } RangeTemp <<= 1; RangeMask <<= 1; } if (RangeTemp == 0) {
//
// Not a valid range size. This can never happen!!
//
DBGMSG ("AmdK6MtrrCommitChanges: Bad WC range in region table!\n");
return STATUS_NOT_SUPPORTED; }
//
// Add the region to the next available register.
//
if (dwWcRangeCount == 0) {
KiAmdK6Mtrr.u.hw.mtrr0.base = AmdK6Regions[i].BaseAddress >> 17; KiAmdK6Mtrr.u.hw.mtrr0.mask = RangeMask >> 17; KiAmdK6Mtrr.u.hw.mtrr0.type = AMDK6_MTRR_TYPE_WC; dwWcRangeCount++;
} else if (dwWcRangeCount == 1) {
KiAmdK6Mtrr.u.hw.mtrr1.base = AmdK6Regions[i].BaseAddress >> 17; KiAmdK6Mtrr.u.hw.mtrr1.mask = RangeMask >> 17; KiAmdK6Mtrr.u.hw.mtrr1.type = AMDK6_MTRR_TYPE_WC; dwWcRangeCount++;
} else {
//
// Should never happen! This should have been caught in
// the calling routine.
//
DBGMSG ("AmdK6MtrrCommitChanges: Not enough MTRR registers to satisfy region table!\n");
return STATUS_NOT_SUPPORTED; } } }
//
// Commit the changes to hardware.
//
KiLoadMTRR(NULL);
return STATUS_SUCCESS; }
VOID KiAmdK6MtrrWRMSR ( VOID )
/*++
Routine Description:
Write the AMD K6 MTRRs.
Note: Access to KiAmdK6Mtrr has been synchronized around this call.
Arguments:
None.
Return Value:
None.
--*/
{ //
// Write the MTRRs
//
WRMSR (AMDK6_MTRR_MSR, KiAmdK6Mtrr.u.QuadPart); }
|