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

#define VERBOSE
//#define DEBUG_PCI_BUS
//#define DONT_USE_PNP_FOR_PCI

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

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

namespace Microsoft.Singularity.Drivers
{
    // create the resource object for CTR to fill in
    [DriverCategory]
    [Signature("/pnp/PNP0A03")]
    [Signature("/pnp/PNP0A08")]
    [EnumeratesDevice("pci/...")]
    internal class PciBusResources : DriverCategoryDeclaration
    {
        [IoFixedPortRange(Base = 0x0cf8, Length = 0x08, Shared = true)]
        IoPortRange configPort;

        // Provide to unify creation.
        public static IDevice! DeviceCreate(IoConfig! config, String! instanceName)
        {
            Tracing.Log(Tracing.Debug, "Creating PCI Bus");
            PciBus pciBus = new PciBus(PciEnumerator.GetEnumerator(config));
#if VERBOSE
            DebugStub.WriteLine("Displaying PCI Enumeration:");
            pciBus.Display();
            DebugStub.WriteLine("PCI Enumeration End.");
#endif
            return pciBus;
        }
    }

    [CLSCompliant(false)]
    public class PciBus : IBusDevice
    {
        private PciEnumerator! enumerator;

        static ushort IdentifierFromUnits(uint nBus, uint nDevice, uint nFunction)
        {
            return (ushort)(nFunction | (nDevice << 3) | (nBus << 8));
        }

        public PciBus(PciEnumerator! enumerator)
        {
            this.enumerator = enumerator;
        }

        public void Initialize()
        {
            Tracing.Log(Tracing.Debug, "Initializing PciBus.");
        }

        public void Finalize()
        {
        }

        public SortedList Enumerate()
        {
            return enumerator.Enumerate();
        }

        public void Display()
        {
            SortedList sl = enumerator.Enumerate();
            for ( int i = 0; i < sl.Count; i++ )  {
                object o = sl.GetByIndex(i);
                PciConfig config = o as PciConfig;
                if (config == null) {
                    DebugStub.Break();
                }
                else {
                    string str = sl.GetKey(i) as string;
                    DebugStub.Write("{0}: ",__arglist(str));
                    config.Print();
                }
            }
        }
    }

    [CLSCompliant(false)]
    public class PciEnumerator
    {
        public const uint MAX_BUSES         = 256;
        public const uint MAX_DEVICES       = 32;
        public const uint MAX_FUNCTIONS     = 8;
        public const uint MAX_IDENTIFIER    = (MAX_BUSES * MAX_DEVICES * MAX_FUNCTIONS) - 1;

        private const ushort PCI_ADDRESS_PORT    = 0xcf8;
        private const ushort PCI_DATA_PORT       = 0xcfc;

        private IoPort! addressPort;
        private IoPort! dataPort;

        static ushort IdentifierFromUnits(uint nBus, uint nDevice, uint nFunction)
        {
            return (ushort)(nFunction | (nDevice << 3) | (nBus << 8));
        }

        public static PciEnumerator GetEnumerator(IoConfig config)
        {
#if DONT_USE_PNP_FOR_PCI
            if (config != null && config.FixedRanges != null &&
                config.FixedRanges.Length >= 1 && config.FixedRanges[0] != null &&
                config.FixedRanges[0] is IoPortRange) {
                IoPortRange ports = (IoPortRange)config.FixedRanges[0];
                if(ports != null) {
                    IoPort addrPort = ports.PortAtOffset(0, 4, Access.Write);
                    IoPort dataPort = ports.PortAtOffset(4, 4, Access.ReadWrite);
                    return new PciEnumerator(addrPort, dataPort);
                }
                else {
                    DebugStub.WriteLine("PciBus: config != null but ports == null\n");
                    DebugStub.Break();
                }
            }
            else {
#endif
                IoPortRange dev = new IoPortRange(PCI_ADDRESS_PORT, 8, Access.ReadWrite);

                return new PciEnumerator(dev.PortAtOffset(0, 4, Access.Write),
                                         dev.PortAtOffset(4, 4, Access.ReadWrite));
#if DONT_USE_PNP_FOR_PCI
            }
#endif

        }

        private PciEnumerator(IoPort! addressPort, IoPort! dataPort)
        {
            this.addressPort = addressPort;
            this.dataPort = dataPort;
        }

        private uint Read32(uint identifier, uint offset)
        {
            if (identifier < 0 || identifier > MAX_IDENTIFIER) {
                throw new OverflowException("BAD_IDENTIFIER");
            }
            if ((offset & 0x3) != 0) {
                throw new Exception("BAD_OFFSET");
            }

            uint config = (((uint)offset & 0xfc) |
                           ((uint)identifier << 8) |
                           ((uint)1 << 31));

            addressPort.Write32(config);
            return dataPort.Read32();
        }

        private void Write32(uint identifier, uint offset, uint value)
        {
            if (identifier < 0 || identifier > MAX_IDENTIFIER) {
                throw new OverflowException("BAD_IDENTIFIER");
            }
            if ((offset & 0x3) != 0) {
                throw new Exception("BAD_OFFSET");
            }

            uint config = (((uint)offset & 0xfc) |
                           ((uint)identifier << 8) |
                           ((uint)1 << 31));

            addressPort.Write32(config);
            dataPort.Write32(value);
        }

        private PciPort PortForIdentifier(uint identifier)
        {
            if (identifier < 0 || identifier > MAX_IDENTIFIER) {
                throw new OverflowException("BAD_IDENTIFIER");
            }
            return new PciPort(addressPort, dataPort, (ushort)identifier);
        }

        public SortedList! Enumerate()
        {
            Tracing.Log(Tracing.Debug, "PCI Bus Enumerate");

            SortedList found = new SortedList();
            for (uint bus = 0; bus < MAX_BUSES; bus++) {
                bool hadDevices = false;

                for (uint device = 0; device < MAX_DEVICES; device++) {
                    PciConfig config;
                    uint identifier = IdentifierFromUnits(bus, device, 0);
                    uint u = Read32(identifier, 0);

                    if (u == ~0u || u == 0) {
                        continue;
                    }

                    u = Read32(identifier, 0x0c);

                    uint max_functions = (u & PciConfig.PCI_MULTIFUNCTION) == 0
                        ? (uint)1 : MAX_FUNCTIONS;
#if DEBUG_PCI_BUS
                    Tracing.Log(Tracing.Debug, "    {0}.{1}:C => max={2} [{3:x8}]",
                                bus, device, max_functions, u);
#endif

                    for (uint function = 0; function < max_functions; function++) {
                        identifier = IdentifierFromUnits(bus, device, function);
                        u = Read32(identifier, 0);
#if DEBUG_PCI_BUS
                        Tracing.Log(Tracing.Debug, "    {0}.{1}.{2}:0 => {3:x8}",
                                    bus, device, function, u);
#endif

                        if (u == ~0u || u == 0) {
                            continue;
                        }

                        u = Read32(identifier, 0x0c);
#if DEBUG_PCI_BUS
                        Tracing.Log(Tracing.Debug, "    {0}.{1}.{2}:C => {3:x8}",
                                    bus, device, function, u);
#endif

                        switch (u & PciConfig.PCI_TYPE_MASK) {
                            case PciConfig.PCI_DEVICE_TYPE:
                                config = new PciDeviceConfig(PortForIdentifier(identifier));
                                break;
                            case PciConfig.PCI_BRIDGE_TYPE:
                                config = new PciBridgeConfig(PortForIdentifier(identifier));
                                break;
                            case PciConfig.PCI_CARDBUS_TYPE:
                                config = new PciCardbusConfig(PortForIdentifier(identifier));
                                break;
                            default:
                                config = null;
                                break;
                        }

                        if (config != null) {

                            if (((config.VendorId == 0x8086) && (config.DeviceId == 0x269E)) ||
                                ((config.VendorId == 0x8086) && (config.DeviceId == 0x2681))) {
                                //
                                // TEMP: Do not enumerate Intel SATA controllers on the Dell Precision 490.
                                // This needs to be removed when we have real drivers for them.
                                //
                            }
                            else if ((config.VendorId == 0x8086) &&
                                     (config.DeviceId >= 0x27C0) &&
                                     (config.DeviceId <= 0x27C6)) {
                                //
                                // TEMP: Do not enumerate Intel SATA controllers on the Dell Precision 380.
                                // This needs to be removed when we have real drivers for them.
                                //
                            }
                            else {
                                found.Add(String.Format("/bus{0,4:x4}/dev{1,4:x4}/func{2,4:x4}",
                                                        bus, device, function),
                                          config);
                                hadDevices = true;
                            }
                        }
                    }
                }
                if (!hadDevices) {
                    break;
                }
            }
            return found;
        }
    }
}
