////////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity - Singularity ABI
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   PageTableService.cs
//
//  Note:
//

using System;
using System.GCs;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Singularity;
using MemoryManager = Microsoft.Singularity.Memory.MemoryManager;

namespace Microsoft.Singularity.V1.Services
{
    [CLSCompliant(false)]
    public struct PageTableService
    {
        [ExternalEntryPoint]
        public static unsafe uint * GetPageTable()
        {
            uint * pageTable = MemoryManager.UserPageTable;

            Tracing.Log(Tracing.Debug, "PageTableService.GetPageTable() = {0:x8}",
                        (UIntPtr)pageTable);

            return pageTable;
        }

#if UINTPTR_SUPPORT_IN_ABI
        [ExternalEntryPoint]
        public static UIntPtr GetPageCount()
        {
            UIntPtr count = MemoryManager.UserPageCount;

            Tracing.Log(Tracing.Debug, "PageTableService.GetPageCount() = {0}",
                        count);

            return count;
        }

        [ExternalEntryPoint]
        public static UIntPtr GetBaseAddress()
        {
            UIntPtr addr = MemoryManager.UserBaseAddr;

            Tracing.Log(Tracing.Debug, "PageTableService.GetBaseAddress() = {0}",
                        addr);

            return addr;
        }
#endif

        [ExternalEntryPoint]
        public static uint GetProcessTag()
        {
            uint tag = Thread.CurrentProcess.ProcessTag;

            Tracing.Log(Tracing.Debug, "PageTableService.GetProcessTag() = {0}",
                        (UIntPtr)tag);

            return tag;
        }

        [ExternalEntryPoint]
        public static UIntPtr Allocate(UIntPtr bytes)
        {
            UIntPtr addr = MemoryManager.UserAllocate(
                MemoryManager.PagesFromBytes(bytes),
                Thread.CurrentProcess, 0, PageType.Unknown);

            Tracing.Log(Tracing.Debug,
                        "PageTableService.Allocate(bytes={0} addr={1:x8}",
                        bytes, addr);

            return addr;
        }

        [ExternalEntryPoint]
        public static UIntPtr AllocateIOMemory(UIntPtr limit,
                                               UIntPtr bytes,
                                               UIntPtr alignment)
        {
            UIntPtr addr = MemoryManager.AllocateIOMemory(
                limit, bytes, alignment, Thread.CurrentProcess);

            Tracing.Log(Tracing.Debug,
                        "PageTableService.AllocateIOMemory(limit={0:x}, bytes={1:x}, alignment={2:x}) addr= {3:x8}",
                        limit, bytes, alignment, addr);

            return addr;
        }

        [ExternalEntryPoint]
        public static void FreeIOMemory(UIntPtr addr,
                                        UIntPtr bytes)
        {
            Tracing.Log(Tracing.Debug, "PageTableService.FreeIOMemory(addr={0:x8}, bytes={1:x})",
                        addr, bytes);

            MemoryManager.FreeIOMemory(addr, bytes, Thread.CurrentProcess);
        }

        [ExternalEntryPoint]
        public static UIntPtr AllocateExtend(UIntPtr addr,
                                             UIntPtr bytes)
        {
            UIntPtr got = MemoryManager.UserExtend(
                addr, MemoryManager.PagesFromBytes(bytes),
                Thread.CurrentProcess, PageType.Unknown);

            Tracing.Log(Tracing.Debug,
                        "PageTableService.AllocateExtend(addr={0:x}, bytes={1:x}) addr={2:x8}",
                        addr, bytes, got);

            return got;
        }

        [ExternalEntryPoint]
        public static void Free(UIntPtr addr,
                                UIntPtr bytes)
        {
            Tracing.Log(Tracing.Debug, "PageTableService.Free(addr={0:x8}, bytes={1:x})",
                        addr, bytes);

            // We always round up block allocations to the nearest page,
            // so round up when freeing, too.
            MemoryManager.UserFree(addr,
                                   MemoryManager.PagesFromBytes(bytes),
                                   Thread.CurrentProcess);
        }

        [ExternalEntryPoint]
        public static unsafe bool QueryImpl(
            UIntPtr queryAddr,
            UIntPtr * regionAddr,
            UIntPtr * regionSize)
        {
            return Query(queryAddr, out *regionAddr, out *regionSize);
        }

        public static bool Query(UIntPtr queryAddr,
                                 out UIntPtr regionAddr,
                                 out UIntPtr regionSize)
        {
            PageType type = MemoryManager.UserQuery(
                queryAddr, out regionAddr, out regionSize);

            bool used = (type != PageType.Unknown);

            Tracing.Log(Tracing.Debug,
                        "PageTableService.Query(query={0:x8}, addr={1:x8}, size={2:x}) type={3}",
                        queryAddr, regionAddr, regionSize, (uint)type);

            return used;
        }

        [ExternalEntryPoint]
        public static unsafe void GetUsageStatisticsImpl(
            ulong * allocatedCount,
            ulong * allocatedBytes,
            ulong * freedCount,
            ulong * freedBytes)
        {
            GetUsageStatistics(out *allocatedCount, out *allocatedBytes,
                out *freedCount, out *freedBytes);
        }

        public static void GetUsageStatistics(out ulong allocatedCount,
                                              out ulong allocatedBytes,
                                              out ulong freedCount,
                                              out ulong freedBytes)
        {
            MemoryManager.GetUserStatistics(
                out allocatedCount,
                out allocatedBytes,
                out freedCount,
                out freedBytes);
        }

        //////////////////////////////////////////////////////////////////////
#if !UINTPTR_SUPPORT_IN_ABI
        [ExternalEntryPoint]
        public static uint GetPageCount()
        {
            uint count = (uint)MemoryManager.UserPageCount;

            Tracing.Log(Tracing.Debug, "PageTableService.GetPageCount() = {0:x8}",
                        count);

            return count;
        }

        [ExternalEntryPoint]
        public static uint GetBaseAddress()
        {
            uint addr = (uint)MemoryManager.UserBaseAddr;

            Tracing.Log(Tracing.Debug, "PageTableService.GetBaseAddress() = {0}",
                        addr);

            return addr;
        }

        [ExternalEntryPoint]
        public static uint Allocate(uint bytes)
        {
            uint addr = (uint)MemoryManager.UserAllocate(
                MemoryManager.PagesFromBytes(bytes),
                Thread.CurrentProcess, 0, PageType.Unknown);

            Tracing.Log(Tracing.Debug,
                        "PageTableService.Allocate(bytes={0} addr={1:x8}",
                        bytes, addr);

            return addr;
        }

        [ExternalEntryPoint]
        public static uint AllocateIOMemory(uint limit,
                                            uint bytes,
                                            uint alignment)
        {
            uint addr = (uint)MemoryManager.AllocateIOMemory(
                limit, bytes, alignment, Thread.CurrentProcess);

            Tracing.Log(Tracing.Debug,
                        "PageTableService.AllocateIOMemory(limit={0:x}, bytes={1:x}, alignment={2:x}) addr= {3:x8}",
                        limit, bytes, alignment, addr);

            return addr;
        }

        [ExternalEntryPoint]
        public static uint AllocateExtend(uint addr,
                                          uint bytes)
        {
            uint got = (uint)MemoryManager.UserExtend(
                addr, MemoryManager.PagesFromBytes(bytes),
                Thread.CurrentProcess, PageType.Unknown);

            Tracing.Log(Tracing.Debug,
                        "PageTableService.AllocateExtend(addr={0:x}, bytes={1:x}) addr={2:x8}",
                        addr, bytes, got);

            return addr;
        }

        [ExternalEntryPoint]
        public static void Free(uint addr,
                                uint bytes)
        {
            Tracing.Log(Tracing.Debug, "PageTableService.Free(addr={0:x8}, bytes={1:x})",
                        addr, bytes);

            // We always round up block allocations to the nearest page,
            // so round up when freeing, too.
            MemoryManager.UserFree(addr,
                                   MemoryManager.PagesFromBytes(bytes),
                                   Thread.CurrentProcess);
        }

        [ExternalEntryPoint]
        public static void FreeIOMemory(uint addr,
                                        uint bytes)
        {
            Tracing.Log(Tracing.Debug, "PageTableService.FreeIOMemory(addr={0:x8}, bytes={1:x})",
                        addr, bytes);

            MemoryManager.FreeIOMemory(addr, bytes, Thread.CurrentProcess);
        }

        [ExternalEntryPoint]
        public static unsafe bool QueryImpl(
            uint queryAddr,
            uint * regionAddr,
            uint * regionSize)
        {
            return Query(queryAddr, out *regionAddr, out *regionSize);
        }

        public static bool Query(uint queryAddr,
                                 out uint regionAddr,
                                 out uint regionSize)
        {
            UIntPtr ra;
            UIntPtr rs;

            PageType type = MemoryManager.UserQuery(
                queryAddr, out ra, out rs);

            bool used = (type != PageType.Unknown);

            regionAddr = (uint)ra;
            regionSize = (uint)rs;

            Tracing.Log(Tracing.Debug,
                        "PageTableService.Query(query={0:x8}, addr={1:x8}, size={2:x}) type={3}",
                        queryAddr, regionAddr, regionSize, (uint)type);

            return used;
        }
#endif
    }
}
