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

#define DEBUG_INTERRUPTS
#define DEBUG_DISPATCH_IO
#define PIC_DEBUG

using Microsoft.Singularity.Io;

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Singularity.Configuration;
using Microsoft.Singularity.Isal.Arm;

namespace Microsoft.Singularity.Hal
{
    // declare resources for the kernel manifest
    [DriverCategory]
    [Signature("/arm/ti/3430/INTCPS")]
    public sealed class PicResources : DriverCategoryDeclaration
    {
        [IoMemoryRange(0, Default = 0x48200000, Length = 0x1000)]
        internal readonly IoMemoryRange registers;

        // CTR will create the rest of this class:
        public PicResources(IoConfig config)
        {
            // dynamic resources
            registers = (IoMemoryRange)config.DynamicRanges[0];
        }
    }

    [CLSCompliant(false)]
    public sealed class Pic : HalPic
    {
        // size of register block
        const int PicRegisterSize          = 0x00000280; // size of registers in block

        // registers
        const uint INTCPS_REVISION         = 0x00000000; // INTCPS block revision
        const uint INTCPS_SYSCONFIG        = 0x00000010; // configuration
        const uint INTCPS_SYSSTATUS        = 0x00000014; // status
        const uint INTCPS_SIR_IRQ          = 0x00000040; // IRQ source interrupt
        const uint INTCPS_SIR_FIQ          = 0x00000044; // FIQ source interrupt
        const uint INTCPS_CONTROL          = 0x00000048; // control
        const uint INTCPS_PROTECTION       = 0x0000004c; // pic access control
        const uint INTCPS_IDLE             = 0x00000050; // clock idling
        const uint INTCPS_IRQ_PRIORITY     = 0x00000060; // IRQ priority level
        const uint INTCPS_FIQ_PRIORITY     = 0x00000064; // FIQ priority level
        const uint INTCPS_THRESHHOLD       = 0x00000068; // priority threshold control
        const uint INTCPS_ITR              = 0x00000080; // raw interrupt status vector
        const uint INTCPS_MIR              = 0x00000084; // interrupt mask vector
        const uint INTCPS_MIR_CLEAR        = 0x00000088; // clear interrupt mask vector
        const uint INTCPS_MIR_SET          = 0x0000008c; // set interrupt mask vector
        const uint INTCPS_ISR_SET          = 0x00000090; // assert software interrupt vector
        const uint INTCPS_ISR_CLEAR        = 0x00000094; // clear software interrupt vector
        const uint INTCPS_PENDING_IRQ      = 0x00000098; // pending (masked) IRQ interrupt vector
        const uint INTCPS_PENDING_FIQ      = 0x0000009c; // pending (masked) FIQ interrupt vector
        const uint INTCPS_ILR_Base         = 0x00000100; // interrupt priority & fast/slow control
        const uint INTCPS_ILR_Offset       = 0x00000004; // interrupt priority & fast/slow control

        // source interrupt fields
        const uint INTCPS_SIR_IRQ_ACTIVEIRQ_Mask   = 0x0000007f; // active IRQ number
        const uint INTCPS_SIR_FIQ_ACTIVEFIQ_Mask   = 0x0000007f; // active FIQ number

        // interrupt priority fields
        const uint INTCPS_IRQ_PRIORITY_Mask     = 0x0000003f; // current irq priority
        const uint INTCPS_FIQ_PRIORITY_Mask     = 0x0000003f; // current fiq priority

        // interrupt level/routing fields
        const uint INTCPS_ILR_FIQNIRQ           = 0x00000001; // FIQ/IRQ routing
        const uint INTCPS_ILR_PRIORITY_Mask     = 0x0000000d; // interrupt priority
        const int INTCPS_ILR_PRIORITY_Shift     = 0x00000002; // interrupt priority

        // registers
        private IoMappedPort intcps_revision;       // INTCPS block revision
        private IoMappedPort intcps_sysconfig;      // configuration
        private IoMappedPort intcps_sysstatus;      // status
        private IoMappedPort intcps_sir_irq;        // IRQ source interrupt
        private IoMappedPort intcps_sir_fiq;        // FIQ source interrupt
        private IoMappedPort intcps_control;        // control
        private IoMappedPort intcps_protection;     // pic access control
        private IoMappedPort intcps_idle;           // clock idling
        private IoMappedPort intcps_irq_priority;   // IRQ priority level
        private IoMappedPort intcps_fiq_priority;   // FIQ priority level
        private IoMappedPort intcps_threshhold;     // priority threshold control
        private IoMappedPort[] intcps_itr;          // raw interrupt status vector
        private IoMappedPort[] intcps_mir;          // interrupt mask vector
        private IoMappedPort[] intcps_mir_clear;    // clear interrupt mask vector
        private IoMappedPort[] intcps_mir_set;      // set interrupt mask vector
        private IoMappedPort[] intcps_isr_set;      // assert software interrupt vector
        private IoMappedPort[] intcps_isr_clear;    // clear software interrupt vector
        private IoMappedPort[] intcps_pending_irq;  // pending (masked) IRQ interrupt vector
        private IoMappedPort[] intcps_pending_fiq;  // pending (masked) FIQ interrupt vector
        private IoMappedPort intcps_ilr;            // interrupt level/routing

        // constants for PIC interrupt vectors
        const byte INTCPS_Vectors          = 0x60; // count of OMAP3430 interrupt vectors
        const byte INTCPS_Subvector_Size   = 0x20; // size of sub-vectors

        private uint irqMask0; // copy of interrupt mask
        private uint irqMask1; // copy of interrupt mask
        private uint irqMask2; // copy of interrupt mask

        internal Pic(PnpConfig config)
        {
            PicResources pr = new PicResources(config);

            IoMemoryRange range = pr.registers;
            IoMemory regs = range.MemoryAtOffset(0, PicRegisterSize, Access.ReadWrite);

            intcps_revision = regs.MappedPortAtOffset(INTCPS_REVISION, 4, Access.Read);
            intcps_sysconfig = regs.MappedPortAtOffset(INTCPS_SYSCONFIG, 4, Access.ReadWrite);
            intcps_sysstatus = regs.MappedPortAtOffset(INTCPS_SYSSTATUS, 4, Access.Read);
            intcps_sir_irq = regs.MappedPortAtOffset(INTCPS_SIR_IRQ, 4, Access.Read);
            intcps_sir_fiq = regs.MappedPortAtOffset(INTCPS_SIR_FIQ, 4, Access.Read);
            intcps_control = regs.MappedPortAtOffset(INTCPS_CONTROL, 4, Access.ReadWrite);
            intcps_protection = regs.MappedPortAtOffset(INTCPS_PROTECTION, 4, Access.ReadWrite);
            intcps_idle = regs.MappedPortAtOffset(INTCPS_IDLE, 4, Access.ReadWrite);
            intcps_irq_priority = regs.MappedPortAtOffset(INTCPS_IRQ_PRIORITY, 4, Access.ReadWrite);
            intcps_fiq_priority = regs.MappedPortAtOffset(INTCPS_FIQ_PRIORITY, 4, Access.ReadWrite);
            intcps_threshhold = regs.MappedPortAtOffset(INTCPS_THRESHHOLD, 4, Access.ReadWrite);
            intcps_itr = CreatePortVector(regs, INTCPS_ITR, Access.Read);
            intcps_mir = CreatePortVector(regs, INTCPS_MIR, Access.ReadWrite);
            intcps_mir_clear = CreatePortVector(regs, INTCPS_MIR_CLEAR, Access.Write);
            intcps_mir_set = CreatePortVector(regs, INTCPS_MIR_SET, Access.Write);
            intcps_isr_set = CreatePortVector(regs, INTCPS_ISR_SET, Access.ReadWrite);
            intcps_isr_clear = CreatePortVector(regs, INTCPS_ISR_CLEAR, Access.Write);
            intcps_pending_irq = CreatePortVector(regs, INTCPS_PENDING_IRQ, Access.Read);
            intcps_pending_fiq = CreatePortVector(regs, INTCPS_PENDING_FIQ, Access.Read);
            intcps_ilr = regs.MappedPortAtOffset(
                INTCPS_ILR_Base,
                INTCPS_ILR_Offset * INTCPS_Vectors,
                Access.ReadWrite
                );
        }

        private static IoMappedPort[] CreatePortVector(IoMemory regs, uint offset, Access access)
        {
            return new IoMappedPort[3] {
                regs.MappedPortAtOffset(offset + 0x00, 4, access),
                regs.MappedPortAtOffset(offset + 0x20, 4, access),
                regs.MappedPortAtOffset(offset + 0x40, 4, access)
            };
        }

        public void Initialize()
        {
            DebugStub.WriteLine("Pic.Initializing IRQs");

            // mask all interrupts
            MaskAll();

            // clear all pending interrupts
            intcps_isr_clear[0].Write32(0xffffffff);
            intcps_isr_clear[1].Write32(0xffffffff);
            intcps_isr_clear[2].Write32(0xffffffff);
        }

        public void Finalize()
        {
            UnmaskAll();
        }

        [NoHeapAllocation]
        public override byte InterruptToIrq(byte interrupt)
        {
            DebugStub.WriteLine("Pic.InterruptToIrq({0})", __arglist(interrupt));
            // hack - all h/w interrupts on ARM are routed through a common vector
            DebugStub.Assert((interrupt == ExceptionVector.Fiq) ||
                             (interrupt == ExceptionVector.Irq));

            IoResult result = IoResult.Success;

            if (interrupt == ExceptionVector.Fiq) {

                // read currently active FIQ
                ushort sir = intcps_sir_fiq.Read16NoThrow(ref result);
                return (byte)(sir & INTCPS_SIR_FIQ_ACTIVEFIQ_Mask);

            }
            else { // if (interrupt == ExceptionVector.Irq)

                // read currently active IRQ
                ushort sir = intcps_sir_irq.Read16NoThrow(ref result);
                return (byte)(sir & INTCPS_SIR_IRQ_ACTIVEIRQ_Mask);
            }
        }

        [NoHeapAllocation]
        public override byte IrqToInterrupt(byte irq)
        {
            DebugStub.WriteLine("Pic.IrqToInterrupt({0})", __arglist(irq));
            // range check
            DebugStub.Assert(irq < INTCPS_Vectors);

            // check where the interrupt is routed: IRQ or FIQ
            IoResult result = IoResult.Success;
            uint ilr = intcps_ilr.Read32NoThrow(irq * INTCPS_ILR_Offset, ref result);

            if ((ilr & INTCPS_ILR_FIQNIRQ) != 0) {
                return (byte)ExceptionVector.Fiq;
            }
            else {
                return (byte)ExceptionVector.Irq;
            }
        }

        public override byte MaximumIrq
        {
            [NoHeapAllocation]
            get { return INTCPS_Vectors - 1; }
        }

        [System.Diagnostics.Conditional("PIC_DEBUG")]
        [NoHeapAllocation]
        private void DumpBits(String label, IoMappedPort[] regs)
        {
            IoResult result = IoResult.Success;
            uint v0 = regs[0].Read32NoThrow(ref result);
            uint v1 = regs[1].Read32NoThrow(ref result);
            uint v2 = regs[2].Read32NoThrow(ref result);

            DebugStub.WriteLine(label, __arglist(v0, v1, v2));
        }

        [System.Diagnostics.Conditional("PIC_DEBUG")]
        [NoHeapAllocation]
        private void DumpRegisters()
        {
            IoResult result = IoResult.Success;

            ushort currentIrqNumber = (ushort)(intcps_sir_irq.Read16NoThrow(ref result)
                                               & INTCPS_SIR_IRQ_ACTIVEIRQ_Mask);
            ushort currentIrqPriority = (ushort)(intcps_irq_priority.Read16NoThrow(ref result)
                                                 & (ushort)INTCPS_IRQ_PRIORITY_Mask);
            DebugStub.WriteLine("PIC Current IRQ: {0:x4} Priority: {0:x4}",
                                __arglist(currentIrqNumber, currentIrqPriority));

            ushort currentFiqNumber = (ushort)(intcps_sir_fiq.Read16NoThrow(ref result)
                                               & INTCPS_SIR_FIQ_ACTIVEFIQ_Mask);
            ushort currentFiqPriority = (ushort)(intcps_fiq_priority.Read16NoThrow(ref result)
                                                 & INTCPS_FIQ_PRIORITY_Mask);
            DebugStub.WriteLine("PIC Current FIQ: {0:x4} Priority: {0:x4}",
                                __arglist(currentFiqNumber, currentFiqPriority));

            DumpBits("PIC raw interrupt status: {2:x8}{1:x8}{0:x8}", intcps_itr);
            DumpBits("PIC interrupt mask:       {2:x8}{1:x8}{0:x8}", intcps_mir);
            DumpBits("PIC software interrupts:  {2:x8}{1:x8}{0:x8}", intcps_isr_set);
            DumpBits("PIC pending IRQs:         {2:x8}{1:x8}{0:x8}", intcps_pending_irq);
            DumpBits("PIC pending FIQs:         {2:x8}{1:x8}{0:x8}", intcps_pending_fiq);

            for (uint i = 0; i < INTCPS_Vectors; ++i) {

                uint ilr = intcps_ilr.Read32NoThrow(i * INTCPS_ILR_Offset, ref result);

                string interruptType = ((ilr & INTCPS_ILR_FIQNIRQ) != 0) ? "fiq" : "irq";
                byte priority = (byte)((ilr & INTCPS_ILR_PRIORITY_Mask)
                                       >> INTCPS_ILR_PRIORITY_Shift);

                DebugStub.WriteLine("PIC ILR[{0}]: {1} Priority: {2:x2}",
                                    __arglist(i, interruptType, priority));
            }

            DebugStub.Assert(IoResult.Success == result);
        }

        [NoHeapAllocation]
        public override void ClearInterrupt(byte interrupt)
        {
            byte irq = InterruptToIrq(interrupt);

            Mask(irq);
            AckIrq(irq);
            Unmask(irq);

#if DEBUG_INTERRUPTS
            DebugStub.WriteLine("pic.ClearInterrupt({2:x8}{1:x8}{0:x8})",
                                __arglist(irqMask0, irqMask1, irqMask2));
#endif
        }

        [NoHeapAllocation]
        public override void AckIrq(byte irq)
        {
            DebugStub.Assert(Processor.InterruptsDisabled());
#if PIC_DEBUG
            DumpRegisters();
#endif

            // Mark the IRQ as activated and mask it
#if DEBUG_INTERRUPTS
            DebugStub.WriteLine("Int{0:x2}  Acked, Mask={3:x8}{2:x8}{1:x8}",
                                __arglist(irq, irqMask0, irqMask1, irqMask2));
#endif

            // Quiet the interrupt controller.
            IoResult result = IoResult.Success;
            uint n = irq / 32u;
            uint bit = 1u << (irq % 32);
            if (n < intcps_isr_clear.Length) {
                intcps_isr_clear[n].Write32NoThrow(bit, ref result);
            }
            DebugStub.Assert(IoResult.Success == result);

#if PIC_DEBUG
            DumpRegisters();
#endif
        }

        [NoHeapAllocation]
        public override void EnableIrq(byte irq)
        {
            if (irq >= INTCPS_Vectors) {
                DebugStub.Break();
                // throw new OverflowException
                // (String.Format("irq {0} out of range.", irq));
            }

#if DEBUG_INTERRUPTS
            DebugStub.WriteLine("Int{0:x2}  Enable, Mask={3:x8}{2:x8}{1:x8}",
                                __arglist(irq, irqMask0, irqMask1, irqMask2));
#endif

            bool saved = Processor.DisableInterrupts();
            try {
                Unmask(irq);
#if PIC_DEBUG
                DumpRegisters();
#endif
            }
            finally {
                Processor.RestoreInterrupts(saved);
            }
        }

        [NoHeapAllocation]
        public override void DisableIrq(byte irq)
        {
            if (irq >= INTCPS_Vectors) {
                DebugStub.Break();
                //                throw new OverflowException
                // (String.Format("irq {0} out of range.", irq));
            }

#if DEBUG_INTERRUPTS
            DebugStub.WriteLine("Int{0:x2} Disable", __arglist(irq));
#endif
            bool saved = Processor.DisableInterrupts();
            try {
                Mask(irq);
#if PIC_DEBUG
                DumpRegisters();
#endif
            }
            finally {
                Processor.RestoreInterrupts(saved);
            }
        }

        //////////////////////////////////////////////////////////////////////
        //
        //
        [NoHeapAllocation]
        private void Mask(byte irq)
        {
            DebugStub.Assert(Processor.InterruptsDisabled());
            uint n = irq / 32u;
            uint bit = 1u << (irq % 32);
#if DEBUG_DISPATCH_IO
            DebugStub.WriteLine("PIC.Mask({0}) => {1:x8}", __arglist(irq, bit));
#endif

            uint mask = GetMaskWord(n);
            if ((mask & bit) == 0) {

#if DEBUG_DISPATCH_IO
                DebugStub.WriteLine("-- Mask IRQs [{0}: old={1:x8} new={2:x8}",
                                    __arglist(n, mask, mask | bit));
#endif
                IoResult result = IoResult.Success;
                intcps_mir_set[n].Write32NoThrow(bit, ref result);
                DebugStub.Assert(IoResult.Success == result);

                SetMaskWord(n, mask | bit);
#if PIC_DEBUG
                DumpBits("-- PIC interrupt mask:    {2:x8}{1:x8}{0:x8}", intcps_mir);
#endif
            }
        }

        [NoHeapAllocation]
        private void Unmask(byte irq)
        {
            DebugStub.Assert(Processor.InterruptsDisabled());
            uint n = irq / 32u;
            uint bit = 1u << (irq % 32);
#if DEBUG_DISPATCH_IO
            DebugStub.WriteLine("PIC.Unmask({0}) => {1:x8}", __arglist(irq, bit));
#endif

            uint mask = GetMaskWord(n);
            if ((mask & bit) != 0) {

#if DEBUG_DISPATCH_IO
                DebugStub.WriteLine("-- Unmask IRQs [{0}: old={1:x8} new={2:x8}",
                                    __arglist(n, mask, mask & ~bit));
#endif
#if false // Enable this to set interrupt without hardware.
                IoResult result = IoResult.Success;
                intcps_mir_clear[n].Write32NoThrow(bit, ref result);
                intcps_isr_set[n].Write32NoThrow(bit, ref result);
                DebugStub.Assert(IoResult.Success == result);
#endif

                SetMaskWord(n, mask & ~bit);
#if PIC_DEBUG
                DumpBits("-- PIC interrupt mask:    {2:x8}{1:x8}{0:x8}", intcps_mir);
#endif
            }
        }

        [NoHeapAllocation]
        public bool IrqMasked(byte irq)
        {
            uint n = irq / 32u;
            uint bit = 1u << (irq % 32);

            if (irq >= INTCPS_Vectors) {
                return true;
            }
            return ((GetMaskWord(n) & bit) != 0);
        }

        [NoHeapAllocation]
        private uint GetMaskWord(uint n)
        {
            switch (n) {
                case 0: return irqMask0;
                case 1: return irqMask1;
                case 2: return irqMask2;
            }
            return 0;
        }

        [NoHeapAllocation]
        private void SetMaskWord(uint n, uint value)
        {
            switch (n) {
                case 0: irqMask0 = value; break;
                case 1: irqMask1 = value; break;
                case 2: irqMask2 = value; break;
            }
        }

        private void MaskAll()
        {
            DebugStub.Assert(Processor.InterruptsDisabled());

            for (int i = 0; i < intcps_mir_set.Length; i++) {
                intcps_mir_set[i].Write32(0xffffffff);
            }
            irqMask0 = irqMask1 = irqMask2 = 0xffffffff;
        }

        private void UnmaskAll()
        {
            DebugStub.Assert(Processor.InterruptsDisabled());

            for (int i = 0; i < intcps_mir_clear.Length; i++) {
                intcps_mir_clear[i].Write32(0xffffffff);
            }
            irqMask0 = irqMask1 = irqMask2 = 0x0;
        }
    }
}

