// ----------------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ----------------------------------------------------------------------------

using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Singularity.Isal;
using Microsoft.Singularity.Hal;
using Microsoft.Singularity.Io;

namespace Microsoft.Singularity.Hal
{
    //
    // Platform is the basic abstraction of the HAL execution platform
    //
    // Part of the platform consts
    //
    [CLSCompliant(false)]
    [AccessedByRuntime("referenced in c++", AllFields = true)]
    [StructLayout(LayoutKind.Sequential, Pack=4)]
    public class Platform
    {
        ///////////////////////////////////////////////////////////////////////
        // Constants: these remain the same on all platforms.
        ///////////////////////////////////////////////////////////////////////

        // TODO: Fix case of constants to camel case

        [AccessedByRuntime("referenced in c++")]
        // 4KB is currently the only supported size
        internal const uint PAGE_SIZE           = 0x00001000; // 4KB

        // This is the ABI Stub (MpSyscalls.x86)
        [AccessedByRuntime("referenced in c++")]
        internal const uint MP_ABI_BASE         = 0x00600000;

        //
        // These constants control the gross layout of *virtual*
        // memory
        //

        // The physical address and extent of the high-memory
        // range to map into the Kernel space, and mark
        // "uncached". This window is for communicating with
        // hardware.
        [AccessedByRuntime("referenced in c++")]
        public const uint UNCACHED_PHYSICAL     = 0xFE000000;
        [AccessedByRuntime("referenced in c++")]
        public const uint UNCACHED_MAPPED       = 0x02000000;

        // This is the amount of *contiguous, physical* memory
        // to reserve at boot for use as I/O memory
        public const uint IO_MEMORY_SIZE        = 0x00800000; // 8MB

        // This determines where the kernel/user boundary is.
        // Currently, it needs to be multiple of 1GB.
        [AccessedByRuntime("referenced in c++")]
        internal const uint KERNEL_BOUNDARY     = 0x40000000; // 1GB

        // This determines the maximum virtual address
        // we will use. Setting this to less than the
        // machine's maximum pointer size can reduce the
        // overhead of paging structures.
        //
        // NOTE: we are not currently safe to use the top
        // bit of addresses (because of the "mark" bit in the
        // multi-use word header), so restrict ourselves to the
        // bottom 2GB.
        [AccessedByRuntime("referenced in c++")]
        public const uint MAX_VIRTUAL_ADDR      = 0x80000000; // 2GB

        // Exit Codes:
        [AccessedByRuntime("referenced in c++")]
        internal const int EXIT_AND_RESTART     = 0x1fff;
        [AccessedByRuntime("referenced in c++")]
        internal const int EXIT_AND_SHUTDOWN    = 0x1ffe;
        [AccessedByRuntime("referenced in c++")]
        internal const int EXIT_AND_WARMBOOT    = 0x1ffd;
        [AccessedByRuntime("referenced in c++")]
        internal const int EXIT_AND_HALT        = 0x1ffc;

        ///////////////////////////////////////////////////////////////////////
        // Boot configuration information: this is all filled out by the loader
        ///////////////////////////////////////////////////////////////////////

        // Size of instance; this is a versioning/consistency check
        [AccessedByRuntime("referenced in c++")]
        public readonly int         Size;

#if ISA_IX64

        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     Dummy;

#endif

        [AccessedByRuntime("referenced in c++")]
        public readonly uint        BootYear;
        [AccessedByRuntime("referenced in c++")]
        public readonly uint        BootMonth;
        [AccessedByRuntime("referenced in c++")]
        public readonly uint        BootDay;
        [AccessedByRuntime("referenced in c++")]
        public readonly uint        BootHour;
        [AccessedByRuntime("referenced in c++")]
        public readonly uint        BootMinute;
        [AccessedByRuntime("referenced in c++")]
        public readonly uint        BootSecond;

        // Max # of CPUs allowed. !!! is this a long term thing? Should it be a constant?
        [AccessedByRuntime("referenced in c++")]
        public readonly int         CpuMaxCount;
        [AccessedByRuntime("referenced in c++")]
        public readonly int         CpuRealCount;

        // Warm boot count (0 = cold boot, 1 = first warm boot, etc.)
        [AccessedByRuntime("referenced in c++")]
        public readonly int         BootCount;

        // Description of memory
        // @todo: this is a simple description of devices in the physical address space.
        // Eventually we want a richer way for HAL to communicate the memory layout to
        // S, rather than having S make assumptions about where HAL is and isn't using memory.
        [AccessedByRuntime("referenced in c++")]
        // Field must be integral for 16-bit boot code
        public readonly UIntPtr     Smap32;

        public unsafe Microsoft.Singularity.SMAPINFO *Smap {
            [NoHeapAllocation]
                get {
                return (Microsoft.Singularity.SMAPINFO *) Smap32;
            }
        }

        [AccessedByRuntime("referenced in c++")]
        public readonly int         SmapCount;

        // Lowest address which should be accessed as physical memory
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     PhysicalBase;

        //For now I'm just doing a single communication channel from subservient kernel
        //to the BSP kernel.
        [AccessedByRuntime("referenced in c++")]
        unsafe public readonly uint*OutgoingMessage;

        [AccessedByRuntime("referenced in c++")]
        unsafe public readonly int* OutgoingCount;

        [AccessedByRuntime("referenced in c++")]
        unsafe public readonly uint*IncomingFree;       //previously sent messages that cane be safely freed

        [AccessedByRuntime("referenced in c++")]
        unsafe public readonly int* IncomingFreeCount;

        [AccessedByRuntime("referenced in c++")]
        unsafe public readonly uint*IncomingMessage;    //messages bound for this kernel

        [AccessedByRuntime("referenced in c++")]
        unsafe public readonly int* IncomingCount;

        [AccessedByRuntime("referenced in c++")]
        unsafe public readonly uint*OutgoingFree;       // messages that this kernel has processed

        [AccessedByRuntime("referenced in c++")]
        unsafe public readonly int* OutgoingFreeCount;

        [AccessedByRuntime("referenced in c++")]
        public readonly uint        MaxBufferLength;

        [AccessedByRuntime("referenced in c++")]
        public readonly  uint       thisDestinationId;

        [AccessedByRuntime("referenced in c++")]
        public readonly  uint       hasApic;


        public int ApicId {
            [NoHeapAllocation]
                get {
#if ISA_IX
                if(hasApic == 1) {
                    // THIS SHOULD GO AWAY. WE SHOULD:
                    //  - 1. CHECK HEAP ALLOCATOR INITIALIZED AND PROCESSOR TABLE
                    //    - YES, return info from processor
                    //    - NO, return 0
                    uint p0, p1, p2, p3;
                    Isa.ReadCpuid((uint)1, out p0, out p1, out p2, out p3);
                    return (int)((p1 & 0xFF000000) >> 24);
                }

                return 0;
#elif ISA_ARM
                return 0;
#else
#error "E_UNSUPPORTED_PLATFORM"
#endif
            }
        }

        //
        // This represents "boot memory" in which
        // managed structures are allocated before the
        // GC and memory manager is initialized. These
        // pages must be marked early in MM initialization
        // so that they are known as nonGC pages.
        //
        // It is OK for this to be null if a given platform
        // allocates these classes in memory that is otherwise
        // accounted for.
        //
        // Examples are the Platform class, and each
        // processor class.
        //
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     BootAllocatedMemory;

        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     BootAllocatedMemorySize;

        // Command line (if any) given to HAL launcher.
        // !!! either generalize this or distill it to specific options.
        [AccessedByRuntime("referenced in c++")]
        // Field must be integral for 16-bit boot code
        public readonly UIntPtr     CommandLine32;
        public unsafe char *        CommandLine {
            [NoHeapAllocation]
                get {
                return (char *) CommandLine32;
            }
        }

        [AccessedByRuntime("referenced in c++")]
        public readonly int         CommandLineCount;

        // CpuContext record is stored off of FS at the value stored at the following offset
        // of fs.  A platform is guaranteed to have 2 pointers available at this offset.
        [AccessedByRuntime("referenced in c++")]
        public readonly unsafe int  CpuRecordPointerOffset;
        [AccessedByRuntime("referenced in c++")]
        public readonly unsafe int  ThreadRecordPointerOffset;

        // Various logging buffers
        // !!! Move these out of HAL
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     LogRecordBuffer;
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     LogRecordSize;
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     LogTextBuffer;
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     LogTextSize;

        // Kernel Dll (kernel.x86) where S execution will begin;
        // this is reported to S for use in debugging infrastructure
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     KernelDllBase;
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     KernelDllSize;
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     KernelDllFirstPage; // first page may be disjoint on CE

        // Initial minidump file address (if any)
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     MiniDumpBase;
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     MiniDumpLimit;

        // TODO: Remove DebuggerType; replace with BOOL and always go through HAL

        // Debugger type to attach to
        [AccessedByRuntime("referenced in c++")]
        public readonly int         DebuggerType;
        // Debugger type enumeration
        [AccessedByRuntime("referenced in c++")]
        public const int            DEBUGGER_NONE       = 0;
        [AccessedByRuntime("referenced in c++")]
        public const int            DEBUGGER_SERIAL     = 1;
        [AccessedByRuntime("referenced in c++")]
        public const int            DEBUGGER_1394       = 2;

        // EntryPoint return values
        [AccessedByRuntime("referenced in c++")]
        public const int            SUCCESS = 0;
        [AccessedByRuntime("referenced in c++")]
        public const int            ERROR_BAD_SIZE       = 1;
        [AccessedByRuntime("referenced in c++")]
        public const int            ERROR_BAD_PLATFORM   = 2;

        ///////////////////////////////////////////////////////////////////////
        // Platform abstractions - these are filled in by the HAL during kernel
        // initialization
        ///////////////////////////////////////////////////////////////////////

        [AccessedByRuntime("referenced in c++")]
        public static Platform      thePlatform;

        [AccessedByRuntime("referenced in c++")]
        protected Cpu               bootCpu;

        public static Platform ThePlatform
        {
            [Inline]
                [NoHeapAllocation]
                get {
                return thePlatform;
            }
        }

        public static Cpu BootCpu
        {
            [Inline]
                [NoHeapAllocation]
                get {
                return thePlatform.bootCpu;
            }
        }

        public static Cpu CurrentCpu
        {
            [Inline]
                [NoHeapAllocation]
                get {
                return GetCurrentCpu();
            }
        }

        [AccessedByRuntime("accessed by C++")]
        [Inline]
        [NoHeapAllocation]
        public static Cpu Cpu(int id)
        {
            return thePlatform.GetCpu(id);
        }

        // Returns the current processor.  Note that in general you need to
        // have preemption disabled for this to return a meaningful value.

        [AccessedByRuntime("defined in hal.cpp")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        [StackBound(32)]
        public static extern Cpu GetCurrentCpu();

        // Platform is initially configured before Kernel.Main.  At this point
        // it is a minimal shell containing the boot records and the boot processor
        // information.  Services are not available until InitializeServices is
        // called later (which requires the object heap to be set up.)

        // called before boot; cannot allocate memory
        [NoHeapAllocation]
        [AccessedByRuntime("referenced in c++")]
        public unsafe void Initialize(Cpu bootCpu)
        {
            this.bootCpu = bootCpu;
            thePlatform = this;
        }

        /// Platform

#region BootInformation

        // Sanity Check
        public uint     RecSize;

        // Debug Stub Information
#if ISA_IX
        public ushort   DebugBasePort;
#elif ISA_ARM
        public uint     DebugBasePort;
#endif
        public ushort   DebugSpinBase;
        public uint     TwiddleSpinBase;

        // Self-descriptive information
        public ulong    Info32;
        public ulong    Kill32;
        public uint     KillAction;

        // Location (in high memory) of the executable images
        public ulong    DumpAddr32;

        // File image table ???
        public ulong    FileImageTableBase32;
        public uint     FileImageTableEntries;

        // Extent of that data
        public uint     DumpSize32;

        public ulong    DumpRemainder;

        // Marks the highest address used by the kernel image (undumped from high memory)
        public ulong    DumpLimit;
        public ulong    NativeContext;
        public ulong    Cpus;

#if ISA_IX64
        public ulong    Ppml432; // pointer to Page Map Level-4
        public ulong    AddrTss; // pointer to the 64-bit TSS containing the IST stack table
#endif

        // IDT and PIC
        public ushort   BiosPicMask;
        public byte     BiosWarmResetCmos;
        public uint     BiosWarmResetVector;
        public uint     Info16;

        // Temporary IDT
        public ulong    IdtEnter0;
        public ulong    IdtEnter1;
        public ulong    IdtEnterN;
        public ulong    IdtTarget;

        public ulong    Pdpt32;

        //
        // This is the kernel's operating map that secondary processors
        // must use. It may be updated by the kernel during the CPU 0
        // initialization process.
        //
        // Initially set to the same value as Pdpt32 by singboot when
        // CPU 0 is initialized.
        //
        public ulong    KernelPdpt64;

        public ulong    Undump;

        // PCI Information (V2.0+)
        public uint     PciBiosAX;
        public uint     PciBiosBX;
        public uint     PciBiosCX;
        public uint     PciBiosEDX;

        // BIOS Information
        public ulong    AcpiRoot32;
        public ulong    PnpNodesAddr32;
        public uint     PnpNodesSize32;
        public ulong    SmbiosRoot32;
        public ulong    DmiRoot32;
        public uint     IsaCsns;
        public ushort   IsaReadPort;
        public uint     Ebda32;
        public uint     MpFloat32;

        // 1394 Information
        public ulong    Ohci1394Base;
        public ulong    Ohci1394BufferAddr32;
        public uint     Ohci1394BufferSize32;

        // VESA Information
        public ulong    VesaBuffer;

        // MP specific variables
        public ulong    MpEnter32;          // Entry point
        public uint     MpCpuCount;         // No of AP's booted
        public uint     MpStatus32;         // Error indicator
        public ulong    MpStartupLock32;    // Pointer to MP init lock var

        public Microsoft.Singularity.MpBootInfo   MpBootInfo;

#endregion // BootInformation

        //////////////////////////////////////////////////////////////////////
        // Kernel fields initialized by early kernel code
        //////////////////////////////////////////////////////////////////////

        private Cpu[]   processors;

        private HalDevices devices;

        [NoHeapAllocation]
        public void RegisterCpu(Cpu p)
        {
            DebugStub.Assert(processors != null);
            DebugStub.Assert(p.Id >= 0);
            DebugStub.Assert(p.Id < processors.Length);
            DebugStub.Assert((processors[p.Id] == null) || (processors[p.Id] == p));
            processors[p.Id] = p;
        }

        // This routine is used to report GC objects which are manufactured by the
        // HAL, in order to trace their reference fields.  (Note that such objects
        // must also end up on pages labelled NonGC for tracing, etc to work correctly.)

        internal void VisitSpecialData(System.GCs.ReferenceVisitor visitor)
        {
            // The Platform object and the processor objects are all allocated
            // on fixed pages, so must be explicitly traced.

            visitor.VisitReferenceFields(thePlatform);

            foreach (Cpu p in processors) {
                if (p != null) {
                    visitor.VisitReferenceFields(p);
                }
            }
        }

        [NoHeapAllocation]
        public bool DisableInterrupts()
        {
            return Isa.DisableInterrupts();
        }

        [NoHeapAllocation]
        public void EnableInterrupts()
        {
            Isa.EnableInterrupts();
        }

        [NoHeapAllocation]
        public  bool AreInterruptsDisabled()
        {
            return Isa.AreInterruptsDisabled();
        }

        // Halt causes the current CPU to cease execution until an interrupt occurs.

        [NoHeapAllocation]
        public void Halt()
        {
            // @BUG this is broken - we can migrate to another CPU
            CurrentCpu.halted = true;
            Isa.Halt();
        }

        // RendezvousCpus sends a rendezvous to the other processors.

        [NoHeapAllocation]
        public void RendezvousCpus()
        {
            // Not implemented
        }

        // Kill exits the platform environment.

        [AccessedByRuntime("referenced in c++")]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [StackBound(64)]
        [NoHeapAllocation]
        private static extern void NativeKill(int exitCode);

        [NoHeapAllocation]
        public void Kill(int exitCode)
        {
            NativeKill(exitCode);
        }

        // TBD: there is currently no standard processor array, it is maintained by the HAL.
        // (Not sure if this is valuable.)
        [NoHeapAllocation]
        public Cpu GetCpu(int i)
        {
            return processors[i];
        }

        // This will cause the CPUs to get enabled; each will have EntryPoint called
        public void EnableMoreCpus(int cpus)
        {
            //
            // Currently a separate code path in kernel.cs calls the HalDevices.StartApProcessors()
            // due to layering issues with the HalPic's in the kernel, and the HAL being below
            // the kernel not allowed to call up into the kernel. Eventually when these
            // devices move into the HAL (and PNP resource assignment issues are resolved) this
            // code will call StartAppProcessors(cpus).
            //
        }

#region Debugging
        [NoHeapAllocation]
        public void FreezeAllCpus()
        {
            devices.FreezeProcessors();
        }
#endregion // debugging

#region Timer
        // Request that an interrupt fire immediately on the given processor.
        // Note that this may be called on other CPUs to facilitate IPC.

        [NoHeapAllocation]
        public void WakeNow(int cpu)
        {
            int fromCpu = Microsoft.Singularity.Processor.GetCurrentProcessorId();
            devices.SendFixedIPI((byte)Kernel.HalIpiInterrupt, fromCpu, cpu);
        }
#endregion // Timer

        // This is called to fully initialize the platform
        public void InitializeServices()
        {
            processors = new Cpu[CpuMaxCount];
            processors[0] = bootCpu;
            bootCpu.InitializeServices();

            // TBD
        }

#region HAL
        // Get our HalDevices interface from the externally implemented HAL
        public static void InitializeHal(Processor processor)
        {
            thePlatform.devices = (HalDevices)HalDevicesFactory.Initialize(processor);
        }

        public static void ReleaseResources()
        {
            thePlatform.devices.ReleaseResources();
        }

        //
        // Adding and removing interrupts from the Pic.
        //
        [NoHeapAllocation]
        public static void EnableIoInterrupt(byte irq)
        {
            thePlatform.devices.EnableIoInterrupt(irq);
        }

        [NoHeapAllocation]
        public static void DisableIoInterrupt(byte irq)
        {
            thePlatform.devices.DisableIoInterrupt(irq);
        }

        [NoHeapAllocation]
        public static bool InternalInterrupt(byte interrupt)
        {
            return thePlatform.devices.InternalInterrupt(interrupt);
        }

        [NoHeapAllocation]
        public static byte GetMaximumIrq()
        {
            return thePlatform.devices.GetMaximumIrq();
        }

        [NoHeapAllocation]
        public static int GetProcessorCount()
        {
            return thePlatform.devices.GetProcessorCount();
        }

        public static void StartApProcessors(int cpus)
        {
            thePlatform.devices.StartApProcessors(cpus);
        }

        [NoHeapAllocation]
        public static void ResetApProcessors()
        {
            thePlatform.devices.ResetApProcessors();
        }

        [NoHeapAllocation]
        public static void FreezeProcessors()
        {
            thePlatform.devices.FreezeProcessors();
        }

        // send fixed interrupt
        [NoHeapAllocation]
        public static void SendFixedIPI(byte vector, int from, int to)
        {
            thePlatform.devices.SendFixedIPI(vector, from, to);
        }

        [NoHeapAllocation]
        public static void BroadcastFixedIPI(byte vector, bool includeSelf)
        {
            thePlatform.devices.BroadcastFixedIPI(vector, includeSelf);
        }

        [NoHeapAllocation]
        public static void ClearFixedIPI(int interrupt)
        {
            thePlatform.devices.ClearFixedIPI(interrupt);
        }

        public static byte TranslatePciInterrupt(byte currentInterruptLine,
                                                 byte pciInterruptPin,
                                                 PciPort pciPort)
        {
            return thePlatform.devices.TranslatePciInterrupt(currentInterruptLine,
                                                             pciInterruptPin,
                                                             pciPort);
        }
#endregion // HAL

    }
}

