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

//

//#define DEBUG_DISPATCH_IO
//#define DEBUG_IO


using Microsoft.Singularity.Io;

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

using Microsoft.Singularity.Configuration;

namespace Microsoft.Singularity.Hal
{

    // create the resource object for LTR to fill in
    [DriverCategory]
    [Signature("/pseudobus0/tpm12")]
    public sealed class TpmResources : DriverCategoryDeclaration
    {
        [IoMemoryRange(1, Default = 0xfed40000, Length = 0x4000)]
        public IoMemoryRange pcrs;
    }


    //////////////////////////////////////////////////////////////////////////
    //
    //  Underlying device implementation.
    //
    internal class Tpm
    {
        private class TpmRegisterOffsets
        {
            public const int TpmRegAccess = 0x0;
            public const int TpmStatus = 0x18;
            public const int TpmBurst = 0x19;
            public const int TpmDataFifo = 0x24;
        };

        private class TpmRegAccessMasks
        {
            public const byte TpmAccessValidStatus  = 0x80;
            public const byte TpmAccessActiveLocality  = 0x20;
            public const byte TpmAccessHasBeenSeized = 0x10;
            public const byte TpmAccessRequestUse = 0x2;
        };

        private class TpmRegStatusMasks
        {
            public const byte TpmStatusValid = 0x80;
            public const byte TpmStatusCommandReady = 0x40;
            public const byte TpmStatusGo = 0x20;
            public const byte TpmStatusDataAvailable = 0x10;
            public const byte TpmStatusExpect = 0x8;
            public const byte TpmStatusRetry = 0x2;

        };

        // Retries assuming 1 ms wait between retry
        //
        // TODO: those are the default values, grab the actual ones from the device

        private class Retries
        {
            public const int MaxStatusRetries = 200;
            public const int MaxDataRetries = 400;

        }

        byte[] capability_command = {0, 193,
                                     0, 0, 0, 18,
                                     0, 0, 0, 101,
                                     0, 0, 0, 6,
                                     0, 0, 0, 0 };

        private int locality = 0;

        private IoMemory tpmIoMemory;

        private byte[] minResponse;

        //private const int minResponseLen = 6;
        private const int minResponseLen = 6;
        private const int dataLenOffset = 2;


        // Constructor
        internal Tpm(/*TpmResources! res*/)
        {
            Tracing.Log(Tracing.Debug, "Tpm: Initialize() called\n");

            DebugStub.WriteLine("Tpm: Initialize() called");

            tpmIoMemory = IoMemory.MapPhysicalMemory(new UIntPtr(0xfed40000),
                                                  new UIntPtr(0x4000),
                                                  true,
                                                  true);

            if (tpmIoMemory == null) {
                DebugStub.WriteLine("Tpm: Initialize(), MapPhysicalMemory failed");

            }

            minResponse = new byte[minResponseLen];



        }

        internal void PrintRegisters()
        {
            byte TpmAccess = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmRegAccess);
            byte TpmStatus = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmStatus);

            DebugStub.WriteLine("Tpm: PrintRegisters() Access={0:x2}, Status={1:x2}", __arglist(TpmAccess, TpmStatus));


        }

        ushort GetBurst()
        {
            ushort burst = 0;
            for (int j = 0; j < Retries.MaxDataRetries; j++) {
                burst = tpmIoMemory.Read16(locality + TpmRegisterOffsets.TpmBurst);

                if (burst != 0)
                    break;

                Thread.Sleep(1);
            }

            return burst;


        }

        //byte Read8(int byteOffset)

        internal bool WaitOnBit(int address, byte mask, int MaxRetries)
        {
            for (int i = 0; i < MaxRetries; i++) {
                byte data = tpmIoMemory.Read8(address);

                if ((data & mask) > 0)
                    return true;

                Thread.Sleep(1);
            }
            return false;

        }

        internal bool Send(byte[] command)
        {
            bool Status = true;

            DebugStub.WriteLine("Tpm: Send() called");
            PrintRegisters();


            byte TpmAccess = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmRegAccess);


            //
            // check the access register
            //

            //
            // check if the TPM has been disabled
            //

            if (TpmAccess == 0xff) {
                DebugStub.WriteLine("Tpm: Send() TPM disabled");
                return false;
            }

            if ((TpmAccess & TpmRegAccessMasks.TpmAccessHasBeenSeized) > 0) {
                DebugStub.WriteLine("Tpm: Send() TPM seized");
                return false;
            }

            if ((TpmAccess & TpmRegAccessMasks.TpmAccessValidStatus) == 0) {
                DebugStub.WriteLine("Tpm: Send() TPM had invalid status");
                return false;
            }


            //
            // request locality use
            //

            if ((TpmAccess & TpmRegAccessMasks.TpmAccessActiveLocality) == 0) {
                //
                TpmAccess |= TpmRegAccessMasks.TpmAccessActiveLocality;
                tpmIoMemory.Write8(locality + TpmRegisterOffsets.TpmRegAccess, TpmAccess);

                if (!WaitOnBit(locality + TpmRegisterOffsets.TpmRegAccess,
                               TpmRegAccessMasks.TpmAccessActiveLocality,
                               Retries.MaxStatusRetries))
                {
                    DebugStub.WriteLine("Tpm: Send() could not set locality");
                    return false;

                }

            }

            //
            // check if the device is ready
            //

            byte TpmStatus = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmStatus);

            if ((TpmStatus & TpmRegStatusMasks.TpmStatusCommandReady) == 0) {
                TpmStatus |= TpmRegStatusMasks.TpmStatusCommandReady;
                tpmIoMemory.Write8(locality + TpmRegisterOffsets.TpmStatus, TpmStatus);

                if (!WaitOnBit(locality + TpmRegisterOffsets.TpmStatus,
                               TpmRegStatusMasks.TpmStatusCommandReady,
                               Retries.MaxStatusRetries))
                {
                    DebugStub.WriteLine("Tpm: Send() device not ready");
                    PrintRegisters();
                    return false;

                }

            }

            DebugStub.WriteLine("Tpm: Send() command length={0:x2}", __arglist(command.Length));

            //
            // write the data to the data register
            //

            for (int i = 0; i < command.Length;) {
                ushort burst = 0;

                burst = GetBurst();

                if (burst == 0) {
                    DebugStub.WriteLine("Tpm: Send() timed out waiting for burst");
                    return false;
                }

                while (burst > 0) {
                    burst--;

                    if (i == command.Length)
                        break;

                    //
                    // last byte, check if we still expect
                    //

                    if (i == command.Length - 1) {
                        TpmStatus = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmStatus);

                        if ((TpmStatus & TpmRegStatusMasks.TpmStatusExpect) == 0) {
                            //
                            // something went wrong
                            //
                            DebugStub.WriteLine("Tpm: Send() not expecting more data before last byte");
                            return false;

                        }
                        DebugStub.WriteLine("Tpm: Send() last byte expected ok");

                    }

                    tpmIoMemory.Write8(locality + TpmRegisterOffsets.TpmDataFifo, command[i]);


                    i++;

                }


            }

            if (!WaitOnBit(locality + TpmRegisterOffsets.TpmStatus,
                           TpmRegStatusMasks.TpmStatusValid,
                           Retries.MaxDataRetries))
            {
                DebugStub.WriteLine("Tpm: Send(), WaitOnTpmStatusValid timed out after command write");
                PrintRegisters();
                return false;

            }

            TpmStatus = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmStatus);

            if ((TpmStatus & TpmRegStatusMasks.TpmStatusExpect) > 0) {
                //
                // more data needed
                //
                DebugStub.WriteLine("Tpm: Send(), more data needed");
                return false;

            }


            //
            // launch the command
            //
            TpmStatus |= TpmRegStatusMasks.TpmStatusGo;
            tpmIoMemory.Write8(locality + TpmRegisterOffsets.TpmStatus, TpmStatus);

            DebugStub.WriteLine("Tpm: Send(), success");

            return Status;

        }

        private bool ReadBytes(byte[] buffer, int offset, int numBytes, ushort prevBurst, out ushort burstRemainder)
        {
            byte TpmStatus;
            burstRemainder = 0;
            //
            // read the remaining burst bytes
            //


            if (prevBurst > 0) {
                for (int i = 0; i < prevBurst; i++) {
                   if (numBytes > 0) {
                       TpmStatus = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmStatus);

                       if ((TpmStatus & TpmRegStatusMasks.TpmStatusDataAvailable) == 0) {
                           DebugStub.WriteLine("Tpm: ReadBytes() no more data avaliable, ={0:d4}", __arglist(numBytes));
                           return false;
                       }


                   }

                   if (numBytes <= 0)
                        return true;


                    buffer[offset] = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmDataFifo);
                    offset++;
                    numBytes--;
                }
            }

            ushort burst = 0;

            while (numBytes > 0) {
                //get next burst

                burst = GetBurst();

                if (burst == 0) {
                    DebugStub.WriteLine("Tpm: ReadBytes() timed out waiting for burst");
                    return false;
                }

                while (burst > 0) {
                    burst--;
                    if (numBytes == 1) {
                        TpmStatus = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmStatus);
                        if ((TpmStatus & TpmRegStatusMasks.TpmStatusDataAvailable) == 0) {
                            DebugStub.WriteLine("Tpm: ReadBytes() no more data avaliable2");
                            return false;
                        }
                    }

                    buffer[offset] = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmDataFifo);
                    offset++;
                    numBytes--;

                    if (numBytes == 0) {
                        burstRemainder = burst;
                        return true;
                    }


                }


            }
            return false;

        }

        bool ReceiveAttempt(out byte[] response)
        {
            bool Ready = false;
            bool DataAvailable = false;
            bool Status = true;
            response = null;
            byte TpmStatus;

            DebugStub.WriteLine("Tpm: ReceiveAttempt() called");

            for (int i = 0; i < Retries.MaxDataRetries; i++) {
                if (WaitOnBit(locality + TpmRegisterOffsets.TpmStatus,
                   TpmRegStatusMasks.TpmStatusValid,
                   Retries.MaxDataRetries))
                {
                    Ready = true;

                }
            }

            if (!Ready) {
                DebugStub.WriteLine("Tpm: ReceiveAttempt() device not ready");
                return false;
            }

            for (int i = 0; i < Retries.MaxDataRetries; i++) {

                TpmStatus = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmStatus);

                if ((TpmStatus & TpmRegStatusMasks.TpmStatusDataAvailable) > 0) {
                    DataAvailable = true;
                    break;

                }
                Thread.Sleep(1);
            }

            if (!DataAvailable) {
                DebugStub.WriteLine("Tpm: ReceiveAttempt(), data not available");
                return false;

            }

            //
            // read enough bytes to determine the length
            //

            ushort burstRemainder;

            Status = ReadBytes(minResponse, 0, minResponseLen, 0, out burstRemainder);

            if (!Status) {
                DebugStub.WriteLine("Tpm: ReceiveAttempt(), ReadBytes failed");
                return false;
            }

            int dataLen = 0;

            //
            // acquire data length
            //

            dataLen = (minResponse[dataLenOffset] << 24) | (minResponse[dataLenOffset + 1] << 16) |
                      (minResponse[dataLenOffset + 2] << 8) | (minResponse[dataLenOffset + 3]);

            DebugStub.WriteLine("Tpm: ReceiveAttempt() data length={0:d4}", __arglist(dataLen));

            response = new byte[dataLen];

            for (int i = 0; i < minResponseLen; i++) {
                response[i] = minResponse[i];

            }

            ushort nextBurstRem;


            Status = ReadBytes(response, minResponseLen, dataLen - minResponseLen, burstRemainder, out nextBurstRem);

            if (!Status) {
                DebugStub.WriteLine("Tpm: ReceiveAttempt(), ReadBytes failed2");
                return false;
            }


            if (!WaitOnBit(locality + TpmRegisterOffsets.TpmStatus,
               TpmRegStatusMasks.TpmStatusValid,
               Retries.MaxDataRetries))
            {
                DebugStub.WriteLine("Tpm: ReceiveAttempt(), valid bit not set after done reading");
                //PrintRegisters();
                return false;

            }

            return Status;



        }


        bool Receive(out byte[] response)
        {
            bool Status;
            response = null;

            for (int i = 0; i < Retries.MaxDataRetries; i++) {
                Status = ReceiveAttempt(out response);
                if (Status)
                    return Status;

                byte TpmStatus = tpmIoMemory.Read8(locality + TpmRegisterOffsets.TpmStatus);
                TpmStatus |= TpmRegStatusMasks.TpmStatusRetry;
                tpmIoMemory.Write8(locality + TpmRegisterOffsets.TpmStatus, TpmStatus);
                Thread.Sleep(10);

            }

            return false;


        }

        internal bool SendReceive(byte[] request, out byte[] response)
        {
            bool Status;

            response = null;

            Status = Send(request);
            if (!Status)
                return Status;

            Status = Receive(out response);

            if (Status) {
                DebugStub.WriteLine("Tpm: SendReceive(), success");

            }

            return Status;

        }

        // Device methods
        public void Initialize()
        {

        }
//
//      ///////////////////////////////////////////////////////////////////////
//      //
//      // Register accessors / modifiers / utilities
//      //
//
//      private uint Read(uint offset)
//      {
//          return ioMemory.Read32((int) offset);
//      }
//
//      private void Write(uint offset, uint value)
//      {
//          ioMemory.Write32((int) offset, value);
//      }
//
//      private void SetBits(int offset, uint bits)
//      {
//          ioMemory.Write32(offset, ioMemory.Read32(offset) | bits);
//      }
//
//      private void ClearBits(int offset, uint bits)
//      {
//          ioMemory.Write32(offset, ioMemory.Read32(offset) & ~bits);
//      }
//

        public void ReleaseResources()
        {
            Tracing.Log(Tracing.Debug, "Tpm: Finalize() called\n");
        }

        // how do we pass a string back?

        public string Name
        {
            get { return "TPM 1.2 compliant device"; }
        }

        public string Version
        {
            get { return "0.1"; }
        }

    }
}
