// ----------------------------------------------------------------------------
//
//  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;

#if ISA_IX
using Microsoft.Singularity.Isal.IX;
#endif

namespace Microsoft.Singularity.Hal
{
    //
    // Cpu contains per-CPU data structures that HAL sets up.
    //

    [AccessedByRuntime("referenced in c++")]
    [CLSCompliant(false)]
    [StructLayout(LayoutKind.Sequential, Pack=4)]
    public class Cpu
    {
        /////////////////////////////////////////////////////////////////////////////////////
        // Boot configuration information: this is all filled out by the loader or another CPU
        //////////////////////////////////////////////////////////////////////////////////

        public Cpu()
        {
        }

        protected Cpu(int id, UIntPtr stackLimit, UIntPtr stackBegin)
        {
            Size = 0;
            Id = id;
            KernelStackLimit = stackLimit;
            KernelStackBegin = stackBegin;
        }

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

        // Cpu index
        [AccessedByRuntime("referenced in c++")]
        public readonly int        Id;

        // The HAL is responsible for allocating a page for processor-specific
        // data structures.  This is the address of that page. (Note it must be paragraph aligned.)
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     CpuRecordPage;

        // The HAL is responsible for allocating the kernel stack.  The boundaries
        // are reported here.
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     KernelStackLimit; // lower bounds of stack
        [AccessedByRuntime("referenced in c++")]
        public readonly UIntPtr     KernelStackBegin; // upper bounds of stack

        //Is 0 or 1...
        [AccessedByRuntime("referenced in c++")]
        public readonly int         DomainBsp;

        ///// NaticeCpu
#if ISA_IX
        // This is our segment table.
        [AccessedByRuntime("referenced in c++")]
        DescriptorTable                     segments;
#endif

        internal bool halted;

        ///////////////////////////////////////////////////////////////////////
        // Kernel information: initialized by kernel during early boot
        ///////////////////////////////////////////////////////////////////////

        // Low level initialization (no allocation allowed)
        [NoHeapAllocation]
        [AccessedByRuntime("referenced in c++")]
        public unsafe void Initialize()
        {
            if (DomainBsp == 0) {
                Platform.ThePlatform.RegisterCpu(this);
            }

            halted = false;
        }

        // High level intialization (requires object allocation)
        public void InitializeServices()
        {
        }

        // Called from the ISA Target upon interrupt dispatch.
        [NoHeapAllocation]
        public unsafe void DispatchInterrupt(InterruptContext *context)
        {
            Processor p = Processor.CurrentProcessor;
            int interrupt = context->ExceptionId;

            // Indicate that we are in an interrupt context.
            Thread target = null;
            Thread current = Processor.GetCurrentThread();

            Kernel.Waypoint(801);

            // Don't generate loads of output for debugger-related interrupts
#if DEBUG_INTERRUPTS
            DebugStub.WriteLine("Int{0:x2}", __arglist(interrupt));
            context->Display();
#endif

            if (Processor.IsSamplingEnabled) {
                if (p.nextSampleIdle == false) {
                    p.Profiler.LogStackTrace(context->InstructionPointer,
                                             context->StackPointer);
                }
                p.nextSampleIdle = false;
            }

            if (halted) {
                p.clock.CpuResumeFromHaltEvent();
                halted = false;
            }

            unchecked {
                if (interrupt != p.clockInterrupt &&
                    interrupt != p.timerInterrupt) {
                    // We don't log the clockInterrupt because of all the spew.
                    Tracing.Log(Tracing.Debug, "Interrupt 0x{0:x}, count={1:x}, eip={2:x} [CC={3:x8}]",
                                (UIntPtr)(uint)interrupt,
                                (UIntPtr)p.interruptCounts[interrupt],
                                (UIntPtr)context->InstructionPointer,
                                (UIntPtr)(uint)Processor.CycleCount);
                }
            }

            Monitoring.Log(Monitoring.Provider.Processor,
                           (ushort)ProcessorEvent.Interrupt, 0,
                           (uint)interrupt, 0, 0, 0, 0);

            if (interrupt == Kernel.HalIpiInterrupt) {
#if DEBUG_IPI
                DebugStub.WriteLine("IPI received 0x{0:x2} on processor {1}",
                                    __arglist(interrupt, p.Id));
#endif // DEBUG_DISPATCH_TIMER
                Platform.ClearFixedIPI(interrupt);
                //p.dispatcher.HandlePreemptionReschedule();

            }
            else if (interrupt == p.timerInterrupt) {
                // Polling on every timer interrupt is EXCEEDINGLY costly
                p.timer.ClearInterrupt();
                p.dispatcher.HandlePreemptionReschedule(p.timer);
            }
            else if (interrupt == p.clockInterrupt) {

                p.clock.ClearInterrupt();
#if DEBUG
                // Check for a debug break.
                if (DebugStub.PollForBreak()) {
                    DebugStub.WriteLine("Debugger ctrl-break after interrupt 0x{0:x2}",
                                        __arglist(interrupt));
                    DebugStub.Break();
                }
#endif // DEBUG
            }
#if ISA_IX
            else if (interrupt == EVectors.GCSynchronization) {

                Platform.ClearFixedIPI(interrupt);

                MpExecution.GCSynchronizationInterrupt();
            }
            else if (interrupt == EVectors.SpuriousInterrupt) {

                // FIXME: identify the source of these isolated interrupts after the
                // warmboot. Ignore them for now.
                DebugStub.WriteLine("Spurious interrupt");
            }
#endif // ISA_IX
            else {
                if (!Platform.InternalInterrupt((byte)interrupt)) {
                    HalPic pic = p.GetPic();

                    DebugStub.Assert(pic != null);

                    pic.ClearInterrupt((byte)interrupt);
                    IoIrq.SignalInterrupt(pic.InterruptToIrq((byte)interrupt));

#if DEBUG_DISPATCH_IO
                    DebugStub.WriteLine("++DispatchInterruptEvent Irq={0:x2}, Thread={1:x8}",
                                        __arglist(pic.InterruptToIrq((byte)interrupt),
                                                  Kernel.AddressOf(target)));
#endif // DEBUG_DISPATCH_IO

                    p.dispatcher.HandleIOReschedule();
                }
                else {
                    // Potentially missed interrupt
                    DebugStub.Break();
                }
            }

#if DEBUG_INTERRUPTS
            DebugStub.WriteLine("(2nd)Int{0:x2}", __arglist(interrupt));
            context->Display();
            if (!Processor.InterruptsDisabled()) {
                DebugStub.WriteLine("        interrupts enabled!!!!!!!");
                DebugStub.Break();
            }
#if DEBUG_DEEPER
            DebugStub.WriteLine("Int{0:x2}", __arglist(interrupt));
            Thread.DisplayAbbrev(ref context, " int end");
#endif
#endif

            // Now swap in the resulting current thread. (Note that this call will not return.)
            Isa.GetCurrentThread()->spill.Resume();
        }
    }
}

