///////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  Note: A very random bag of memory related functionality.
//
//  Caveat emptor:
//   - In TLB management the assumption is UNIPROC.
//   - TLB management assumes 1:1 page translation.
//   - TLB managment only invalidates DTLB entries.

namespace Microsoft.Singularity.Isal.Arm.XScale
{
    using System;
    using System.Runtime.InteropServices;
    using System.Runtime.CompilerServices;

    using Singularity.Io;

#if !SINGULARITY_KERNEL
#error "Compiling bad stuff into user space."
#endif // SINGULARITY_KERNEL

    [CLSCompliant(false)]
    [AccessedByRuntime("referenced in asm")]
    public sealed class Mmu
    {
        static UIntPtr ttb;

        static IoMemory l2TablePool; // Pool used for finite number of L2 pages
        static uint []  l2UsedBitmap;

        const uint CacheLineSize  = 32;
        const uint L2CacheSize    = 512 * 1024;
        const uint L1ICacheSize   = 32 * 1024;
        const uint L1DCacheSize   = 32 * 1024;

        const uint L2TableEntries = 256;
        const uint L2EntrySize    = 4;
        const uint L2TableSize    = L2TableEntries * L2EntrySize;
        static readonly uint MaxL2Tables;

        const uint L1SectionSize  = 0x100000;
        const uint L2SectionSize  = 0x1000;
        const int  L1SectionShift = 20;
        const int  L2SectionShift = 12;

        static Mmu()
        {
            ttb = GetTTB();

            // NB Table is 1MB section and 1MB aligned to avoid modifying page table entries on
            // page containing pool of

            l2TablePool  = IoMemory.AllocatePhysical(L1SectionSize, L1SectionSize);
            DebugStub.Assert(RangeIsPageAligned(l2TablePool.VirtualAddress,
                                                (uint)l2TablePool.Length,
                                                L1SectionSize));

            MaxL2Tables = L1SectionSize / L2TableSize;

            l2UsedBitmap = new uint [MaxL2Tables / 32];
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Page table management
        //

        [NoHeapAllocation]
        static private bool RangeIsPageAligned(UIntPtr start, UIntPtr length, UIntPtr pageSize)
        {
            return ((start & pageSize - 1) == 0) && ((length & pageSize - 1) == 0);
        }

        [NoHeapAllocation]
        private unsafe static UIntPtr AllocateL2Table()
        {
            for (int i = 0; i < l2UsedBitmap.Length; i++) {
                if (l2UsedBitmap[i] == 0xff) {
                    continue;
                }
                for (int j = 0; j < 32; j++) {
                    uint bit = 1u << j;
                    if ((l2UsedBitmap[i] & bit) == 0) {
                        l2UsedBitmap[i] |= bit;
                        int index = i * 32 + j;
                        return l2TablePool.VirtualAddress + (uint)(index * L2TableSize);
                    }
                }
            }
            return UIntPtr.Zero;
        }

        [NoHeapAllocation]
        private unsafe static void FreeL2Table(UIntPtr tableAddress)
        {
            DebugStub.Assert(
                tableAddress >= l2TablePool.VirtualAddress &&
                tableAddress <= (l2TablePool.VirtualAddress + MaxL2Tables * L2TableSize));
            int tableIndex = (int)((tableAddress - l2TablePool.VirtualAddress) / L2TableSize);
            uint mask = ~(1u << (tableIndex & 31));
            l2UsedBitmap[tableIndex / 32] &= mask;
        }

        [NoHeapAllocation]
        [Inline]
        private static bool IsSectionType(uint l1Entry)
        {
            return ((l1Entry & L1.TypeMask) == L1.SectionType) &&
                ((l1Entry & L1.SupersectionTypeMask) != L1.SupersectionType);
        }

        [NoHeapAllocation]
        private unsafe static void ConvertL1EntryToL2Table(uint l1EntryIndex)
        {
            DebugStub.Assert(Processor.InterruptsDisabled());
            uint* l1EntryPtr = ((uint*)ttb) + l1EntryIndex;
            uint l1Entry = *l1EntryPtr;

            // Convert page attributes from L1Entry to L2Entry

            DebugStub.Assert(IsSectionType(l1Entry));
            uint tex = (l1Entry & L1.TexMask) >> L1.TexRoll;
            uint ap  = (l1Entry & L1.ApMask) >> L1.ApRoll;
            uint cb  = l1Entry & (L1.CBit | L1.BBit);

            uint l2Bits = (L2Coarse.ExtendedSmallPageType |
                           cb | (ap << L2Coarse.Ap0Roll) |
                           (tex << L2Coarse.ExtendedSmallPageTexRoll));

            // Fill L2 Page Table

            uint addr    = l1Entry & L1.SectionAddressMask;

            UIntPtr l2Table = AllocateL2Table();
            DebugStub.Assert(((uint)l2Table & (L2TableSize - 1)) == 0);

            uint* l2EntryPtr = (uint*)l2Table;
            for (uint i = 0; i < L2TableEntries; i++) {
                *l2EntryPtr = addr | l2Bits;
                addr += 4096;
                l2EntryPtr++;
            }

            // Convert L1 section entry to L1 coarse page entry
            // and update.
            l1Entry &= (L1.DomainMask | L1.PBit);
            l1Entry |= L1.CoarsePageTableType;
            l1Entry |= (uint)l2Table;

            InvalidateDTlbEntry(*l1EntryPtr & L1.SectionAddressMask);
            *l1EntryPtr = l1Entry;

            CleanInvalidateDCacheLine((UIntPtr) l1EntryPtr);
            CleanInvalidateDCacheLines(l2Table, L2TableSize);
        }

        [NoHeapAllocation]
        private unsafe static void MakeRangeL1Entries(UIntPtr start, UIntPtr length)
        {
            DebugStub.Assert(RangeIsPageAligned(start, length, L1SectionSize));

            for (UIntPtr limit = start + length; start != limit; start += L1SectionSize) {
                uint* entryPtr = ((uint*)ttb) + (start >> L1SectionShift);
                uint type = *entryPtr & L1.TypeMask;
                if (type == L1.CoarsePageTableType) {
                    uint* l2Start = (uint*)(*entryPtr & L1.CoarsePageTableAddressMask);
                    uint  l2Entry = *l2Start;
                    for (int i = 0; i < L2TableEntries; i++) {
                        // Invalidate translations for entries in L2 table
                        InvalidateDTlbEntry(l2Start[i] & L2Coarse.ExtendedSmallPageAddressMask);
                    }

                    FreeL2Table((UIntPtr)l2Start);
                    CleanInvalidateDCacheLines((UIntPtr)l2Start, L2TableSize);

                    // Change entry from L2 Table pointer to L1 Section Entry
                    uint l1Entry = *entryPtr;

                    l1Entry &= (L1.DomainMask | L1.PBit);
                    l1Entry |= (l2Entry & (L2Coarse.ExtendedSmallPageTexMask | L2Coarse.Ap0Mask)) << (L1.ApRoll - L2Coarse.Ap0Roll);
                    l1Entry |= l2Entry & (L2Coarse.CBit | L2Coarse.BBit);
                    l1Entry |= L1.SectionType;
                    l1Entry |= start & L1.SectionAddressMask;

                    *entryPtr = l1Entry;
                    InvalidateDTlbEntry(start);
                }
                else {
                    DebugStub.Assert(IsSectionType(*entryPtr));
                }
            }
        }

        [NoHeapAllocation]
        private unsafe static void MakeRangeL2Entries(UIntPtr start, UIntPtr length)
        {
            DebugStub.Assert(RangeIsPageAligned(start, length, L2SectionSize));

            UIntPtr limit = (start + length + L1SectionSize - 1) & ~(L1SectionSize - 1);
            for (start = start & ~(L1SectionSize - 1); start != limit; start += L1SectionSize) {
                uint* entryPtr = ((uint*)ttb) + (start >> L1SectionShift);
                uint oldEntry = *entryPtr;
                uint type = *entryPtr & L1.TypeMask;
                if (type == L1.SectionType) {
                    ConvertL1EntryToL2Table((uint)start >> L1SectionShift);

                    uint addr = oldEntry & L1.SectionAddressMask;
                    InvalidateDTlbEntry((UIntPtr)addr);
                }
                else {
                    DebugStub.Assert(type == L1.CoarsePageTableType);
                }
            }
        }

        [NoHeapAllocation]
        private unsafe static void SetL2Attributes(UIntPtr start,
                                                   UIntPtr length,
                                                   bool cacheable,
                                                   bool bufferable)
        {
            DebugStub.Assert(RangeIsPageAligned(start, length, L2SectionSize));
            DebugStub.Assert((start & (L2SectionSize - 1)) + length <= L1SectionSize);

            uint* l1EntryPtr = ((uint*)ttb) + (start >> L1SectionShift);
            uint l1Entry = *l1EntryPtr;
            DebugStub.Assert((l1Entry & L1.TypeMask) == L1.CoarsePageTableType);

            uint cb = 0;
            if (cacheable) {
                cb |= L2Coarse.CBit;
            }
            if (bufferable) {
                cb |= L2Coarse.BBit;
            }
            const uint cbMask = ~(L2Coarse.CBit | L2Coarse.BBit);

            uint* l2StartPtr = (uint*)(l1Entry & L1.CoarsePageTableAddressMask);
            l2StartPtr += (start & L1SectionSize - 1) >> L2SectionShift;

            uint done = 0;

            uint* l2EndPtr = l2StartPtr + (length >> L2SectionShift);
            for (uint* l2Ptr = l2StartPtr; l2Ptr != l2EndPtr; l2Ptr++) {
                *l2Ptr = (*l2Ptr & cbMask) | cb;

                uint addr = *l2Ptr & L2Coarse.ExtendedSmallPageAddressMask;
                InvalidateDTlbEntry((UIntPtr)addr);
                CleanInvalidateDCacheLine(new UIntPtr(l2Ptr));

                DebugStub.Assert(addr != 0);
                done++;
            }
        }

        [NoHeapAllocation]
        private unsafe static void SetL1Attributes(UIntPtr  start,
                                                   UIntPtr  length,
                                                   bool     cacheable,
                                                   bool     bufferable)
        {
            DebugStub.Assert(RangeIsPageAligned(start, length, L1SectionSize));

            uint cbBits = 0;
            if (cacheable) {
                cbBits |= L1.CBit;
            }
            if (bufferable) {
                cbBits |= L1.BBit;
            }

            uint l1EntryOffset = ((uint)start) / L1SectionSize;

            uint* l1EntryStart = ((uint*)(ttb)) + l1EntryOffset;
            uint* l1EntryEnd   = l1EntryStart + ((length + L1SectionSize - 1) >> L1SectionShift);

            for (uint* l1Entry = l1EntryStart; l1Entry != l1EntryEnd; l1Entry++) {

                DebugStub.Assert(IsSectionType(*l1Entry));
                DebugStub.Assert((*l1Entry & L1.SectionSbz) == 0);

                *l1Entry &= ~(L1.CBit|L1.BBit);
                *l1Entry |= cbBits;

                uint addr = *l1Entry & L1.SectionAddressMask;
                InvalidateDTlbEntry((UIntPtr)addr);
                CleanInvalidateDCacheLine(new UIntPtr(l1Entry));
            }
        }

        [NoHeapAllocation]
        public unsafe static void SetCacheAttributes(UIntPtr startVA, UIntPtr length, bool cacheable, bool bufferable)
        {
            if (length == 0) {
                return;
            }

            DebugStub.Assert(
                RangeIsPageAligned(startVA, length, L1SectionSize) ||
                RangeIsPageAligned(startVA, length, L2SectionSize)
                );

            bool en = Processor.DisableInterrupts();
            try {
                UIntPtr todo = length;

                if (RangeIsPageAligned(startVA, todo, L1SectionSize)) {
                    MakeRangeL1Entries(startVA, todo);
                    SetL1Attributes(startVA, todo, cacheable, bufferable);
                }
                else if (RangeIsPageAligned(startVA, todo, L2SectionSize)) {
                    UIntPtr l1Boundary = (startVA + L1SectionSize - 1) & ~(L1SectionSize - 1);
                    if (startVA != l1Boundary) {
                        UIntPtr unaligned = l1Boundary - startVA;
                        if (unaligned > todo) {
                            unaligned = todo;
                        }
                        MakeRangeL2Entries(startVA, unaligned);
                        SetL2Attributes(startVA, unaligned, cacheable, bufferable);
                        todo -= unaligned;
                    }

                    if (todo > L1SectionSize) {
                        UIntPtr l1Todo = todo & ~(L1SectionSize - 1);
                        MakeRangeL1Entries(l1Boundary, l1Todo);
                        SetL1Attributes(l1Boundary, l1Todo,
                                        cacheable, bufferable);
                        l1Boundary += l1Todo;
                        todo -= l1Todo;
                    }

                    if (todo > 0) {
                        MakeRangeL2Entries(l1Boundary, todo);
                        SetL2Attributes(l1Boundary, todo,
                                        cacheable, bufferable);
                    }
                }
                else {
                    DebugStub.Break();
                }
            }
            finally {
                if (cacheable == false || bufferable == false) {
                    DebugStub.Assert(length != 0);
                    CleanInvalidateDCacheLines(startVA, length);
                }
                Processor.RestoreInterrupts(en);
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Cache management
        //

        [NoHeapAllocation]
        public static void CleanInvalidateDCacheLines(UIntPtr start, UIntPtr length)
        {
            Tracing.Log(Tracing.Debug, "CleanInvalidateDCacheLines({0:x8}..{1:x8})",
                        start, start + length);

            DebugStub.Assert(length < new UIntPtr(256 * 1024 * 1024));

            if (length == 0) {
                return;
            }

            DebugStub.Assert(length < new UIntPtr(256 * 1024 * 1024));

            bool en = Processor.DisableInterrupts();
            FlushPrefetch();
            MemoryFence();
            try {
                if (length >= L1DCacheSize) {
                    CleanInvalidateL1DCache();
                }
                else {
                    CleanInvalidateL1DCacheLines((uint)start, (uint)length);
                }

                if (length >= L2CacheSize) {
                    CleanInvalidateL2Cache();
                }
                else {
                    CleanInvalidateL2CacheLines((uint)start, (uint)length);
                }
            }
            finally {
                FlushPrefetch();
                MemoryFence();
                Processor.RestoreInterrupts(en);
            }
        }

        [NoHeapAllocation]
        public static void CleanDCacheLines(UIntPtr start, UIntPtr length)
        {
            if (length == 0) {
                return;
            }

            bool en = Processor.DisableInterrupts();
            FlushPrefetch();
            MemoryFence();
            try {
                if (length >= L1DCacheSize) {
                    CleanL1DCache();
                }
                else {
                    CleanL1DCacheLines((uint)start, (uint)length);
                }

                if (length >= L2CacheSize) {
                    CleanL2Cache();
                }
                else {
                    CleanL2CacheLines((uint)start, (uint)length);
                }
            }
            finally {
                FlushPrefetch();
                MemoryFence();
                Processor.RestoreInterrupts(en);
            }
        }

#if false
        [NoHeapAllocation]
        public static void InvalidateICacheLines(UIntPtr start, UIntPtr length)
        {
            Tracing.Log(Tracing.Debug, "InvalidateICacheLines({0:x8}..{1:x8})",
                        start, start + length);

            UIntPtr end = start + length;

            bool en = Processor.DisableInterrupts();
            FlushPrefetch();
            MemoryFence();
            try {
                if ((start & (CacheLineSize - 1)) != 0) {
                    start = start & ~(CacheLineSize - 1);
                    CleanInvalidateICacheLine((uint)start);
                    start += CacheLineSize;
                }
                if (start < end && (end & (CacheLineSize - 1)) != 0) {
                    end = end & ~(CacheLineSize - 1);
                    CleanInvalidateICacheLine((uint)end);
                }

                if (start < end) {
                    UIntPtr alength = end - start;

                    if (alength >= L1ICacheSize) {
                        InvalidateL1ICache();
                    }
                    else {
                        InvalidateL1ICacheLines((uint)start, (uint)alength);
                    }

                    InvalidateL2CacheLines((uint)start, (uint)alength);
                }
            }
            finally {
                FlushPrefetch();
                MemoryFence();
                Processor.RestoreInterrupts(en);
            }
        }
#endif

        [NoHeapAllocation]
        public static void InvalidateDCacheLines(UIntPtr start,
                                                 UIntPtr length)
        {
            if (length == 0) {
                return;
            }

            DebugStub.Assert(length < new UIntPtr(256 * 1024 * 1024));

            bool en = Processor.DisableInterrupts();
            FlushPrefetch();
            MemoryFence();
            try {
                if ((start & (CacheLineSize - 1)) != 0) {
                    // Start is not aligned, line may other data on it.
                    CleanInvalidateDCacheLine(start);
                }

                if (((start + length) & (CacheLineSize - 1)) != 0) {
                    // End is not aligned, line may other data on it.
                    CleanInvalidateDCacheLine((start + length) & ~(CacheLineSize - 1));
                }

                // NB Invalidate lines over specified range, never
                // invalidate entire cache since it likely contains
                // other peoples modified data.
                InvalidateL1DCacheLines((uint)start, (uint)length);
                InvalidateL2CacheLines((uint)start, (uint)length);
            }
            finally {
                FlushPrefetch();
                MemoryFence();
                Processor.RestoreInterrupts(en);
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Native methods
        //

        [AccessedByRuntime("defined in mmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(16)]
        private static extern UIntPtr GetTTB();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        public static extern void FlushPrefetch();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        public static extern void MemoryFence();

        //
        // TLB Management
        //

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        public static extern void InvalidateIAndDTlb();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateITlb();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateDTlb();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateITlbEntry(UIntPtr entry);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateDTlbEntry(UIntPtr entry);

        //
        // Cache Management (PRIVATE)
        //
        // All assume interrupts disabled.
        //

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanL1DCacheLines(UIntPtr start, UIntPtr length);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanInvalidateL1DCacheLines(UIntPtr start, UIntPtr length);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateL1DCacheLines(UIntPtr alignedStart, UIntPtr alignedLength);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateL1ICacheLines(UIntPtr alignedStart, UIntPtr alignedLength);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanInvalidateL2CacheLines(UIntPtr alignedStart, UIntPtr alignedLength);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanL2CacheLines(UIntPtr alignedStart, UIntPtr alignedLength);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateL2CacheLines(UIntPtr alignedStart, UIntPtr alignedLength);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanDCacheLine(UIntPtr addr);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanInvalidateDCacheLine(UIntPtr addr);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateICacheLine(UIntPtr addr);

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanL1DCache();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanInvalidateL1DCache();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateL1DCache();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void InvalidateL1ICache();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanInvalidateL2Cache();

        [AccessedByRuntime("defined in XscaleMmu.asm")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        private static extern void CleanL2Cache();
    }
}
