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

//#define VERBOSE

using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Configuration;

using System;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.Configuration.Assemblies;
using System.Runtime.Remoting;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Reflection;

namespace Microsoft.Singularity.Drivers
{
    [CLSCompliant(false)]
    public class PnpBios : IBusDevice
    {
        // since this is in the kernel, it can make whatever ports and memory it
        // wants.  The purpose of having a decorated DeviceRequirement object is
        // just to generate metadata correctly; in truth, we can't do CTR on
        // this because these resources aren't dynamic, and so we can't count on
        // IoSystem to get the resources for us.  Instead, we get our own
        // resources, and then we tell IoSystem about them via the method
        // ReportConfig
        [DriverCategory]
        [Signature("/root/pnp0")]
        [EnumeratesDevice("/pnp/...")]
        class MyConfig : DriverCategoryDeclaration
        {
            [IoMemoryRange(0, Default = 0xf3c30000, Length = 1, AllowWrite = false)]
            IoMemoryRange mem1;

            [IoPortRange(1, Default = 0x20b, Length = 1, AllowWrite = false)]
            IoPortRange port1;

            [IoFixedPortRange(Base = 0x0a79, Length = 0x01, AllowRead = false)]
            IoPortRange isaWritePort;

            [IoFixedPortRange(Base = 0x0279, Length = 0x01, AllowRead = false)]
            IoPortRange isaReadport;
        }

        private static byte lowestbit(uint bits)
        {
            for (byte i = 0; i < 32; i++, bits >>= 1) {
                if ((bits & 1) != 0) {
                    return i;
                }
            }
            return byte.MaxValue;
        }

        private static char hexdigit(byte c)
        {
            return (char)((c >= 10) ? ('A' + (char)c - 10) : ('0' + (char)c));
        }

        [StructLayout(LayoutKind.Explicit)]
        [CLSCompliant(false)]
        public struct PNPNODE {
            [FieldOffset( 0)] public ushort   Size;   //0
            [FieldOffset( 2)] public byte     Node;   //2
            [FieldOffset( 3)] public byte     ProductId0; //3
            [FieldOffset( 4)] public byte     ProductId1; //4
            [FieldOffset( 5)] public byte     ProductId2; //5
            [FieldOffset( 6)] public byte     ProductId3; //6
            [FieldOffset( 7)] public byte     DeviceType0;    //7
            [FieldOffset( 8)] public byte     DeviceType1;    //8
            [FieldOffset( 9)] public byte     DeviceType2;    //9
            [FieldOffset(10)] public ushort   DeviceAttributes;   //10..11
        }

        private IoMemory!  region;
        private IoPort!  isaReadPort;
        private IoPort!  isaWritePort;
        private IoPort!  isaAddressPort;

        private byte    isaCsns;
        private string  newId; // not thread safe!

        private const ushort ISA_ADDRESS_PORT   = 0x0279;
        private const ushort ISA_WRITE_PORT     = 0x0A79;

        private const byte ISA_ADDR_WAKE    = 0x03;
        private const byte ISA_ADDR_CONFIG  = 0x04;
        private const byte ISA_ADDR_STATUS  = 0x05;

        private byte GetIsaResByte()
        {
            isaAddressPort.Write8(ISA_ADDR_STATUS);
            while ((isaReadPort.Read8() & 0x1) == 0) {
                // wait for ready.
            }
            isaAddressPort.Write8(ISA_ADDR_CONFIG);
            return isaReadPort.Read8();
        }

        private void GetIsaResBytes(byte[]! buffer, int begin, int limit)
        {
            for (int i = begin; i < limit; i++) {
                buffer[i] = GetIsaResByte();
            }
        }

        public PnpBios(Resources.PnpBiosInfo pbi)
            requires pbi.pnpRegion != null;
            requires pbi.isaReadPort != null;
        {
            region = pbi.pnpRegion.AtOffset(0, (int)pbi.pnpRegion.Length);
            isaReadPort = pbi.isaReadPort;
            isaWritePort = new IoPort(ISA_WRITE_PORT, 1, Access.Write);
            isaAddressPort = new IoPort(ISA_ADDRESS_PORT, 1, Access.Write);
            isaCsns = (byte)pbi.isaCsns;
        }

        public void Initialize()
        {
        }

        public void Finalize()
        {
        }

        private string DecodeId(byte[]! buffer, int offset)
        {
            return "" +
                (char)('@' + (buffer[offset + 0] >> 2)) +
                (char)('@' + (((buffer[offset + 0] & 0x3) << 3) | (buffer[offset + 1] >> 5))) +
                (char)('@' + ((buffer[offset + 1] & 0x1f))) +
                hexdigit((byte)(buffer[offset + 2] >> 4)) +
                hexdigit((byte)(buffer[offset + 2] & 0xf)) +
                hexdigit((byte)(buffer[offset + 3] >> 4)) +
                hexdigit((byte)(buffer[offset + 3] & 0xf));
        }

        private string DecodePnpId(int offset)
        {
            byte[] buffer = new byte [7];
            region.Read8(offset, buffer, 0, 7);

            return DecodeId(buffer, 0);
        }

        private void DecodeIsaLarge(byte[]! buffer, int size, ArrayList! list)
        {
#if VERBOSE
            DebugStub.Print("  IsaLarge[{0}]: ", __arglist(buffer[0] & 0x7f));
#endif

            switch (buffer[0] & 0x7f) {
              default:
#if VERBOSE
                DebugStub.Print("   ???1 {0,2:x2}\n", __arglist(buffer[0] & 0x7f));
#endif
                break;
              case 0x1:
                if (buffer[10] == 0) {
                    break;
                }
#if VERBOSE
                DebugStub.Print("   Memory inf={0,2:x2} min={1,4:x4}00 max={2,4:x4}00 aln={3,4:x4} len={4,4:x4}00\n",
                                __arglist(
                                    buffer[3],
                                    BitConverter.ToUInt16(buffer, 4),
                                    BitConverter.ToUInt16(buffer, 6),
                                    BitConverter.ToUInt16(buffer, 8),
                                    BitConverter.ToUInt16(buffer, 10)
                                    ));
#endif
                list.Add(new IoMemoryRange((uint)BitConverter.ToUInt16(buffer, 4) << 8,
                                           (uint)BitConverter.ToUInt16(buffer, 10) << 8,
                                           Access.ReadWrite));
                break;
              case 0x2:
#if VERBOSE
                DebugStub.Print("   PNP ANSI Id\n");
#endif
                break;
              case 0x4:
#if VERBOSE
                DebugStub.Print("   PNP Vendor\n");
#endif
                break;
              case 0x5:
                if (BitConverter.ToUInt32(buffer, 16) == 0) {
                    break;
                }
#if VERBOSE
                DebugStub.Print("   32-bit Memory inf={0,2:x2} min={1,8:x8} max={2,8:x8} aln={3,8:x8} len={4,8:x8}\n",
                                __arglist(
                                    buffer[3],
                                    BitConverter.ToUInt32(buffer, 4),
                                    BitConverter.ToUInt32(buffer, 8),
                                    BitConverter.ToUInt32(buffer, 12),
                                    BitConverter.ToUInt32(buffer, 16)));
#endif
                list.Add(new IoMemoryRange((uint)BitConverter.ToUInt32(buffer, 4),
                                           (uint)BitConverter.ToUInt32(buffer, 16),
                                           Access.ReadWrite));
                break;
              case 0x6:
                if (BitConverter.ToUInt32(buffer, 8) == 0) {
                    break;
                }
#if VERBOSE
                DebugStub.Print("   32-bit Fixed Memory inf={0,2:x2} bas={1,8:x8}..{2,8:x8}\n",
                                __arglist(
                                    buffer[3],
                                    BitConverter.ToUInt32(buffer, 4),
                                    BitConverter.ToUInt32(buffer, 4) + BitConverter.ToUInt32(buffer, 8) - 1
                                    ));
#endif
                list.Add(new IoMemoryRange((uint)BitConverter.ToUInt32(buffer, 4),
                                           (uint)BitConverter.ToUInt32(buffer, 8),
                                           Access.ReadWrite));
                break;
            }
        }

        private void DecodeIsaSmall(byte[]! buffer, int size, ArrayList! list)
        {
#if VERBOSE
            DebugStub.Print("  IsaSmall[{0}]: ", __arglist(buffer[0] >> 3));
#endif

            switch (buffer[0] >> 3) {
                default:
#if VERBOSE
                    DebugStub.Print("   ???2 {0,2:x2}\n", __arglist(buffer[0] >> 3));
#endif
                    break;

                case 0x1: // Version
#if VERBOSE
                    DebugStub.Print("   Version PnP={0,2:x2}, Vendor={1,2:x2}\n",
                                    __arglist(
                                        buffer[1],
                                        buffer[2]));
#endif
                    break;
                case 0x2: // Logical Device
#if VERBOSE
                    DebugStub.Print("   Logical Device={0} Flags={1,2:x2} {2,2:x2}\n",
                                    __arglist(
                                        DecodeId(buffer, 1),
                                        buffer[5],
                                        buffer[6]));
#endif
                    break;
                case 0x3: // Compatible Device
#if VERBOSE
                    DebugStub.Print("   Compatible Device={0}\n",
                                    __arglist(DecodeId(buffer, 1)));
#endif
                    newId = "/" + DecodeId(buffer,1) + newId;
                    break;
                case 0x4: // IRQ
                    byte irq = lowestbit(BitConverter.ToUInt16(buffer, 1));
                    if (size >= 4) {
#if VERBOSE
                        DebugStub.Print("   IRQ {0,4:x4} type={1,2:x2}\n",
                                        __arglist(irq, buffer[3]));
#endif
                    }
                    else {
#if VERBOSE
                        DebugStub.Print("   IRQ {0,4:x4}\n", __arglist(irq));
#endif
                    }
                    list.Add(new IoIrqRange(irq, 1));
                    break;
                case 0x5: // DMA
                    byte dma = lowestbit(buffer[1]);
#if VERBOSE
                    DebugStub.Print("   DMA {0,2:x2} type={1,2:x2}\n",
                                    __arglist(dma, buffer[2]));
#endif
                    list.Add(new IoDmaRange(dma, 1));
                    break;
                case 0x6: // [
#if VERBOSE
                    DebugStub.Print("   Start pri={0} [\n",
                                    __arglist((buffer[0] >> 3 == 2 ? buffer[1] : (byte)0)));
#endif
                    break;
                case 0x7: // ]
#if VERBOSE
                    DebugStub.Print("   ] End\n");
#endif
                    break;
                case 0x8: // Port
                    if (buffer[7] == 0) {
                        break;
                    }
#if VERBOSE
                    DebugStub.Print("   I/O Port inf={0,2:x2} min={1,4:x4} max={2,4:x4} aln={3,2:x2} len={4,2:x2} {5,4:x4}..{6,4:x4}\n",
                                    __arglist(
                                        buffer[1],
                                        BitConverter.ToUInt16(buffer, 2),
                                        BitConverter.ToUInt16(buffer, 4),
                                        buffer[6],
                                        buffer[7],
                                        BitConverter.ToUInt16(buffer, 2),
                                        BitConverter.ToUInt16(buffer, 2) + buffer[7] - 1));
#endif
                    list.Add(new IoPortRange(BitConverter.ToUInt16(buffer, 2),
                                             buffer[7],
                                             Access.ReadWrite));
                    break;
                case 0x9: // Fixed Port
                    if (buffer[3] == 0) {
                        break;
                    }
#if VERBOSE
                    DebugStub.Print("   Fixed I/O Port bas={0,4:x4} len={1,2:x2}  {2,4:x4}..{3,4:x4}\n",
                                    __arglist(
                                        BitConverter.ToUInt16(buffer, 1),
                                        buffer[3],
                                        BitConverter.ToUInt16(buffer, 1),
                                        BitConverter.ToUInt16(buffer, 1) + buffer[3] - 1));
#endif
                    list.Add(new IoPortRange(BitConverter.ToUInt16(buffer, 1),
                                             buffer[3],
                                             Access.ReadWrite));
                    break;
                case 0xe: // Vendor
#if VERBOSE
                    DebugStub.Print("   Vendor\n");
#endif
                    break;
            }
        }

        private byte[]! SizeBuffer(byte[] buffer, int size)
        {
            if (buffer == null || buffer.Length < size) {
                return new byte[size];
            }
            return buffer;
        }

        private PnpConfig DecodeIsa()
        {
            ArrayList list = new ArrayList();
            byte[] buffer = null;

            for (;;) {
                byte b = GetIsaResByte();
                int size;

                if (b == 0x79) {
                    break;
                }

                if ((b & 0x80) != 0) {
                    byte b1 = GetIsaResByte();
                    byte b2 = GetIsaResByte();
                    size = 3 + (((int)b2 << 8) | b1);

                    buffer = SizeBuffer(buffer, size);
                    buffer[0] = b;
                    buffer[1] = b1;
                    buffer[2] = b2;
                    GetIsaResBytes(buffer, 3, size);

                    DecodeIsaLarge(buffer, size, list);
                }
                else {
                    size = 1 + (b & 0x7);
                    buffer = SizeBuffer(buffer, size);
                    buffer[0] = b;
                    GetIsaResBytes(buffer, 1, size);

                    DecodeIsaSmall(buffer, size, list);
                }
            }
            return NewPnpConfig((!)newId, list);
        }

        private PnpConfig DecodePnp(int offset, int limit)
        {
            ArrayList list = new ArrayList();
            byte[] buffer = null;

            while (offset < limit) {
                if (region.Read8(offset) == 0x79) {
                    offset += 2;
                    break;
                }

                int size;

                if ((region.Read8(offset) & 0x80) != 0) {
                    size = 3 + region.Read16(offset + 1);

                    buffer = SizeBuffer(buffer, size);
                    region.Read8(offset, buffer, 0, size);

                    DecodeIsaLarge(buffer, size, list);
                }
                else {
                    size = 1 + (region.Read8(offset) & 0x7);

                    buffer = SizeBuffer(buffer, size);
                    region.Read8(offset, buffer, 0, size);

                    DecodeIsaSmall(buffer, size, list);
                }
                offset += size;
            }
            return NewPnpConfig((!)newId, list);
        }

        private static PnpConfig! NewPnpConfig(string! id, ArrayList! list)
        {
            ArrayList idlist = new ArrayList();

            string pnp_id = "/pnp" + id;

            idlist.Add(pnp_id);

            int pos = 5; // skip "/pnp/"

            // DebugStub.WriteLine("parsing isa pnp id: " + pnp_id);

            for (;;) {
                int next = pnp_id.IndexOf('/', pos);
                if (next == -1)
                    break;
                string variant = pnp_id.Substring(0, next);
                idlist.Add(variant);
                // DebugStub.WriteLine("adding variant: " + variant);
                pos = next + 1;
            }
            
            string[]! ids = (!)(string[])idlist.ToArray(typeof(string));

            // DebugStub.Break();
            return new PnpConfig(ids, list);
        }


        // create a PnpConfig object that tells all of the resources being used
        // by this resource, so that we can account for everything in IoSystem

        // This may seem perverse, but it is necessary!  We don't use
        // enumeration to get the root device, and our accounting tool requires
        // the IoConfig objects that are created via enumeration.  Since the
        // memory range and readport range are gleaned through the boot process,
        // without enumeration, we have to provide this hack to get the resource
        // config into an IoConfig object, or else our accounting will not
        // include the root bus.
#if SINGULARITY_KERNEL
        public PnpConfig! ReportConfig()
#else
        internal PnpConfig! ReportConfig()
#endif
        {
            ArrayList list = new ArrayList();
            IoRange [] fixedRanges = new IoRange[2];

            list.Add(new IoMemoryRange((UIntPtr)region.PhysicalAddress.Value,
                                       (UIntPtr)region.Length,
                                       true, true));
            list.Add(new IoPortRange(isaReadPort.Port,
                                     isaReadPort.Size,
                                     isaReadPort.Readable,
                                     isaReadPort.Writable));

            // since we're creating manual (and, for that matter, trusted)
            // accounting, let's save a bit of work and generate the fixed
            // resources right now.
            fixedRanges[0] = new IoPortRange(isaWritePort.Port,
                                             isaWritePort.Size,
                                             isaWritePort.Readable,
                                             isaWritePort.Writable);
            fixedRanges[1] = new IoPortRange(isaAddressPort.Port,
                                             isaAddressPort.Size,
                                             isaAddressPort.Readable,
                                             isaAddressPort.Writable);
            PnpConfig p = new PnpConfig(new string[] { "" }, list);
            p.FixedRanges = fixedRanges;
            return p;
        }


        public SortedList Enumerate()
        {
            SortedList found = new SortedList();

            Tracing.Log(Tracing.Audit, "Probing ISA Information");
            DebugStub.WriteLine("Enumerating ISA PNP:");
            int node = 0x000;
            for (byte csn = 1; csn <= isaCsns; csn++) {
#if VERBOSE
                DebugStub.Print("Reading csn={0}\n", __arglist(csn));
#endif
                isaAddressPort.Write8(ISA_ADDR_WAKE);
                isaWritePort.Write8(csn);

                byte[] buffer = new byte[9];
                GetIsaResBytes(buffer, 0, 9);
                newId = "/" + DecodeId(buffer, 0);

                DebugStub.Print(newId);
                DebugStub.Print("/");
                DebugStub.Print(node);
                DebugStub.Print(":\n");
                found.Add(String.Format("/{0,3:x3}", node++), DecodeIsa());
            }

#if VERBOSE
            DebugStub.Print("\n");
#endif
            // See http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/devids.txt
            node = 0x100;
            for (int offset = 0; offset < region.Length;) {
                ushort size = region.Read16(offset);
#if VERBOSE
                ushort attr = region.Read16(offset + 10);
#endif

                newId = "/" + DecodePnpId(offset + 3);

#if VERBOSE
                DebugStub.Print("  {0,8:x8} {1} {2,4:x4}",
                                __arglist((uint)(region.PhysicalAddress.Value + (ulong)offset), newId, attr));
                String s = "";

                if ((attr & 0x0100) != 0) s += " dyn";
                if ((attr & 0x0080) != 0) s += " onl";
                if ((attr & 0x0040) != 0) s += " rmv";
                if ((attr & 0x0020) != 0) s += " dck";
                if ((attr & 0x0010) != 0) s += " ipl";
                if ((attr & 0x0008) != 0) s += " inp";
                if ((attr & 0x0004) != 0) s += " out";
                if ((attr & 0x0002) != 0) s += " cfg";
                if ((attr & 0x0001) != 0) s += " dis";

                DebugStub.WriteLine(s);
#endif

                DebugStub.WriteLine("{0}/{1:x3}:", __arglist(newId, node));
                found.Add(String.Format("/{0,3:x3}", node++),
                          DecodePnp(offset + 12, offset + size));

                offset += size;
            }
            DebugStub.WriteLine("");
            return found;
        }
    }
}
