////////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   PhysicalPages.cs - Primitive memory manager
//
//  Notes:
//
//  This module manages access to the machine's physical memory pages.
//  This serves as the substrate to paging, so this code cannot assume
//  that it can actually read and write the physical pages themselves,
//  as no mapping to them is likely to exist.
//

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;

using Microsoft.Singularity;
using Microsoft.Singularity.Hal;

namespace Microsoft.Singularity.Memory
{
    [NoCCtor]
    [CLSCompliant(false)]
    public class PhysicalPages
    {
        /////////////////////////////////////
        // CONSTANTS
        /////////////////////////////////////

        // Never use physical memory below this limit
        internal const ulong LowerRAMBlackout = 0x200000; // 2MB

        /////////////////////////////////////
        // CONSTANTS
        /////////////////////////////////////
        private static SpinLock pagesLock;

        private static uint freeList; // index into the table or PageBlock.Sentinel
        private static unsafe PageBlock* physTable;
        private static uint numBlocks;
        private static ulong tableSize;

        // The physical max RAM address
        private static ulong addressLimit;

        /////////////////////////////////////
        // PUBLIC METHODS
        /////////////////////////////////////

        //
        // Initialize() must be called during OS boot *BEFORE* paging
        // is enabled, so that physical memory can be accessed
        // directly. We consult the system's memory map to pick a
        // range of memory where we can situate the physical page
        // table, and initialize it.
        //
        // the reserveSize argument specifies how much physical
        // memory to set aside and return a PhysicalAddress to.
        // This is used by the MemoryManager to reserve an area
        // of contiguous physical memory for use in I/O operations.
        //
        // After Initialize()ation, the PhysicalPages module
        // assumes that its page table will be identity-mapped
        // into the kernel's virtual memory space.
        //
        internal static unsafe PhysicalAddress Initialize(ulong reserveSize)
        {
            Platform p = Platform.ThePlatform;
            UIntPtr lowerAddress = UIntPtr.Zero;
            UIntPtr upperAddress = UIntPtr.Zero;

            // NB: our initialization is currently naive; we
            // set up a table to describe all of physical memory,
            // even though there are holes in it for reserved
            // memory that we will never be able to use.
            // This makes indexing much easier, though.

            InitializeLock();

            // Retrieve the highest RAM address
            MemoryManager.PhysicalMemoryLimits(out lowerAddress, out upperAddress);
            addressLimit = (ulong)upperAddress;

            DebugStub.WriteLine("Highest usable RAM address is 0x{0:x8}",
                                __arglist(addressLimit));

            // How much room do we need for the pageblock table?
            numBlocks = GetNumBlocks(addressLimit);
            ulong requiredSpace = (ulong)numBlocks * (ulong)sizeof(PageBlock);

            // Now we need to find a location in physical memory
            // where we can stick the physical pageblock table.
            ulong startAddr = LowerRAMBlackout;
            if (p.BootAllocatedMemorySize != 0) {
                if (startAddr > p.BootAllocatedMemory &&
                    startAddr < p.BootAllocatedMemory + p.BootAllocatedMemorySize) {
                    startAddr = (ulong)p.BootAllocatedMemory + (ulong)p.BootAllocatedMemorySize;
                }
            }

            ulong physLocation = FindFreeBlockInSMAP(startAddr, requiredSpace);

            if (physLocation == 0) {
                // Failed to find a spot to park the physical page
                // table. This is fatal!
                Kernel.Panic("Couldn't find enough room to initialize hardware page table");
            }

            // Initialize all the descriptor blocks as "free" (zeroed)
            physTable = (PageBlock*)physLocation;
            tableSize = requiredSpace;
            freeList = 0; // First descriptor

            for (uint i = 0; i < numBlocks; i++) {
                PageBlock* thisBlock = GetBlock(i);
                thisBlock->Initialize();
                thisBlock->prevIdx = (i == 0) ? PageBlock.Sentinel : i - 1;
                thisBlock->nextIdx = (i == numBlocks - 1) ? PageBlock.Sentinel : i + 1;
            }

            // Mark the blackout area of low physical memory as used
            MarkRangeUsed(0, startAddr - 1);

            // Now mark the range of physical pages occupied by the
            // hardware table itself as being used!
            MarkRangeUsed((ulong)physTable, requiredSpace);

            // Mark any non-Free areas (according to SMAP) as in use
            SMAPINFO* smap = p.Smap;
            for (uint i = 0; i < p.SmapCount; i++) {
                if ((smap[i].type != (ulong)SMAPINFO.AddressType.Free) &&
                    ((smap[i].addr + smap[i].size) > startAddr) &&
                    (smap[i].addr < addressLimit)) {

                    ulong unaccountedStart, unaccountedLength;

                    if (smap[i].addr >= startAddr) {
                        unaccountedStart = smap[i].addr;
                        unaccountedLength = smap[i].size;
                    }
                    else {
                        // Part of this memory window is already accounted for
                        unaccountedStart = startAddr;
                        unaccountedLength = smap[i].size - (startAddr - smap[i].addr);
                    }

                    MarkRangeUsed(unaccountedStart, unaccountedLength);
                }
            }

            // Mark out all the Platform special regions as busy
            if (p.MiniDumpLimit != 0) {
                ulong miniDumpSize = (ulong)p.MiniDumpLimit - (ulong)p.MiniDumpBase;
                MarkRangeUsed((ulong)p.MiniDumpBase,
                              MemoryManager.PagePad(miniDumpSize));
            }
            if (p.KernelDllSize != 0) {
                MarkRangeUsed((ulong)p.KernelDllBase,
                              MemoryManager.PagePad((ulong)p.KernelDllSize));
            }
            if (p.BootAllocatedMemorySize != 0) {
                MarkRangeUsed((ulong)p.BootAllocatedMemory,
                              MemoryManager.PagePad((ulong)p.BootAllocatedMemorySize));
            }

            // Last step: find an available area to situate the caller's
            // requested reserve-block, if any.
            PhysicalAddress reservedPtr = PhysicalAddress.Null;

            if (reserveSize != 0) {
                reservedPtr = new PhysicalAddress(
                    FindFreeBlockInSMAP(
                        MemoryManager.PagePad((ulong)physTable + tableSize),
                        reserveSize));

                if (reservedPtr == PhysicalAddress.Null) {
                    Kernel.Panic("Couldn't find enough physically contiguous memory to reserve");
                }

                if (reservedPtr.Value + reserveSize > 0xFFFFFF) {
                    Kernel.Panic("Couldn't find enough physically contiguous memory below 0xFFFFFF for I/O memory");
                }
            }

            // Mark the reserved block as used. It's up to the caller
            // to administer its use.
            MarkRangeUsed(reservedPtr.Value, reserveSize);

            CheckConsistency();

            DebugStub.WriteLine("PhysicalPages initialized at 0x{0:x8} - 0x{1:x8} with {2} physical pages available.",
                                __arglist(reservedPtr.Value,
                                          reservedPtr.Value + reserveSize,
                                          GetFreePageCount()));

            return reservedPtr;
        }

        // Allocate a single hardware page
        internal unsafe static PhysicalAddress AllocPage()
        {
            bool iflag = Lock();

            try {
                CheckConsistency();

                if (freeList != PageBlock.Sentinel) {
                    uint blockIdx = freeList;
                    DebugStub.Assert(GetBlock(blockIdx)->IsLinked(freeList));
                    uint freePage = GetBlock(blockIdx)->FirstFreePage();
                    MarkPageInBlockUsed(blockIdx, freePage);

                    ulong physPage =
                        ((ulong)blockIdx * (ulong)PageBlock.PagesPerBlock) + freePage;

                    ulong physAddr = (ulong)MemoryManager.PageSize * physPage;

                    return new PhysicalAddress(physAddr);
                }

                DebugStub.WriteLine("** Physical memory exhausted! **");
                return PhysicalAddress.Null;
            }
            finally {
                CheckConsistency();
                Unlock(iflag);
            }
        }

        // Free a single physical page
        internal unsafe static void FreePage(PhysicalAddress addr)
        {
            bool iflag = Lock();

            try {
                CheckConsistency();
                ulong pageNum = MemoryManager.PagesFromBytes(addr.Value);
                uint blockIdx = (uint)(pageNum / (ulong)PageBlock.PagesPerBlock);
                uint pageIdx = (uint)(pageNum % (ulong)PageBlock.PagesPerBlock);
                PageBlock* thisBlock = GetBlock(blockIdx);
                bool wasFull = thisBlock->Full;

                thisBlock->MarkAsFree(pageIdx);

                if (wasFull) {
                    // This block now has a page available; put it on the
                    // free list
                    DebugStub.Assert(!thisBlock->IsLinked(freeList));
                    thisBlock->Link(ref freeList);
                }
            }
            finally {
                CheckConsistency();
                Unlock(iflag);
            }
        }

        // Report how many pages are free
        internal unsafe static ulong GetFreePageCount()
        {
            bool iflag = Lock();

            try {
                return FreePageCountFromList();
            }
            finally {
                Unlock(iflag);
            }
        }

        public static ulong GetMaxMemory()
        {
            return addressLimit;
        }

        public static unsafe ulong GetFreeMemory()
        {
            return GetFreePageCount() * MemoryManager.PageSize;
        }

        public static unsafe ulong GetUsedMemory()
        {
            return GetMaxMemory() - GetFreeMemory();
        }

        /////////////////////////////////////
        // PRIVATE METHODS
        /////////////////////////////////////

        [Inline]
        private unsafe static PageBlock* GetBlock(uint blockIdx)
        {
            DebugStub.Assert(blockIdx < numBlocks);
            DebugStub.Assert(blockIdx != PageBlock.Sentinel);
            return &(physTable[blockIdx]);
        }

        // Get the number of descriptor blocks that are necessary for
        // a given amount of physical RAM
        private static uint GetNumBlocks(ulong physicalRAM)
        {
            ulong pages = MemoryManager.PagesFromBytes(physicalRAM);

            // Round up the number of blocks needed
            ulong blocks = pages / PageBlock.PagesPerBlock;

            if ((pages % PageBlock.PagesPerBlock) > 0) {
                blocks++;
            }

            return (uint)blocks;
        }

        private static unsafe void MarkPageInBlockUsed(uint blockIdx,
                                                       uint pageInBlock)
        {
            PageBlock* pBlock = GetBlock(blockIdx);
            pBlock->MarkAsUsed(pageInBlock);

            if (pBlock->Full) {
                pBlock->Unlink(ref freeList);
            }
        }

        // Mark a range of physical memory as being used. THIS IS ONLY
        // USED DURING INITIALIZATION, and so is not particularly
        // efficient.
        //
        // Addresses do not have to be page-aligned, but whole pages will
        // be marked.
        //
        // To allow SMAP overlaps with HAL regions make this ignore
        // region overlaps.
        private static unsafe void MarkRangeUsed(ulong startAddr, ulong length)
        {
            ulong firstPage = startAddr & ~MemoryManager.PageMask;
            // lastPage ends up being the address of the page immediately *after*
            // the data ends.
            ulong lastPage = MemoryManager.PagePad(startAddr + length);

            for (ulong pageAddr = firstPage; pageAddr < lastPage; pageAddr += MemoryManager.PageSize) {
                ulong pageNum = MemoryManager.PagesFromBytes(pageAddr);
                uint blockIdx = (uint)(pageNum / (ulong)PageBlock.PagesPerBlock);
                uint pageInBlock = (uint)(pageNum % (ulong)PageBlock.PagesPerBlock);
                PageBlock* pBlock = GetBlock(blockIdx);

                if (!pBlock->PageInUse(pageInBlock)) {
                    pBlock->MarkAsUsed(pageInBlock);

                    if (pBlock->Full) {
                        pBlock->Unlink(ref freeList);
                    }
                }
            }
        }

        private static unsafe ulong FindFreeBlockInSMAP(ulong startAddr, ulong requiredSpace)
        {
            SMAPINFO *smap = Platform.ThePlatform.Smap;

            for (uint i = 0; i < Platform.ThePlatform.SmapCount; i++) {
                if (smap[i].type == (ulong)SMAPINFO.AddressType.Free &&
                    smap[i].addr + smap[i].size > startAddr) {

                    ulong usableSize, usableStart;

                    if (smap[i].addr < startAddr) {
                        usableSize = smap[i].size - (startAddr - smap[i].addr);
                        usableStart = startAddr;
                    }
                    else {
                        usableSize = smap[i].size;
                        usableStart = smap[i].addr;
                    }

                    if (usableSize >= requiredSpace) {
                        return usableStart;
                    }
                }
            }

            return 0;
        }

        private unsafe static ulong FreePageCountFromList()
        {
            ulong retval = 0;
            uint freeIdx = freeList;

            while (freeIdx != PageBlock.Sentinel) {
                PageBlock* pBlock = GetBlock(freeIdx);
                DebugStub.Assert(!pBlock->Full);
                retval += pBlock->FreePages;
                freeIdx = pBlock->nextIdx;
            }

            return retval;
        }

        private unsafe static void CheckConsistency()
        {
#if SELF_TEST
            ulong pagesFromFreeList = FreePageCountFromList();
            ulong pagesFromWalk = 0;

            // Now walk the whole page table and count up
            // pages that way
            for (uint i = 0; i < numBlocks; i++) {
                PageBlock* pBlock = GetBlock(i);
                pagesFromWalk += pBlock->FreePages;
            }

            DebugStub.Assert(pagesFromWalk == pagesFromFreeList);
#endif
        }

        private static void InitializeLock()
        {
#if SINGULARITY_MP
            pagesLock = new SpinLock(SpinLock.Types.PhysicalPages);
#endif // SINGULARITY_MP
        }

        [NoStackLinkCheckTrans]
        private static bool Lock()
        {
            bool enabled = Processor.DisableInterrupts();
#if SINGULARITY_MP
            pagesLock.Acquire(Thread.CurrentThread);
#endif // SINGULARITY_MP
            return enabled;
        }

        [NoStackLinkCheckTrans]
        private static void Unlock(bool iflag)
        {
#if SINGULARITY_MP
            pagesLock.Release(Thread.CurrentThread);
#endif // SINGULARITY_MP
            Processor.RestoreInterrupts(iflag);
        }


        /////////////////////////////////////
        // HELPER STRUCTS
        /////////////////////////////////////

        // This is our descriptor for a range of physical
        // pages. Each PageBlock describes a range of
        // physical pages with a used/not-used bitmap.
        [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)]
        private struct PageBlock
        {
            // CONSTS

            // "null" value for indexes
            internal const uint Sentinel = uint.MaxValue;
            private const uint RegionsPerBlock = 29;
            internal const uint PagesPerBlock = RegionsPerBlock * 32;
            private const uint FullRegion = uint.MaxValue;
            private const uint BlockMapMask = 0x1FFFFFFF; // 29 bits
            private const uint FullMap = BlockMapMask;
            private const uint UnusedMask = 0xE0000000;

            // ------------------------------ struct fields
            internal unsafe uint prevIdx; // 4 bytes
            internal unsafe uint nextIdx; // +4 bytes = 8bytes

            // The PageBlock has a mini-tree structure:
            // this first word is a bitmap indicating
            // the availability of 29 subregions.

            // The bit for a subregion is SET if it is
            // *completely full*, and CLEAR otherwise.
            // The top 3 bits are kept SET.
            internal uint blockMap;       // +4 bytes = 12 bytes

            // Each region has 4 bytes, making PageBlock
            // 128 bytes in size all together. Each region
            // describes 32 pages, one per bit. Each bit
            // is SET if the page is in use, and CLEAR
            // if it is free.

            // Due to C# madness, here are some dummy
            // variables to block out the space.
            uint  i1;                    // +4 bytes = 16bytes
            ulong l1, l2, l3, l4, l5,
                  l6, l7, l8, l9, l10,
                  l11, l12, l13, l14;    // +112 bytes = 128bytes

            // ------------------------------

            internal void Initialize()
            {
                prevIdx = nextIdx = Sentinel;
                blockMap = 0;
                l1 = l2 = l3 = l4 = l5 =
                    l6 = l7 = l8 = l9 = l10 =
                    l11 = l12 = l13 = l14 = 0;
                i1 = 0;
                CheckConsistency();
            }

            internal unsafe void Unlink(ref uint listHead)
            {
                DebugStub.Assert(IsLinked(listHead));

                if (prevIdx != Sentinel) {
                    DebugStub.Assert(GetBlock(prevIdx)->nextIdx == Index, "PhysicalPages.PageBlock.Unlink: inconsistent prev->next");
                    GetBlock(prevIdx)->nextIdx = nextIdx;
                }
                else {
                    // We are the list head.
                    DebugStub.Assert(listHead == Index, "PhysicalPages.PageBlock.Unlink: inconsistent head");
                    listHead = nextIdx;
                }

                if (nextIdx != Sentinel) {
                    DebugStub.Assert(GetBlock(nextIdx)->prevIdx == Index, "PhysicalPages.PageBlock.Unlink: inconsistent next->prev");
                    GetBlock(nextIdx)->prevIdx = prevIdx;
                }

                nextIdx = Sentinel;
                prevIdx = Sentinel;
            }

            internal unsafe void Link(ref uint listHead)
            {
                DebugStub.Assert(!IsLinked(listHead));

                if (listHead != Sentinel) {
                    DebugStub.Assert(GetBlock(listHead)->prevIdx == Sentinel, "PhysicalPages.PageBlock.Link: Inconsistent head-entry prev link");
                    GetBlock(listHead)->prevIdx = Index;
                }

                nextIdx = listHead;
                listHead = Index;
                prevIdx = Sentinel;
            }

            internal unsafe bool PageInUse(uint pageIdx)
            {
                CheckConsistency();
                DebugStub.Assert(pageIdx < PagesPerBlock, "PhysicalPages.PageBlock.PageInUse: Bad page index");

                fixed ( uint* pBeforeRegions = &blockMap ) {
                    uint* pRegions = pBeforeRegions; // Point just before regions
                    pRegions++; // Advance to the first region word
                    uint regionIdx = pageIdx / 32;
                    uint bitIdx = pageIdx % 32;
                    return ((pRegions[regionIdx] & (1 << (int)bitIdx)) != 0);
                }
            }

            [Inline]
            internal unsafe bool RegionFull(uint regionIdx)
            {
                DebugStub.Assert(regionIdx < RegionsPerBlock, "PhysicalPages.PageBlock.RegionFull: Bad region index");
                return ((blockMap & (1 << (int)regionIdx)) != 0);
            }

            // pageIdx specifies a page within this block
            // (zero is the first, PagesPerBlock-1 is the last)
            internal unsafe void MarkAsUsed(uint pageIdx)
            {
                CheckConsistency();

                fixed( uint *pBeforeRegions = &blockMap ) {
                    uint* pRegions = pBeforeRegions; // Point just before regions
                    pRegions++; // Advance to the first region word
                    uint regionIdx = pageIdx / 32;
                    uint bitIdx = pageIdx % 32;

                    DebugStub.Assert(pageIdx < PagesPerBlock, "PhysicalPages.PageBlock.MarkAsUsed: Bad page index");
                    DebugStub.Assert(!PageInUse(pageIdx), "PhysicalPages.PageBlock.MarkAsUsed: Page already in use");
                    DebugStub.Assert(!RegionFull(regionIdx), "PhysicalPages.PageBlock.MarkAsUsed: Region already full");

                    // Mark the page used in its subregion
                    pRegions[regionIdx] |= ((uint)1 << (int)bitIdx);

                    // If our whole subregion is full, mark the blockMap
                    if (pRegions[regionIdx] == FullRegion) {
                        blockMap |= ((uint)1 << (int)regionIdx);
                    }
                }

                CheckConsistency();
            }

            internal unsafe void MarkAsFree(uint pageIdx)
            {
                CheckConsistency();

                fixed( uint *pBeforeRegions = &blockMap ) {
                    uint* pRegions = pBeforeRegions; // Point just before regions
                    pRegions++; // Advance to the first region word
                    uint regionIdx = pageIdx / 32;
                    uint bitIdx = pageIdx % 32;
                    DebugStub.Assert(pageIdx < PagesPerBlock, "PhysicalPages.PageBlock.MarkAsFree: Bad page index");
                    DebugStub.Assert(PageInUse(pageIdx), "PhysicalPages.PageBlock.MarkAsFree: Page already free");

                    // Mark the page free in its subregion
                    pRegions[regionIdx] &= ~((uint)1 << (int)bitIdx);

                    // Mark our subregion as being usable
                    blockMap &= ~((uint)1 << (int)regionIdx);
                }

                CheckConsistency();
            }

            // Illegal to call this on a full block.
            internal unsafe uint FirstFreePage()
            {
                DebugStub.Assert(!Full, "PhysicalPages.PageBlock.FirstFreePage: Block already full");

                // Look for a clear bit in the region map
                for (int i = 0; i < RegionsPerBlock; i++) {
                    if ((blockMap & (uint)((uint)1 << i)) == 0) {
                        // This region is available. Look
                        // for a free page in the specific
                        // region.
                        fixed (uint* pBeforeRegions = &blockMap) {
                            uint* pRegion = pBeforeRegions + i + 1;
                            for (int j = 0; j < 32; j++) {
                                if ((*pRegion & ((uint)1 << j)) == 0) {
                                    // This page is free.
                                    return ((uint)i * 32) + (uint)j;
                                }
                            }
                        }
                    }
                }

                // If we're not full, we should never get here.
                Kernel.Panic("Inconsistency in PhysicalPages.PageBlock.FirstFreePage()");
                return 0;
            }

            internal bool Full {
                [Inline]
                get {
                    CheckConsistency();
                    return (blockMap & BlockMapMask) == FullMap;
                }
            }


            internal bool Empty {
                [Inline]
                 get {
                    CheckConsistency();
                    return (blockMap & BlockMapMask) == 0;
                }
            }

            // Illegal to call this on a full block.
            internal unsafe uint FreePages {
                get {
                    CheckConsistency();

                    uint retval = 0;

                    for (int i = 0; i < RegionsPerBlock; i++) {
                        if ((blockMap & (uint)((uint)1 << i)) == 0) {
                            // This region has free pages.
                            fixed (uint* pBeforeRegions = &blockMap) {
                                uint* pRegion = pBeforeRegions + i + 1;
                                for (int j = 0; j < 32; j++) {
                                    if ((*pRegion & ((uint)1 << j)) == 0) {
                                        // This page is free.
                                        retval++;
                                    }
                                }
                            }
                        }
                    }

                    return retval;
                }
            }

            internal bool IsLinked(uint listHead) {
                CheckConsistency();

                if (prevIdx == Sentinel) {
                    // We are the head of *some* list
                    return listHead == Index;
                }

                if ((prevIdx != Sentinel) || (nextIdx != Sentinel)) {
                    // We are linked into *some* list. We only
                    // have the freeList at the moment, so for now
                    // skip the expensive process of walking the list
                    // to confirm we're in the right one
                    return true;
                }

                return false;
            }

            internal unsafe uint Index {
                [Inline]
                get {
                    fixed( PageBlock* pThis = &this ) {
                        DebugStub.Assert(pThis >= physTable, "PhysicalPages.PageBlock.Index: this < physTable");
                        DebugStub.Assert((ulong)pThis < (ulong)physTable + (ulong)tableSize, "PhysicalPages.PageBlock.Index: this > table bounds");
                        return (uint)(pThis - physTable);
                    }
                }
            }

            private unsafe void CheckConsistency()
            {
#if SELF_TEST
                // Top bits should be kept clear
                DebugStub.Assert((blockMap & UnusedMask) == 0);

                fixed ( uint* pBeforeRegions = &blockMap ) {
                    uint* pRegion = pBeforeRegions;
                    pRegion++;

                    for (uint i = 0; i < RegionsPerBlock; i++, pRegion++) {
                        if ((*pRegion) != FullRegion) {
                            DebugStub.Assert((blockMap & ((uint)1 << (int)i)) == 0, "PhysicalPages.PageBlock.CheckConsistency: blockMap shows a free region as full");
                        }
                        else {
                            DebugStub.Assert((blockMap & ((uint)1 << (int)i)) != 0, "PhysicalPages.PageBlock.CheckConsistency: blockMap shows a full region as free");
                        }
                    }
                }
#endif
            }
        }
    }
}

