/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    pat.c

Abstract:

    This module implements interfaces that set the Page Attribute
    Table. These entry points only exist on i386 machines.

--*/

#include "ki.h"
#include "pat.h"

//
// Use lockstep mechanism from mtrr code.
//

#include "mtrr.h"

#if DBG
#define DBGMSG(a)   DbgPrint(a)
#else
#define DBGMSG(a)
#endif

//
// Structure used for PAT initialization
//

typedef struct _NEW_PAT {

    PAT                 Attributes;

    //
    // IPI context to coordinate concurrent PAT update
    //

    PROCESSOR_LOCKSTEP  Synchronize;
} NEW_PAT, *PNEW_PAT;

// Prototypes

VOID
KeRestorePAT (
    VOID
    );

VOID
KiInitializePAT (
    VOID
    );

VOID
KiLoadPAT (
    IN PNEW_PAT Context
    );

VOID
KiLoadPATTarget (
    IN PKIPI_CONTEXT    SignalDone,
    IN PVOID            Context,
    IN PVOID            Parameter2,
    IN PVOID            Parameter3
    );

#if DBG
VOID
KiDumpPAT (
    PUCHAR      DebugString,
    PAT         Attributes
    );
#endif

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGELK,KiInitializePAT)
#pragma alloc_text(PAGELK,KiLoadPAT)
#pragma alloc_text(PAGELK,KiLoadPATTarget)
#endif

VOID
KeRestorePAT (
    VOID
    )
/*++
Routine Description:

    Reinitialize the Page Attribute Table (PAT) on all processors.

    N.B. The caller must have the PAGELK code locked

  Arguments:

    None.

Return Value:

    None.
--*/
{
    if (KeFeatureBits & KF_PAT) {
        KiInitializePAT();
    }
}

VOID
KiInitializePAT (
    VOID
    )
/*++

Routine Description:

    Initialize the Page Attribute Table (PAT) on all processors. PAT
    is setup to provide WB, WC, STRONG_UC and WEAK_UC as the memory
    types such that mm macros for enabling/disabling/querying caching
    (MI_DISABLE_CACHING, MI_ENABLE_CACHING and MI_IS_CACHING_ENABLED)
    are unaffected.

    PAT_Entry   PAT Index   PCD PWT     Memory Type
    0            0           0   0       WB
    1            0           0   1       WC *
    2            0           1   0       WEAK_UC
    3            0           1   1       STRONG_UC
    4            1           0   0       WB
    5            1           0   1       WC *
    6            1           1   0       WEAK_UC
    7            1           1   1       STRONG_UC

    N.B. The caller must have the PAGELK code locked and ensure that the
    PAT feature is supported.

  Arguments:

    None.

Return Value:

    None.

--*/
{
    PAT         PatAttributes;
    KIRQL       OldIrql;
    PKPRCB      Prcb;
    NEW_PAT     NewPAT;
#if !defined(NT_UP)
    KIRQL       NewIrql;
    KAFFINITY   TargetProcessors;
#endif

    ASSERT ((KeFeatureBits & KF_PAT) != 0);

    //
    // Initialize the PAT
    //

    PatAttributes.hw.Pat[0] = PAT_TYPE_WB;
    PatAttributes.hw.Pat[1] = PAT_TYPE_USWC;
    PatAttributes.hw.Pat[2] = PAT_TYPE_WEAK_UC;
    PatAttributes.hw.Pat[3] = PAT_TYPE_STRONG_UC;
    PatAttributes.hw.Pat[4] = PAT_TYPE_WB;
    PatAttributes.hw.Pat[5] = PAT_TYPE_USWC;
    PatAttributes.hw.Pat[6] = PAT_TYPE_WEAK_UC;
    PatAttributes.hw.Pat[7] = PAT_TYPE_STRONG_UC;

    //
    // Synchronize with other IPI functions which may stall
    //
    KeAcquireSpinLock (&KiReverseStallIpiLock, &OldIrql);

    Prcb = KeGetCurrentPrcb();

    NewPAT.Attributes = PatAttributes;
    NewPAT.Synchronize.TargetCount = 0;
    NewPAT.Synchronize.TargetPhase = &Prcb->ReverseStall;
    NewPAT.Synchronize.Processor = Prcb->Number;


#if !defined(NT_UP)

    //
    // Collect all the (other) processors
    //

    TargetProcessors = KeActiveProcessors & ~Prcb->SetMember;
    if (TargetProcessors != 0) {

        KiIpiSendSynchronousPacket (
            Prcb,
            TargetProcessors,
            KiLoadPATTarget,
            (PVOID) (&NewPAT),
            NULL,
            NULL
            );

        //
        // Wait for all processors to be collected
        //

        KiIpiStallOnPacketTargets(TargetProcessors);

        //
        // All processors are now waiting.  Raise to high level to
        // ensure this processor doesn't enter the debugger due to
        // some interrupt service routine.
        //

        KeRaiseIrql (HIGH_LEVEL, &NewIrql);

        //
        // There's no reason for any debug events now, so signal
        // the other processors that they can all begin the PAT update
        //

        Prcb->ReverseStall += 1;
    }

#endif

    //
    // Update PAT
    //

    KiLoadPAT(&NewPAT);

    //
    // Release lock and lower to initial irql
    //

    KeReleaseSpinLock (&KiReverseStallIpiLock, OldIrql);
    MmEnablePAT();
    return;
}

VOID
KiLoadPATTarget (
    IN PKIPI_CONTEXT SignalDone,
    IN PVOID NewPAT,
    IN PVOID Parameter2,
    IN PVOID Parameter3
    )
/*++

Routine Description:

    Synchronize with target processors prior to PAT modification.

Arguments:

    Context     - Context which includes the PAT to load

Return Value:

    None

--*/
{
    PNEW_PAT Context;

    UNREFERENCED_PARAMETER (Parameter2);
    UNREFERENCED_PARAMETER (Parameter3);

    Context = (PNEW_PAT) NewPAT;

    //
    // Wait for all processors to be ready
    //

    KiIpiSignalPacketDoneAndStall(SignalDone,
                                  Context->Synchronize.TargetPhase);

    //
    // Update PAT
    //

    KiLoadPAT (Context);
}

VOID
KiLoadPAT (
    IN PNEW_PAT Context
    )
/*++

Routine Description:

    This function loads the PAT to all processors.

Arguments:

    Context - Context which includes new PAT to load

Return Value:

    PAT on all processors programmed to new values

--*/
{
    BOOLEAN             Enable;
    ULONG               HldCr0, HldCr4;

    //
    // Disable interrupts
    //

    Enable = KeDisableInterrupts();

    //
    // Synchronize all processors
    //

    KiLockStepExecution (&Context->Synchronize);

    _asm {
        ;
        ; Get current CR0
        ;

        mov     eax, cr0
        mov     HldCr0, eax

        ;
        ; Disable caching & line fill
        ;

        and     eax, not CR0_NW
        or      eax, CR0_CD
        mov     cr0, eax

        ;
        ; Flush caches
        ;

        ;
        ; wbinvd
        ;

        _emit 0Fh
        _emit 09h

        ;
        ; Get current cr4
        ;

        _emit  0Fh
        _emit  20h
        _emit  0E0h             ; mov eax, cr4
        mov     HldCr4, eax

        ;
        ; Disable global page
        ;

        and     eax, not CR4_PGE
        _emit  0Fh
        _emit  22h
        _emit  0E0h             ; mov cr4, eax

        ;
        ; Flush TLB
        ;

        mov     eax, cr3
        mov     cr3, eax
    }

    //
    // Load new PAT
    //

    WRMSR (PAT_MSR, Context->Attributes.QuadPart);

    _asm {

        ;
        ; Flush caches.
        ;

        ;
        ; wbinvd
        ;

        _emit 0Fh
        _emit 09h

        ;
        ; Flush TLBs
        ;

        mov     eax, cr3
        mov     cr3, eax
    }

    _asm {
        ;
        ; Restore CR4 (global page enable)
        ;

        mov     eax, HldCr4
        _emit  0Fh
        _emit  22h
        _emit  0E0h             ; mov cr4, eax

        ;
        ; Restore CR0 (cache enable)
        ;

        mov     eax, HldCr0
        mov     cr0, eax
    }

    //
    // Wait for all processors to reach the same place,
    // restore interrupts and return.
    //

    KiLockStepExecution (&Context->Synchronize);
    KeEnableInterrupts (Enable);
}

