////////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   IdeController.cs
//
//  Base Classes and Interfaces for interacting with ATA
//
//  References used:
//  "AT Attachment Interface with Extension (ATA-2)", Revision 4c, DRAFT
//          09/03/97, ISO Working Group T13, http://www.t13.org/
//
//  "AT Attachment 8 - ATA/ATAPI Command Set (ATA8-ACS)", Revision 0, DRAFT
//          08/17/04, ISO Working Group T13, http://www.t13.org/
//

#define DEBUG_SHARED_IRQ

using System;
using System.Collections;
using System.Text;
using System.Threading;
using Microsoft.SingSharp;
using Microsoft.Singularity;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Drivers.IDE;
using Microsoft.Singularity.V1.Services;

namespace Microsoft.Singularity.Drivers.IDE
{
    public enum IdeSet
    {
        IDE_SET_READ_AHEAD = 0,
        IDE_UNSET_READ_AHEAD,
        IDE_SET_MULTIPLE,
        IDE_SET_NO_REVERT,
        IDE_SET_WRITE_CACHE,
        IDE_SET_MAX_PIO,
        IDE_SET_MAX_UDMA,
    };

    [CLSCompliant(false)]
    public class IdeController
    {
        public const int        PollLoopCount = 1000000;
        public string!          ControllerNameInternal;
        private bool            busMasterDmaAvailable;
        private IoPort[]!       CommandBlock;
        private IoPort[]!       ControlBlock;
        private IdeConfig!      ideConfigHandle;
        private String!         instanceName;
        private IdeDiskConfig   ideDisk1Config;
        private IdeDiskConfig   ideDisk2Config;
        private Thread          irqWorker;
        private bool            irqWorkerStop;
        private bool            initialized;
        private volatile bool   commandPending = false;
        private volatile bool   identifyPending = false;
        private byte            pollStatus = 0;
        private int             cyclesPerReadStatus;
        private int             delay400ns;
        public volatile bool    CatchBug;
        public volatile int     CatchCount;

        public string ControllerName
        {
            get { return ControllerNameInternal; }
        }

        public void SetCommandPending(bool val)
        {
            commandPending = val;
            //Tracing.Log(Tracing.Debug," Command pending set to {0}", commandPending ? "true" : "false");
        }

        public IdeConfig GetConfig()
        {
            return ideConfigHandle;
        }

        public bool GetCommandPending()
        {
            //Tracing.Log(Tracing.Debug," Command pending set to {0}", commandPending ? "true" : "false");
            return commandPending;
        }

        public void SetIdentifyInProgress(bool value)
        {
            identifyPending = value;
        }

        public bool GetIdentifyInProgress()
        {
            return identifyPending;
        }

        public ushort ReadDataPort()
        {
            return ((!)CommandBlock[0]).Read16();
        }

        public void WriteDataPort(ushort value)
        {
            ((!)CommandBlock[0]).Write16(value);
        }

        public byte ReadErrorPort()
        {
            return ((!)CommandBlock[1]).Read8();
        }

        public void WriteFeaturesPort(byte value)
        {
            ((!)CommandBlock[1]).Write8(value);
        }

        public byte ReadSectorCountPort()
        {
            return ((!)CommandBlock[2]).Read8();
        }

        public void WriteSectorCountPort(byte value)
        {
            ((!)CommandBlock[2]).Write8(value);
        }

        public byte ReadLBALowPort()
        {
            return ((!)CommandBlock[3]).Read8();
        }
        public void WriteLBALowPort(byte value)
        {
            ((!)CommandBlock[3]).Write8(value);
        }

        public byte ReadLBAMidPort()
        {
            return ((!)CommandBlock[4]).Read8();
        }
        public void WriteLBAMidPort(byte value)
        {
            ((!)CommandBlock[4]).Write8(value);
        }

        public byte ReadLBAHighPort()
        {
            return ((!)CommandBlock[5]).Read8();
        }
        public void WriteLBAHighPort(byte value)
        {
            ((!)CommandBlock[5]).Write8(value);
        }

        public byte ReadDeviceHeadPort()
        {
            return ((!)CommandBlock[6]).Read8();
        }

        public void WriteDeviceHeadPort(byte value)
        {
            ((!)CommandBlock[6]).Write8(value);
        }

        public byte ReadStatusPort()
        {
            return ((!)CommandBlock[7]).Read8();
        }

        public int ReadFullStatus()
        {
            byte nStatus = ReadAlternateStatusPort();
            if ((nStatus & (byte)ATA6.IdeStatus.ERR) > 0) {
                return (int)(nStatus | (((int)ReadErrorPort()) << 8));
            }
            return (int)nStatus;

        }

        public void WriteCommandPort(byte value)
        {
            ((!)CommandBlock[7]).Write8(value);
        }

        public byte ReadAlternateStatusPort()
        {
            return ((!)ControlBlock[2]).Read8();
        }

        public void WriteControlPort(byte value)
        {
            ((!)ControlBlock[2]).Write8(value);
        }

#if false
        public IoPort DataPort;
        public IoPort ErrorPort;
        public IoPort SectorCountPort;
        public IoPort LBAlowPort;
        public IoPort LBAmidPort;
        public IoPort LBAhighPort;
        public IoPort DeviceHeadPort;
        public IoPort StatusPort;
        public IoPort CommandPort;
#endif
        private IdeController(DiskResources! resources, string! instanceName)
        {
            DebugStub.Print("Entering IdeController\n");
            delay400ns = CalculateDelay400nsCount();

            this.ideConfigHandle = new IdeConfig(resources.irq,
                                                 resources.command,
                                                 resources.control,
                                                 true,
                                                 resources.dma,
                                                 this);

            this.instanceName = instanceName;
            IoPort[]! CommandBlock = this.CommandBlock = new IoPort[8];
            IoPort[]! ControlBlock = this.ControlBlock = new IoPort[8];
            ControllerNameInternal = String.Empty;

            IoPortRange commandPorts = resources.command;
            IoPortRange controlPorts = resources.control;

            CommandBlock[0] = commandPorts.PortAtOffset(0, 2, Access.ReadWrite); //Data Port 16bits
            CommandBlock[1] = commandPorts.PortAtOffset(1, 1, Access.ReadWrite); //Error Port
            CommandBlock[2] = commandPorts.PortAtOffset(2, 1, Access.ReadWrite); //SectorCount
            CommandBlock[3] = commandPorts.PortAtOffset(3, 1, Access.ReadWrite); //LBA low
            CommandBlock[4] = commandPorts.PortAtOffset(4, 1, Access.ReadWrite); //LBA mid
            CommandBlock[5] = commandPorts.PortAtOffset(5, 1, Access.ReadWrite); //LBA high
            CommandBlock[6] = commandPorts.PortAtOffset(6, 1, Access.ReadWrite); //DeviceHead
            CommandBlock[7] = commandPorts.PortAtOffset(7, 1, Access.ReadWrite); //Status & command
//
//          DataPort        = commandPorts.PortAtOffset(0, 2, Access.ReadWrite); //Data Port 16bits
//          ErrorPort       = commandPorts.PortAtOffset(1, 1, Access.Read);      //Error Port
//          SectorCountPort = commandPorts.PortAtOffset(2, 1, Access.ReadWrite); //SectorCount
//          LBAlowPort      = commandPorts.PortAtOffset(3, 1, Access.ReadWrite); //LBA low
//          LBAmidPort      = commandPorts.PortAtOffset(4, 1, Access.ReadWrite); //LBA mid
//          LBAhighPort     = commandPorts.PortAtOffset(5, 1, Access.ReadWrite); //LBA high
//          DeviceHeadPort  = commandPorts.PortAtOffset(6, 1, Access.ReadWrite); //DeviceHead
//          StatusPort      = commandPorts.PortAtOffset(7, 1, Access.Read);      //Status
//          CommandPort      commandPorts.PortAtOffset(7, 1, Access.Write);     //Command
//

            ControlBlock[2] = controlPorts.PortAtOffset(2, 2, Access.ReadWrite);
        }

        internal static IdeController! Create(DiskResources! resources, string! instanceName)
        {
            IdeController controller = new IdeController(resources, instanceName);

            controller.ResetController();
            IdeConfig! ideConfigHandle = controller.ideConfigHandle;

            if (ideConfigHandle.UseDma) {
                ideConfigHandle.BusMasterDma = new BusMasterDma(ideConfigHandle);
                controller.busMasterDmaAvailable = true;

                controller.ControllerNameInternal =
                        String.Format("controller at 0x{0:x4},BM@0x{1:x4},IRQ={2}",
                        ideConfigHandle.CommandBase,
                        ideConfigHandle.DmaBase,
                        ideConfigHandle.Interrupt);

                //
                // Set up Interrupt handling
                //
                ideConfigHandle.CommandEndEvent = new AutoResetEvent(false);
                ideConfigHandle.IdentifyEndEvent = new AutoResetEvent(false);


                ideConfigHandle.Irq = (!)ideConfigHandle.Interrupt.IrqAtOffset(0);
                ideConfigHandle.Irq.RegisterInterrupt();

                controller.irqWorker     = new Thread(new ThreadStart(controller.IrqWorkerMain));
                controller.irqWorkerStop = false;
                controller.irqWorker.Start();
                controller.initialized = true;
            }
            else {
                controller.busMasterDmaAvailable = false;
                controller.ControllerNameInternal = String.Format("PIO IDE controller at 0x{0:x4},{1:x4}",
                    ideConfigHandle.CommandBase, ideConfigHandle.ControlBase);
            }
            return controller;
        }

        public void Initialize()
        {
            Tracing.Log(Tracing.Debug,"Ide Controller Bus: initialize");
        }

        public void Finalize()
        {
            try {
                irqWorkerStop = true;
                ideConfigHandle.Irq.Pulse();    // Wake up irqWorker
                irqWorker.Join();
                ideConfigHandle.Irq.ReleaseInterrupt();
            }
            finally {
            }
        }

        public void ResetController()
        {
            bool iflag = PrivilegedGate.DisableInterrupts();
            try {
                // Post the software reset bit
                WriteControlPort(ATA6.DeviceControlSRST);

                // a 5ns delay is needed after issuing the reset and before
                // issuing the next command
                int spin = delay400ns * 30;
                Tracing.Log(Tracing.Debug," spin value for 5 microSeconds ="+spin);
                Thread.SpinWait(spin);

                WriteControlPort(0);

                // the spec says you need to wait 2ms here.
                // my experience has been > 100ms are needed to recognize Seagate drives
                Thread.Sleep(TimeSpan.FromMilliseconds(125));

                if (!PollBSY(false)) {
                    Tracing.Log(Tracing.Debug,
                                "IDE Controller: controller Reset Aborted, No Drives Present or responding.\n");
                }
                FindDisks();
            }
            finally {
                PrivilegedGate.RestoreInterrupts(iflag);
            }
        } //resetcontroller

        private void FindDisks()
        {
            // Device 0 now must post to the Error Register
            byte errorBits;
            errorBits = ReadErrorPort();
            //Tracing.Log(Tracing.Debug,"IDE controller: controller Reset finished, Error reg: 0x{0:x2}\n", errorBits);
            DebugStub.Print("  IDE Reset: {0:x2}\n", __arglist(errorBits));

            if ((errorBits & 0x1) > 0 && ReadLBALowPort() == 1) {
                DebugStub.Print("  IDE Master Signature {0:x2} {1:x2}\n",
                                __arglist(ReadLBAMidPort(), ReadLBAHighPort()));
                if (ReadLBAMidPort() == 0 && ReadLBAHighPort() == 0) {
                    DebugStub.Print("    IDE Found Master Disk\n");
                    ideDisk1Config = new IdeDiskConfig(ideConfigHandle, 0, instanceName, false);
                    //SOSP hack
                    DiskDriverControl.SetMasterDiskConfig(ideDisk1Config);

                }
                else if (ReadLBAMidPort() == 0x14 && ReadLBAHighPort() == 0xEB) {
                    DebugStub.Print("    IDE Found Master ATAPI16 Device\n");
                    ideDisk1Config = new IdeDiskConfig(ideConfigHandle, 0, instanceName, true);
                    //SOSP hack
                    DiskDriverControl.SetMasterDiskConfig(ideDisk1Config);
                }
                else {
                    DebugStub.Print("    IDE Unknown Master Signature {0:x2} {1:x2}\n",
                                    __arglist(ReadLBAMidPort(), ReadLBAHighPort()));
                    Tracing.Log(Tracing.Debug,"IDEcontroller: Master device has unknown signature 0x{0:x2} 0x{1:x2}.\n",
                        (UIntPtr) ReadLBAMidPort(), (UIntPtr) ReadLBAHighPort());
                }
            }
            else {
                DebugStub.Print("IDEcontroller: Master is missing\n");
                Tracing.Log(Tracing.Debug,"IDEcontroller: Master is missing");
            }

            //
            //  Slave Device
            //

            SelectDevice(1, false);
            if (!PollDRDY(true)) {
                DebugStub.Print("IDEcontroller: Slave is missing\n");
                Tracing.Log(Tracing.Debug,"IdeController: Slave is missing");
                return;
            }
            if (ReadSectorCountPort() == 1 && ReadLBALowPort() == 1) {
                if ((errorBits & 0x80) != 0) {
                    Tracing.Log(Tracing.Debug,"IdeController: Slave device reports failure.\n");
                }
                else {
                    DebugStub.Print("  IDE Slave Signature {0:x2} {1:x2}\n",
                                    __arglist(ReadLBAMidPort(), ReadLBAHighPort()));
                    if (ReadLBAMidPort() == 0 && ReadLBAHighPort() == 0) {
                        DebugStub.Print("    IDE Found Slave Disk\n");
                        ideDisk2Config = new IdeDiskConfig(ideConfigHandle, 1, instanceName, false);
                        //SOSP hack
                        DiskDriverControl.SetSlaveDiskConfig(ideDisk2Config);
                    }
                    else if (ReadLBAMidPort() == 0x14 && ReadLBAHighPort() == 0xEB) {
                        DebugStub.Print("    IDE Found Slave ATAPI16 Device\n");
                        ideDisk2Config = new IdeDiskConfig(ideConfigHandle, 1, instanceName, true);
                        //SOSP hack
                        DiskDriverControl.SetSlaveDiskConfig(ideDisk2Config);
                    }
                    else {
                        DebugStub.Print("    IDE Unknown Slave Signature {0:x2} {1:x2}\n",
                                        __arglist(ReadLBAMidPort(), ReadLBAHighPort()));
                        Tracing.Log(Tracing.Debug,"Controller: Slave device has unknown signature 0x{0:x2} 0x{1:x2}.\n",
                            (UIntPtr) ReadLBAMidPort(), (UIntPtr) ReadLBAHighPort());
                    }
                }
            }
            else {
                DebugStub.Print("IDEcontroller: Slave is missing\n");
                Tracing.Log(Tracing.Debug,"IdeController: Slave is missing.\n");
            }
        }

        private void IrqWorkerMain()
        {
            Tracing.Log(Tracing.Audit, "Start of worker thread.");
            byte status;
            int  countSpurious = 0;
            while (irqWorkerStop == false) {
                ideConfigHandle.Irq.WaitForInterrupt();

                bool iflag = PrivilegedGate.DisableInterrupts();
                bool doNotAck = false;
#if DEBUG_SHARED_IRQ
                //Tracing.Log(Tracing.Debug, "Interrupt signaled.");
#endif // DEBUG_SHARED_IRQ
                try {

                    Tracing.Log(Tracing.Debug,"wakeup {0:x}",
                        ideConfigHandle.BusMasterDma.InterruptHigh() ? (UIntPtr) 1 : (UIntPtr) 0 );

                    //
                    // "The IDE Driver is required to do a read of the [BM] controller status
                    //  register after receiving an interrupt. If the status register returns with the
                    //  Interrupt bit set then the driver knows the IDE device generated the interrupt
                    //  (important for shared interrupts)"

                    if (!ideConfigHandle.BusMasterDma.InterruptHigh()) {
                        //DebugStub.Break();
                        doNotAck = true ;
                    }
                    else {
                        if (CatchBug) {
                            Tracing.Log(Tracing.Debug,"CatchCount ={0}",(UIntPtr) CatchCount);
                            if (CatchCount-- <= 0) {
                                //DebugStub.Break();
                                 CatchBug = false;
                            }
                        }
                        status = ReadStatusPort();
                        if ((status & 0x1) > 0) {
                            Tracing.Log(Tracing.Debug,"ERROR: status={0}",(UIntPtr) status);

                            // Commented this out, this is preventing boot on some platforms
                            // DebugStub.Break(); //error
                        }
                        if (((status & (byte)ATA6.IdeStatus.DRDY) == 0) ||
                            ((status & (byte)ATA6.IdeStatus.ERR) != 0) )
                            {
                                ATA6.DebugQueryStatus(this);
                            }

                        //Tracing.Log(Tracing.Debug," IrqW status {0:x}",(UIntPtr) status);
                        ideConfigHandle.BusMasterDma.Disarm();

                        if (GetCommandPending()) {
                            SetCommandPending(false);
                            ideConfigHandle.CommandEndEvent.Set();
                            //Tracing.Log(Tracing.Debug,"setting CommandEndEvent");
                        }
                        else if (GetIdentifyInProgress()) {
                            SetIdentifyInProgress(false);
                            ideConfigHandle.IdentifyEndEvent.Set();
                        }
                        else {
                            Tracing.Log(Tracing.Debug,
                                        "   Spurious IDE interrupt on {0}\n",
                                        ControllerNameInternal);
                            if (countSpurious++ > 50) {
                                DebugStub.Break();
                            }
                        }
                    }
                }
                finally {
                    if (!doNotAck) {
                        ideConfigHandle.Irq.AckInterrupt();
#if DEBUG_SHARED_IRQ
                        Tracing.Log(Tracing.Debug,"ACKing {0}",
                        ControllerNameInternal);
#endif //DEBUG_SHARED_IRQ
                    }
                    else {
#if DEBUG_SHARED_IRQ
                        Tracing.Log(Tracing.Debug,"NACKing {0}",
                                    ControllerNameInternal);
#endif //DEBUG_SHARED_IRQ
                    }
                    PrivilegedGate.RestoreInterrupts(iflag);
                }
            }
        }
        public bool PollBSY(bool level)
        {
            Delay400ns();

            //TODO: FIXFIX PollLoopCount is a somewhat arbitrary number
            // > 2000 is needed to make SET_FEATURES work on real HW
            for (int i = 0; i < PollLoopCount; i++) {
                pollStatus = ReadStatusPort();
                if (level && (pollStatus & (byte)ATA6.IdeStatus.BSY) > 0) {

                    return true;
                }
                if (!level && (pollStatus & (byte)ATA6.IdeStatus.BSY) == 0) {
                    return true;
                }
            }
            Tracing.Log(Tracing.Debug," BSY failed to post in 400ns");
            return false;
        }

        public bool PollDRDY(bool level)
        {
            // TODO: FIXFIX PollLoopCount is a somewhat arbitrary number
            // > 2000 is needed to make SET_FEATURES work on real HW
            // level, true = high (1), false = low (0)
            for (int i = 0; i < PollLoopCount; i++) {
                byte pollStatus = ReadStatusPort();
                if (level && (pollStatus & (byte)ATA6.IdeStatus.DRDY) > 0) {
                    return true;
                }
                if (!level && (pollStatus & (byte)ATA6.IdeStatus.DRDY) == 0) {
                    return true;
                }
            }
            Tracing.Log(Tracing.Debug," DRDY failed to post in 400ns");
            return false;
        }

        public bool PollDRQ(bool level)
        {
            // level, true = high (1), false = low (0)

            Delay400ns();

            pollStatus = ReadStatusPort();
            if (level && (pollStatus & (byte)ATA6.IdeStatus.DRQ) > 0) {
                return true;
            }
            if (!level && (pollStatus & (byte)ATA6.IdeStatus.DRQ) == 0) {
                return true;
            }
            Tracing.Log(Tracing.Debug," DRQ failed to post in 400ns");
            return false;
        }

        public const byte IDE_FEATURE_ENABLE_WRITE_CACHE = 0x02;   // Enable write caching.
        public const byte IDE_FEATURE_SET_TRANSFER_MODE  = 0x03;   // Set the transfer mode.
        public const byte IDE_FEATURE_ENABLE_READ_AHEAD  = 0xAA;   // Enable read look ahead.
        public const byte IDE_FEATURE_DISABLE_READ_AHEAD = 0x55;   // Disable read look ahead.
        public const byte IDE_FEATURE_DISABLE_DEFAULTS   = 0x66;   // Disable revert to defaults on reset.

        public const byte IDE_MODE_SET_PIO_FLOW_CONTROL  = 0x08;   // page 176
        public const byte IDE_MODE_SET_ULTRA_DMA         = 0x40;   // page 176


        public bool IssueSet(IdeSet setType, ref IdentifyDeviceInformation ident)
        {
            ushort usFullStatus;
            byte nMode =0;

            bool iflag = PrivilegedGate.DisableInterrupts();
            if (!PollBSY(false)) {
                ATA6.DebugQueryStatus(this);
                DebugStub.Break();
            }
            try {
                switch (setType) {
                    case IdeSet.IDE_SET_MULTIPLE:
                        //AddTrace(GetPC(), ReadFullStatus());
                        //WriteSectorCountPort(m_nMaxBlockTransfer);
                        break;

                    case IdeSet.IDE_SET_MAX_UDMA:
                        nMode = (byte)((ident.UltraDmaSupported) ?
                                            ident.HighestUltraDmaMode : (byte) 0);
                        WriteFeaturesPort(IDE_FEATURE_SET_TRANSFER_MODE);
                        WriteSectorCountPort((byte)(IDE_MODE_SET_ULTRA_DMA | nMode));
                        WriteCommandPort((byte)ATA6.IdeCommand.SetFeature);
                        break;

                    case IdeSet.IDE_SET_READ_AHEAD:
                        //AddTrace(GetPC(), ReadFullStatus());
                        WriteFeaturesPort(IDE_FEATURE_ENABLE_READ_AHEAD);
                        break;

                    case IdeSet.IDE_UNSET_READ_AHEAD:
                        //AddTrace(GetPC(), ReadFullStatus());
                        WriteFeaturesPort(IDE_FEATURE_DISABLE_READ_AHEAD);
                        break;

                    case IdeSet.IDE_SET_WRITE_CACHE:
                        //AddTrace(GetPC(), ReadFullStatus());
                        WriteFeaturesPort(IDE_FEATURE_ENABLE_WRITE_CACHE);
                        break;

                    case IdeSet.IDE_SET_NO_REVERT:
                        //AddTrace(GetPC(), ReadFullStatus());
                        WriteFeaturesPort(IDE_FEATURE_DISABLE_DEFAULTS);
                        break;

                }

                WriteCommandPort((byte)ATA6.IdeCommand.SetFeature);
                if (!PollBSY(false)) {
                    ATA6.DebugQueryStatus(this);
                    DebugStub.Break();
                }
                byte status1 = ReadStatusPort();
                Tracing.Log(Tracing.Debug,"status={0}",(UIntPtr)status1);
            }
            finally {
                PrivilegedGate.RestoreInterrupts(iflag);
            }

            return true;
        }

        public void Delay400ns()
        {
            Thread.SpinWait(delay400ns);
        }

        public void SelectDevice(int device, bool atapi)
        {
            // REVIEW: should we be caching the last setting to avoid
            // lots of io port reading/writing?
            byte val = 0;

            if (device > 0) {
                val = ATA6.DriveSelect;
            }
            if (atapi) {
                val |= (byte) ATA6.IdeCommand.AtapiPacket;
            }
            // REVIEW:  what is the minimum requirement ?
            //PollBSY(false);  // wait for bus to be idle
            WriteDeviceHeadPort(val);
            //PollBSY(false);  // wait for bus to be idle
        }

        // TODO: This doesn't work, need to fix it some how.
        public SortedList Enumerate()
        {
            SortedList found = new SortedList();
            if (ideDisk1Config != null) {
                found.Add("/Disk1", ideDisk1Config);
            }
            if (ideDisk2Config != null) {
                found.Add("/Disk2", ideDisk2Config);
            }
            return found;
        }

        private static int CalculateDelay400nsCount()
        {
            ulong cyclesPerSecond = (ulong)Processor.CyclesPerSecond;
            ulong cycleCountStart;
            ulong cycleCountEnd;
            int spinCount = 1000;

            bool iflag = PrivilegedGate.DisableInterrupts();
            try {
                cycleCountStart = Processor.CycleCount;
                Thread.SpinWait(spinCount);
                cycleCountEnd   = Processor.CycleCount;
            }
            finally {
                PrivilegedGate.RestoreInterrupts(iflag);
            }

            ulong cycles   = cycleCountEnd - cycleCountStart;
            ulong iCount   = cycles / (ulong)spinCount;
            ulong cp100NS  = cyclesPerSecond / 10000000; //cycles per 100ns
            if (cp100NS < 1) {
                DebugStub.Print(" slow machine!! setting cp100NS to 1 \n");
                cp100NS = 1;
            }

            int delay400nsCount = (int) ((cp100NS/iCount) *4); //loop iterations needed for 400ns
            if (delay400nsCount < 100) {
                //delay400nsCount = 100;  //FIXFIX:  hack for VPC; CycleCount is not virtual!
                DebugStub.Print("  VPC: setting delay400 spin count to {0}\n",
                                __arglist(delay400nsCount));
            }

            // now check
            iflag = PrivilegedGate.DisableInterrupts();
            ulong s = Processor.CycleCount;
            Thread.SpinWait(delay400nsCount);
            ulong e = Processor.CycleCount;
            PrivilegedGate.RestoreInterrupts(iflag);

            DebugStub.Print("  CyclesPerSecond={0}, cyclesPer100ns={1},cyclesPerLoop={2} Delay loopCount={3}\n",
                            __arglist((ulong)cyclesPerSecond,
                                      (ulong)cp100NS,
                                      (ulong)iCount,
                                      (ulong)delay400nsCount));

            DebugStub.Print("  delay400 took {0} instructions or ~{1} nanoseconds\n",
                            __arglist((ulong)(e-s),
                                      (ulong)(((e-s)/cp100NS))* 100 ));

            return delay400nsCount;
        }
    }
}
