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

//#define VERBOSE

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

using Microsoft.Singularity.Io;
using Microsoft.Singularity.Loader;
using Microsoft.Singularity.Memory;

namespace Microsoft.Singularity
{

    //
    // A protection domain consists of an address space, a communication
    // heap, and a general-purpose memory range. A protection domain
    // can host multiple processes.
    //
    [CLSCompliant(false)]
    public class ProtectionDomain
    {
        /////////////////////////////////////
        // STATIC DATA
        /////////////////////////////////////

        // Table of protection domains
        private const uint maxPDs = 1024;
        private static ProtectionDomain[] PdTable;
        private static SmartSpinlock tableLock;
        private static uint PdIndexGenerator;

        /////////////////////////////////////
        // INSTANCE DATA
        /////////////////////////////////////

        // -------------- Valid before InitHook()
        private AddressSpace space;
        private int refCount;
        private uint id;
        private readonly bool kernelMode;
        private readonly string name;

        // Used to protect the user-range page mapping
        // descriptors.
        private readonly SmartSpinlock userMappingLock;

        // -------------- Valid after InitHook()
        private VirtualMemoryRange userRange;
#if PAGING
        private SharedHeap userSharedHeap;

        // These are always null for a kernel domain
        private PEImage ring3AbiImage;
        private ExportTable ring3AbiExports;
#endif

        // Used for initialization
        private bool initialized;
        private SmartSpinlock initSpin;

        /////////////////////////////////////
        // PUBLIC STATIC METHODS
        /////////////////////////////////////

        internal static void Initialize()
        {
            tableLock = new SmartSpinlock(SpinLock.Types.ProtectionDomainTable);
            PdTable = new ProtectionDomain[maxPDs];
            PdIndexGenerator = 1;

            // Create the default protection domain
            AddressSpace defaultSpace = VMManager.GetBootstrapSpace();
            PdTable[0] = new ProtectionDomain(defaultSpace, "Default", true);
        }

        internal static void InitializeSharedHeapWalker()
        {
            // TODO: The walker needs to become per-protection-domain
            // on paging systems
            SharedHeapWalker.Initialize(
                SharedHeap.KernelSharedHeap.DataOwnerId,
                SharedHeap.KernelSharedHeap.EndpointOwnerId);
        }

        internal static ProtectionDomain FindOrCreateByName(string name,
                                                            bool kernelMode)
        {
            bool iflag = tableLock.Lock();

            try {
                for (uint i = 0; i < PdTable.Length; i++) {
                    if ((PdTable[i] != null) &&
                        (PdTable[i].name == name)) {
                        // found it
                        return PdTable[i];
                    }
                }

                return CreateProtectionDomain(name, kernelMode);
            }
            finally {
                tableLock.Unlock(iflag);
            }
        }

        internal static ProtectionDomain DefaultDomain
        {
            get {
                DebugStub.Assert(PdTable[0] != null);
                return PdTable[0];
            }
        }

        internal static ProtectionDomain CurrentDomain
        {
            get {
#if PAGING
                return Thread.CurrentThread.CurrentDomain;
#else
                return Thread.CurrentThread.Process.Domain;
#endif
            }
        }

        /////////////////////////////////////
        // PUBLIC INSTANCE METHODS
        /////////////////////////////////////

        internal void AddRef()
        {
            Interlocked.Increment(ref refCount);
        }

        internal int Release()
        {
            int decreased = Interlocked.Decrement(ref refCount);
            DebugStub.Assert(decreased > 0);
            return decreased;
        }

        internal AddressSpace AddressSpace {
            get {
                return space;
            }
        }

        internal SmartSpinlock UserMappingLock {
            get {
                return userMappingLock;
            }
        }

        internal bool KernelMode {
            get {
                return kernelMode;
            }
        }

#if PAGING
        internal ExportTable ABIStubs {
            get {
                return ring3AbiExports;
            }
        }
#endif

        //
        // Someone must arrange to call this from *within* the
        // Protection Domain for us to have an opportunity to finish
        // initializing.
        //
        internal unsafe void InitHook()
        {
            // If paging is disabled then just return immediately
            if (!MemoryManager.UseAddressTranslation) {
                return;
            }

            DebugStub.Assert(AddressSpace.CurrentAddressSpace == this.AddressSpace);

            if (this.initialized) {
                // Someone else has already set up the space
                return;
            }

            bool iflag = initSpin.Lock();

            try {
                if (this.initialized) {
                    // Someone else snuck in and initialized
                    return;
                }

                //
                // We're first into this space, so set it up.
                //
#if VERBOSE
                DebugStub.WriteLine("Setting up protection domain \"{0}\"",
                                    __arglist(this.name));
#endif

                userRange = new VirtualMemoryRange(VMManager.UserHeapBase,
                                                   VMManager.UserHeapLimit,
                                                   this);
#if PAGING
                if (kernelMode) {
                    // This will be a ring-0, trusted domain, so just
                    // point the userSharedHeap at the kernel's comm heap.
                    userSharedHeap = SharedHeap.KernelSharedHeap;

                    this.initialized = true;
                }
                else {
                    // Create a new shared heap that lives in
                    // user-land.
                    userSharedHeap = new SharedHeap(this, userRange);
#if VERBOSE
                    DebugStub.WriteLine("  ...Created a shared heap");
#endif

                    //
                    // N.B.: this is kind of tricky. Loading an
                    // executable image involves allocating memory,
                    // which goes through this object. So, before
                    // attempting the load, mark ourselves as initialized.
                    //
                    // ---- DON'T PUT GENUINE INITIALIZATION
                    //      CODE BELOW HERE! ---------
                    this.initialized = true;

                    // Load our own, protection-domain-private copy of the
                    // ABI stubs. These will get shared by all apps in
                    // this domain.
                    IoMemory syscallsMemory = Binder.LoadRawImage("/init", "syscalls.dll");
                    IoMemory loadedMemory;

                    // Load the stubs library into the user range, but make
                    // the kernel process the logical owner. This seems like
                    // the only sensible approach since the stubs do not
                    // belong to any particular process but must be in the
                    // user range of memory.

                    // N.B.: RE-ENTERS this object!
                    ring3AbiImage = PEImage.Load(Process.kernelProcess, syscallsMemory,
                                                 out loadedMemory,
                                                 false, // isForMp
                                                 false // inKernelSpace
                                                 );

                    ring3AbiExports = ring3AbiImage.GetExportTable(loadedMemory);
#if VERBOSE
                    DebugStub.WriteLine("  ...Loaded ring-3 ABI stubs");
#endif
                }
#else // PAGING
                this.initialized = true;
#endif // PAGING

            }
            finally {
                DebugStub.Assert(this.initialized);
                initSpin.Unlock(iflag);
            }
        }

        internal string Name {
            get {
                return this.name;
            }
        }

        //
        // Accessors below are only usable after InitHook()
        //
        internal VirtualMemoryRange UserRange {
            get {
                DebugStub.Assert(initialized);
                return userRange;
            }
        }

#if PAGING
        internal SharedHeap UserSharedHeap {
            [Inline]
            get {
                DebugStub.Assert(initialized);
                return userSharedHeap;
            }
        }

        internal SharedHeap.AllocationOwnerId DataOwnerId
        {
            get {
                DebugStub.Assert(initialized);
                return UserSharedHeap.DataOwnerId;
            }
        }

        internal SharedHeap.AllocationOwnerId EndpointOwnerId
        {
            get {
                DebugStub.Assert(initialized);
                return UserSharedHeap.EndpointOwnerId;
            }
        }

        internal SharedHeap.AllocationOwnerId EndpointPeerOwnerId
        {
            get {
                DebugStub.Assert(initialized);
                return UserSharedHeap.EndpointPeerOwnerId;
            }
        }
#endif

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

        private static ProtectionDomain CreateProtectionDomain(
            string name, bool isKernelDomain)
        {
            ProtectionDomain dp = new ProtectionDomain(name, isKernelDomain);
            dp.id = AssignNewPDSlot(dp);
            return dp;
        }

        private ProtectionDomain(string name, bool isKernelDomain)
            : this(VMManager.CreateNewAddressSpace(), name, isKernelDomain)
        {
        }

        private ProtectionDomain(AddressSpace space, string name, bool isKernelDomain)
        {
            this.space = space;
            this.name = name;
            this.kernelMode = isKernelDomain;
            this.initSpin = new SmartSpinlock(SpinLock.Types.ProtectionDomainInit);
            this.userMappingLock = new SmartSpinlock(SpinLock.Types.ProtectionDomainMapping);
            this.refCount = 1; // represents the table entry
#if VERBOSE
            DebugStub.WriteLine("Created protection domain \"{0}\"", __arglist(name));
#endif
        }

        private static uint AssignNewPDSlot(ProtectionDomain pd)
        {
            // Consider; Assert tableLock is aquired?

            for (uint i = 0; i < PdTable.Length; i++) {
                uint index = (uint)((PdIndexGenerator + i) % PdTable.Length);

                if (PdTable[index] == null) {
                    PdTable[index] = pd;
                    PdIndexGenerator = (uint)((index + 1) % PdTable.Length);
                    return index;
                }
            }

            DebugStub.Assert(false, "Out of process domain slots!");
            return uint.MaxValue;
        }
    }
}
