/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS Kernel
 * FILE:            ntoskrnl/kd/amd64/kdmemsup.c
 * PURPOSE:         Kernel Debugger Safe Memory Support
 *
 * PROGRAMMERS:     arty
 */

#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>

#define HIGH_PHYS_MASK 0x80000000
#define PAGE_TABLE_MASK 0x3ff
#define BIG_PAGE_SIZE (1<<22)
#define CR4_PAGE_SIZE_BIT 0x10
#define PDE_PRESENT_BIT 0x01
#define PDE_W_BIT 0x02
#define PDE_PWT_BIT 0x08
#define PDE_PCD_BIT 0x10
#define PDE_ACCESSED_BIT 0x20
#define PDE_DIRTY_BIT 0x40
#define PDE_PS_BIT 0x80

#define MI_KDBG_TMP_PAGE_1 (HYPER_SPACE + 0x400000 - PAGE_SIZE)
#define MI_KDBG_TMP_PAGE_0 (MI_KDBG_TMP_PAGE_1 - PAGE_SIZE)

/* VARIABLES ***************************************************************/

static BOOLEAN KdpPhysAccess = FALSE;

static
ULONG_PTR
KdpPhysMap(ULONG_PTR PhysAddr, LONG Len)
{
    MMPTE TempPte;
    PMMPTE PointerPte;
    ULONG_PTR VirtAddr;

    TempPte.u.Long = PDE_PRESENT_BIT | PDE_W_BIT | PDE_PWT_BIT |
                     PDE_PCD_BIT | PDE_ACCESSED_BIT | PDE_DIRTY_BIT;

    if ((PhysAddr & (PAGE_SIZE - 1)) + Len > PAGE_SIZE)
    {
        TempPte.u.Hard.PageFrameNumber = (PhysAddr >> PAGE_SHIFT) + 1;
        PointerPte = MiAddressToPte((PVOID)MI_KDBG_TMP_PAGE_1);
        *PointerPte = TempPte;
        VirtAddr = (ULONG_PTR)PointerPte << 10;
        KeInvalidateTlbEntry((PVOID)VirtAddr);
    }

    TempPte.u.Hard.PageFrameNumber = PhysAddr >> PAGE_SHIFT;
    PointerPte = MiAddressToPte((PVOID)MI_KDBG_TMP_PAGE_0);
    *PointerPte = TempPte;
    VirtAddr = (ULONG_PTR)PointerPte << 10;
    KeInvalidateTlbEntry((PVOID)VirtAddr);

    return VirtAddr + (PhysAddr & (PAGE_SIZE - 1));
}

static
ULONGLONG
KdpPhysRead(ULONG_PTR PhysAddr, LONG Len)
{
    ULONG_PTR Addr;
    ULONGLONG Result = 0;

    Addr = KdpPhysMap(PhysAddr, Len);

    switch (Len)
    {
    case 8:
        Result = *((PULONGLONG)Addr);
        break;
    case 4:
        Result = *((PULONG)Addr);
        break;
    case 2:
        Result = *((PUSHORT)Addr);
        break;
    case 1:
        Result = *((PUCHAR)Addr);
        break;
    }

    return Result;
}

static
VOID
KdpPhysWrite(ULONG_PTR PhysAddr, LONG Len, ULONGLONG Value)
{
    ULONG_PTR Addr;

    Addr = KdpPhysMap(PhysAddr, Len);

    switch (Len)
    {
    case 8:
        *((PULONGLONG)Addr) = Value;
        break;
    case 4:
        *((PULONG)Addr) = Value;
        break;
    case 2:
        *((PUSHORT)Addr) = Value;
        break;
    case 1:
        *((PUCHAR)Addr) = Value;
        break;
    }
}

BOOLEAN
NTAPI
KdpTranslateAddress(ULONG_PTR Addr, PULONG_PTR ResultAddr)
{
    ULONG_PTR CR3Value = __readcr3();
    ULONG_PTR CR4Value = __readcr4();
    ULONG_PTR PageDirectory = (CR3Value & ~(PAGE_SIZE-1)) +
        ((Addr >> 22) * sizeof(ULONG));
    ULONG_PTR PageDirectoryEntry = KdpPhysRead(PageDirectory, sizeof(ULONG));

    /* Not present -> fail */
    if (!(PageDirectoryEntry & PDE_PRESENT_BIT))
    {
        return FALSE;
    }

    /* Big Page? */
    if ((PageDirectoryEntry & PDE_PS_BIT) && (CR4Value & CR4_PAGE_SIZE_BIT))
    {
        *ResultAddr = (PageDirectoryEntry & ~(BIG_PAGE_SIZE-1)) +
            (Addr & (BIG_PAGE_SIZE-1));
        return TRUE;
    }
    else
    {
        ULONG_PTR PageTableAddr =
            (PageDirectoryEntry & ~(PAGE_SIZE-1)) +
            ((Addr >> PAGE_SHIFT) & PAGE_TABLE_MASK) * sizeof(ULONG);
        ULONG_PTR PageTableEntry = KdpPhysRead(PageTableAddr, sizeof(ULONG));
        if (PageTableEntry & PDE_PRESENT_BIT)
        {
            *ResultAddr = (PageTableEntry & ~(PAGE_SIZE-1)) +
                (Addr & (PAGE_SIZE-1));
            return TRUE;
        }
    }

    return FALSE;
}

BOOLEAN
NTAPI
KdpSafeReadMemory(ULONG_PTR Addr, LONG Len, PVOID Value)
{
    ULONG_PTR ResultPhysAddr;

    if (!KdpPhysAccess)
    {
        memcpy(Value, (PVOID)Addr, Len);
        return TRUE;
    }

    memset(Value, 0, Len);

    if (!KdpTranslateAddress(Addr, &ResultPhysAddr))
        return FALSE;

    switch (Len)
    {
    case 8:
        *((PULONGLONG)Value) = KdpPhysRead(ResultPhysAddr, Len);
        break;
    case 4:
        *((PULONG)Value) = KdpPhysRead(ResultPhysAddr, Len);
        break;
    case 2:
        *((PUSHORT)Value) = KdpPhysRead(ResultPhysAddr, Len);
        break;
    case 1:
        *((PUCHAR)Value) = KdpPhysRead(ResultPhysAddr, Len);
        break;
    }

    return TRUE;
}

BOOLEAN
NTAPI
KdpSafeWriteMemory(ULONG_PTR Addr, LONG Len, ULONGLONG Value)
{
    ULONG_PTR ResultPhysAddr;

    if (!KdpPhysAccess)
    {
        memcpy((PVOID)Addr, &Value, Len);
        return TRUE;
    }

    if (!KdpTranslateAddress(Addr, &ResultPhysAddr))
        return FALSE;

    KdpPhysWrite(ResultPhysAddr, Len, Value);
    return TRUE;
}

VOID
NTAPI
KdpEnableSafeMem(VOID)
{
    KdpPhysAccess = TRUE;
}

/* EOF */
