Can't sleep so write more source codes! add scan bus functions to get power caps...
[reactos.git] / reactos / drivers / bus / pcix / enum.c
index 89cb5f8..4af77b3 100644 (file)
 
 /* FUNCTIONS ******************************************************************/
 
+BOOLEAN
+NTAPI
+PcipIsSameDevice(IN PPCI_PDO_EXTENSION DeviceExtension,
+                 IN PPCI_COMMON_HEADER PciData)
+{
+    BOOLEAN IdMatch, RevMatch, SubsysMatch;
+    ULONGLONG HackFlags = DeviceExtension->HackFlags;
+
+    /* Check if the IDs match */
+    IdMatch = (PciData->VendorID == DeviceExtension->VendorId) &&
+              (PciData->DeviceID == DeviceExtension->DeviceId);
+    if (!IdMatch) return FALSE;
+
+    /* If the device has a valid revision, check if it matches */
+    RevMatch = (HackFlags & PCI_HACK_NO_REVISION_AFTER_D3) ||
+               (PciData->RevisionID == DeviceExtension->RevisionId);
+    if (!RevMatch) return FALSE;
+
+    /* For multifunction devices, this is enough to assume they're the same */
+    if (PCI_MULTIFUNCTION_DEVICE(PciData)) return TRUE;
+
+    /* For bridge devices, there's also nothing else that can be checked */
+    if (DeviceExtension->BaseClass == PCI_CLASS_BRIDGE_DEV) return TRUE;
+
+    /* Devices, on the other hand, have subsystem data that can be compared */
+    SubsysMatch = (HackFlags & (PCI_HACK_NO_SUBSYSTEM |
+                                PCI_HACK_NO_SUBSYSTEM_AFTER_D3)) ||
+                  ((DeviceExtension->SubsystemVendorId ==
+                    PciData->u.type0.SubVendorID) &&
+                   (DeviceExtension->SubsystemId ==
+                    PciData->u.type0.SubSystemID));
+    return SubsysMatch;
+}
+
+BOOLEAN
+NTAPI
+PciSkipThisFunction(IN PPCI_COMMON_HEADER PciData,
+                    IN PCI_SLOT_NUMBER Slot,
+                    IN UCHAR OperationType,
+                    IN ULONGLONG HackFlags)
+{
+    do
+    {
+        /* Check if this is device enumeration */
+        if (OperationType == PCI_SKIP_DEVICE_ENUMERATION)
+        {
+            /* Check if there's a hackflag saying not to enumerate this device */
+            if (HackFlags & PCI_HACK_NO_ENUM_AT_ALL) break;
+
+            /* Check if this is the high end of a double decker device */
+            if ((HackFlags & PCI_HACK_DOUBLE_DECKER) &&
+                (Slot.u.bits.DeviceNumber >= 16))
+            {
+                /* It belongs to the same device, so skip it */
+                DPRINT1("    Device (Ven %04x Dev %04x (d=0x%x, f=0x%x)) is a ghost.\n",
+                        PciData->VendorID,
+                        PciData->DeviceID,
+                        Slot.u.bits.DeviceNumber,
+                        Slot.u.bits.FunctionNumber);
+                break;
+            }
+        }
+        else if (OperationType == PCI_SKIP_RESOURCE_ENUMERATION)
+        {
+            /* Resource enumeration, check for a hackflag saying not to do it */
+            if (HackFlags & PCI_HACK_ENUM_NO_RESOURCE) break;
+        }
+        else
+        {
+            /* Logic error in the driver */
+            ASSERTMSG(FALSE, "PCI Skip Function - Operation type unknown.");
+        }
+
+        /* Check for legacy bridges during resource enumeration */
+        if ((PciData->BaseClass == PCI_CLASS_BRIDGE_DEV) &&
+            (PciData->SubClass <= PCI_SUBCLASS_BR_MCA) &&
+            (OperationType == PCI_SKIP_RESOURCE_ENUMERATION))
+        {
+            /* Their resources are not enumerated, only PCI and Cardbus/PCMCIA */
+            break;
+        }
+        else if (PciData->BaseClass == PCI_CLASS_NOT_DEFINED)
+        {
+            /* Undefined base class (usually a PCI BIOS/ROM bug) */
+            DPRINT1("    Vendor %04x, Device %04x has class code of PCI_CLASS_NOT_DEFINED\n",
+                    PciData->VendorID,
+                    PciData->DeviceID);
+
+            /*
+             * The Alder has an Intel Extended Express System Support Controller
+             * which presents apparently spurious BARs. When the PCI resource
+             * code tries to reassign these BARs, the second IO-APIC gets
+             * disabled (with disastrous consequences). The first BAR is the
+             * actual IO-APIC, the remaining five bars seem to be spurious
+             * resources, so ignore this device completely.
+             */
+            if ((PciData->VendorID == 0x8086) && (PciData->DeviceID == 8)) break;
+        }
+
+        /* Other normal PCI cards and bridges are enumerated */
+        if (PCI_CONFIGURATION_TYPE(PciData) <= PCI_CARDBUS_BRIDGE_TYPE) return FALSE;
+    } while (FALSE);
+
+    /* Hit one of the known bugs/hackflags, or this is a new kind of PCI unit */
+    DPRINT1("   Device skipped (not enumerated).\n");
+    return TRUE;
+}
+
+VOID
+NTAPI
+PciGetEnhancedCapabilities(IN PPCI_PDO_EXTENSION PdoExtension,
+                           IN PPCI_COMMON_HEADER PciData)
+{
+    ULONG HeaderType, CapPtr, TargetAgpCapabilityId;
+    DEVICE_POWER_STATE WakeLevel;
+    PCI_CAPABILITIES_HEADER AgpCapability;
+    PCI_PM_CAPABILITY PowerCapabilities;
+    PAGED_CODE();
+
+    /* Assume no known wake level */
+    PdoExtension->PowerState.DeviceWakeLevel = PowerDeviceUnspecified;
+
+    /* Make sure the device has capabilities */
+    if (!(PciData->Status & PCI_STATUS_CAPABILITIES_LIST))
+    {
+        /* If it doesn't, there will be no power management */
+        PdoExtension->CapabilitiesPtr = 0;
+        PdoExtension->HackFlags |= PCI_HACK_NO_PM_CAPS;
+    }
+    else
+    {
+        /* There's capabilities, need to figure out where to get the offset */
+        HeaderType = PCI_CONFIGURATION_TYPE(PciData);
+        if (HeaderType == PCI_CARDBUS_BRIDGE_TYPE)
+        {
+            /* Use the bridge's header */
+            CapPtr = PciData->u.type2.CapabilitiesPtr;
+        }
+        else
+        {
+            /* Use the device header */
+            ASSERT(HeaderType <= PCI_CARDBUS_BRIDGE_TYPE);
+            CapPtr = PciData->u.type0.CapabilitiesPtr;
+        }
+
+        /* Make sure the pointer is spec-aligned and located, and save it */
+        DPRINT1("Device has capabilities at: %lx\n", CapPtr);
+        ASSERT(((CapPtr & 0x3) == 0) && (CapPtr >= PCI_COMMON_HDR_LENGTH));
+        PdoExtension->CapabilitiesPtr = CapPtr;
+
+        /* Check for PCI-to-PCI Bridges and AGP bridges */
+        if ((PdoExtension->BaseClass == PCI_CLASS_BRIDGE_DEV) &&
+            ((PdoExtension->SubClass == PCI_SUBCLASS_BR_HOST) ||
+             (PdoExtension->SubClass == PCI_SUBCLASS_BR_PCI_TO_PCI)))
+        {
+            /* Query either the raw AGP capabilitity, or the Target AGP one */
+            TargetAgpCapabilityId = (PdoExtension->SubClass ==
+                                     PCI_SUBCLASS_BR_PCI_TO_PCI) ?
+                                     PCI_CAPABILITY_ID_AGP_TARGET :
+                                     PCI_CAPABILITY_ID_AGP;
+            if (PciReadDeviceCapability(PdoExtension,
+                                        PdoExtension->CapabilitiesPtr,
+                                        TargetAgpCapabilityId,
+                                        &AgpCapability,
+                                        sizeof(PCI_CAPABILITIES_HEADER)))
+            {
+                /* AGP target ID was found, store it */
+                DPRINT1("AGP ID: %lx\n", TargetAgpCapabilityId);
+                PdoExtension->TargetAgpCapabilityId = TargetAgpCapabilityId;
+            }
+        }
+
+        /* Check for devices that are known not to have proper power management */
+        if (!(PdoExtension->HackFlags & PCI_HACK_NO_PM_CAPS))
+        {
+            /* Query if this device supports power management */
+            if (!PciReadDeviceCapability(PdoExtension,
+                                         PdoExtension->CapabilitiesPtr,
+                                         PCI_CAPABILITY_ID_POWER_MANAGEMENT,
+                                         &PowerCapabilities.Header,
+                                         sizeof(PCI_PM_CAPABILITY)))
+            {
+                /* No power management, so act as if it had the hackflag set */
+                DPRINT1("No PM caps, disabling PM\n");
+                PdoExtension->HackFlags |= PCI_HACK_NO_PM_CAPS;
+            }
+            else
+            {
+                /* Otherwise, pick the highest wake level that is supported */
+                WakeLevel = PowerDeviceUnspecified;
+                if (PowerCapabilities.PMC.Capabilities.Support.PMED0)
+                    WakeLevel = PowerDeviceD0;
+                if (PowerCapabilities.PMC.Capabilities.Support.PMED1)
+                    WakeLevel = PowerDeviceD1;
+                if (PowerCapabilities.PMC.Capabilities.Support.PMED2)
+                    WakeLevel = PowerDeviceD2;
+                if (PowerCapabilities.PMC.Capabilities.Support.PMED3Hot)
+                    WakeLevel = PowerDeviceD3;
+                if (PowerCapabilities.PMC.Capabilities.Support.PMED3Cold)
+                    WakeLevel = PowerDeviceD3;
+                PdoExtension->PowerState.DeviceWakeLevel = WakeLevel;
+
+                /* Convert the PCI power state to the NT power state */
+                PdoExtension->PowerState.CurrentDeviceState =
+                    PowerCapabilities.PMCSR.ControlStatus.PowerState + 1;
+
+                /* Save all the power capabilities */
+                PdoExtension->PowerCapabilities = PowerCapabilities.PMC.Capabilities;
+                DPRINT1("PM Caps Found! Wake Level: %d Power State: %d\n",
+                        WakeLevel, PdoExtension->PowerState.CurrentDeviceState);
+            }
+        }
+    }
+
+    /* At the very end of all this, does this device not have power management? */
+    if (PdoExtension->HackFlags & PCI_HACK_NO_PM_CAPS)
+    {
+        /* Then guess the current state based on whether the decodes are on */
+        PdoExtension->PowerState.CurrentDeviceState =
+            PciData->Command & (PCI_ENABLE_IO_SPACE |
+                                PCI_ENABLE_MEMORY_SPACE |
+                                PCI_ENABLE_BUS_MASTER) ?
+            PowerDeviceD0: PowerDeviceD3;
+        DPRINT1("PM is off, so assumed device is: %d based on enables\n",
+                PdoExtension->PowerState.CurrentDeviceState);
+    }
+}
+
 NTSTATUS
 NTAPI
 PciScanBus(IN PPCI_FDO_EXTENSION DeviceExtension)
 {
     ULONG MaxDevice = PCI_MAX_DEVICES;
+    BOOLEAN ProcessFlag = FALSE;
     ULONG i, j, k;
+    LONGLONG HackFlags;
+    PDEVICE_OBJECT DeviceObject;
     UCHAR Buffer[PCI_COMMON_HDR_LENGTH];
+    UCHAR BiosBuffer[PCI_COMMON_HDR_LENGTH];
     PPCI_COMMON_HEADER PciData = (PVOID)Buffer;
+    PPCI_COMMON_HEADER BiosData = (PVOID)BiosBuffer;
     PCI_SLOT_NUMBER PciSlot;
+    NTSTATUS Status;
+    PPCI_PDO_EXTENSION PdoExtension, NewExtension;
+    PPCI_PDO_EXTENSION* BridgeExtension;
     PWCHAR DescriptionText;
+    USHORT SubVendorId, SubSystemId;
     DPRINT1("PCI Scan Bus: FDO Extension @ 0x%x, Base Bus = 0x%x\n",
             DeviceExtension, DeviceExtension->BaseBus);
 
@@ -79,7 +316,8 @@ PciScanBus(IN PPCI_FDO_EXTENSION DeviceExtension)
             /* Find description for this device for the debugger's sake */
             DescriptionText = PciGetDeviceDescriptionMessage(PciData->BaseClass,
                                                              PciData->SubClass);
-            DPRINT1("Device Description \"%S\".\n", DescriptionText ? DescriptionText : L"(NULL)");
+            DPRINT1("Device Description \"%S\".\n",
+                    DescriptionText ? DescriptionText : L"(NULL)");
             if (DescriptionText) ExFreePoolWithTag(DescriptionText, 0);
 
             /* Check if there is an ACPI Watchdog Table */
@@ -89,6 +327,231 @@ PciScanBus(IN PPCI_FDO_EXTENSION DeviceExtension)
                 UNIMPLEMENTED;
                 while (TRUE);
             }
+
+            /* Check for non-simple devices */
+            if ((PCI_MULTIFUNCTION_DEVICE(PciData)) ||
+                (PciData->BaseClass == PCI_CLASS_BRIDGE_DEV))
+            {
+                /* No subsystem data defined for these kinds of bridges */
+                SubVendorId = 0;
+                SubSystemId = 0;
+            }
+            else
+            {
+                /* Read the subsystem information from the PCI header */
+                SubVendorId = PciData->u.type0.SubVendorID;
+                SubSystemId = PciData->u.type0.SubSystemID;
+            }
+
+            /* Get any hack flags for this device */
+            HackFlags = PciGetHackFlags(PciData->VendorID,
+                                        PciData->DeviceID,
+                                        SubVendorId,
+                                        SubSystemId,
+                                        PciData->RevisionID);
+
+            /* Check if this device is considered critical by the OS */
+            if (PciIsCriticalDeviceClass(PciData->BaseClass, PciData->SubClass))
+            {
+                /* Check if normally the decodes would be disabled */
+                if (!(HackFlags & PCI_HACK_DONT_DISABLE_DECODES))
+                {
+                    /* Because this device is critical, don't disable them */
+                    DPRINT1("Not allowing PM Because device is critical\n");
+                    HackFlags |= PCI_HACK_CRITICAL_DEVICE;
+                }
+            }
+
+            /* PCI bridges with a VGA card are also considered critical */
+            if ((PciData->BaseClass == PCI_CLASS_BRIDGE_DEV) &&
+                (PciData->SubClass == PCI_SUBCLASS_BR_PCI_TO_PCI) &&
+                (PciData->u.type1.BridgeControl & PCI_ENABLE_BRIDGE_VGA) &&
+               !(HackFlags & PCI_HACK_DONT_DISABLE_DECODES))
+            {
+                /* Do not disable their decodes either */
+                DPRINT1("Not allowing PM because device is VGA\n");
+                HackFlags |= PCI_HACK_CRITICAL_DEVICE;
+            }
+
+            /* Also skip devices that should not be enumerated */
+            if (PciSkipThisFunction(PciData, PciSlot, 1, HackFlags)) continue;
+
+            /* Check if a PDO has already been created for this device */
+            PdoExtension = PciFindPdoByFunction(DeviceExtension,
+                                                PciSlot.u.AsULONG,
+                                                PciData);
+            if (PdoExtension)
+            {
+                /* Rescan scenarios are not yet implemented */
+                UNIMPLEMENTED;
+                while (TRUE);
+            }
+
+            /* Bus processing will need to happen */
+            ProcessFlag = TRUE;
+
+            /* Create the PDO for this device */
+            Status = PciPdoCreate(DeviceExtension, PciSlot, &DeviceObject);
+            ASSERT(NT_SUCCESS(Status));
+            NewExtension = (PPCI_PDO_EXTENSION)DeviceObject->DeviceExtension;
+
+            /* Check for broken devices with wrong/no class codes */
+            if (HackFlags & PCI_HACK_FAKE_CLASS_CODE)
+            {
+                /* Setup a default one */
+                PciData->BaseClass = PCI_CLASS_BASE_SYSTEM_DEV;
+                PciData->SubClass = PCI_SUBCLASS_SYS_OTHER;
+
+                /* Device will behave erratically when reading back data */
+                NewExtension->ExpectedWritebackFailure = TRUE;
+            }
+
+            /* Clone all the information from the header */
+            NewExtension->VendorId = PciData->VendorID;
+            NewExtension->DeviceId = PciData->DeviceID;
+            NewExtension->RevisionId = PciData->RevisionID;
+            NewExtension->ProgIf = PciData->ProgIf;
+            NewExtension->SubClass = PciData->SubClass;
+            NewExtension->BaseClass = PciData->BaseClass;
+            NewExtension->HeaderType = PCI_CONFIGURATION_TYPE(PciData);
+
+            /* Check for modern bridge types, which are managed by the driver */
+            if ((NewExtension->BaseClass == PCI_CLASS_BRIDGE_DEV) &&
+                ((NewExtension->SubClass == PCI_SUBCLASS_BR_PCI_TO_PCI) ||
+                 (NewExtension->SubClass == PCI_SUBCLASS_BR_CARDBUS)))
+            {
+                /* Acquire this device's lock */
+                KeEnterCriticalRegion();
+                KeWaitForSingleObject(&DeviceExtension->ChildListLock,
+                                      Executive,
+                                      KernelMode,
+                                      FALSE,
+                                      NULL);
+
+                /* Scan the bridge list until the first free entry */
+                for (BridgeExtension = &DeviceExtension->ChildBridgePdoList;
+                     *BridgeExtension;
+                     BridgeExtension = &(*BridgeExtension)->NextBridge);
+
+                /* Add this PDO as a bridge */
+                *BridgeExtension = NewExtension;
+                ASSERT(NewExtension->NextBridge == NULL);
+
+                /* Release this device's lock */
+                KeSetEvent(&DeviceExtension->ChildListLock,
+                           IO_NO_INCREMENT,
+                           FALSE);
+                KeLeaveCriticalRegion();
+            }
+
+            /* Get the PCI BIOS configuration saved in the registry */
+            Status = PciGetBiosConfig(NewExtension, BiosData);
+            if (NT_SUCCESS(Status))
+            {
+                /* This path has not yet been fully tested by eVb */
+                DPRINT1("Have BIOS configuration!\n");
+                UNIMPLEMENTED;
+
+                /* Check if the PCI BIOS configuration has changed */
+                if (!PcipIsSameDevice(NewExtension, BiosData))
+                {
+                    /* This is considered failure, and new data will be saved */
+                    Status = STATUS_UNSUCCESSFUL;
+                }
+                else
+                {
+                    /* Data is still correct, check for interrupt line change */
+                    if (BiosData->u.type0.InterruptLine !=
+                        PciData->u.type0.InterruptLine)
+                    {
+                        /* Update the current BIOS with the saved interrupt line */
+                        PciWriteDeviceConfig(NewExtension,
+                                             &BiosData->u.type0.InterruptLine,
+                                             FIELD_OFFSET(PCI_COMMON_HEADER,
+                                                          u.type0.InterruptLine),
+                                             sizeof(UCHAR));
+                    }
+
+                    /* Save the BIOS interrupt line and the initial command */
+                    NewExtension->RawInterruptLine = BiosData->u.type0.InterruptLine;
+                    NewExtension->InitialCommand = BiosData->Command;
+                }
+            }
+
+            /* Check if no saved data was present or if it was a mismatch */
+            if (!NT_SUCCESS(Status))
+            {
+                /* Save the new data */
+                Status = PciSaveBiosConfig(NewExtension, PciData);
+                ASSERT(NT_SUCCESS(Status));
+
+                /* Save the interrupt line and command from the device */
+                NewExtension->RawInterruptLine = PciData->u.type0.InterruptLine;
+                NewExtension->InitialCommand = PciData->Command;
+            }
+
+            /* Save original command from the device and hack flags */
+            NewExtension->CommandEnables = PciData->Command;
+            NewExtension->HackFlags = HackFlags;
+
+            /* Get power, AGP, and other capability data */
+            PciGetEnhancedCapabilities(NewExtension, PciData);
+
+            /* Power up the device */
+            PciSetPowerManagedDevicePowerState(NewExtension, PowerDeviceD0, FALSE);
+
+            /* Save interrupt pin */
+            NewExtension->InterruptPin = PciData->u.type0.InterruptPin;
+
+            /*
+             * Use either this device's actual IRQ line or, if it's connected on
+             * a master bus whose IRQ line is actually connected to the host, use
+             * the HAL to query the bus' IRQ line and store that as the adjusted
+             * interrupt line instead
+             */
+            NewExtension->AdjustedInterruptLine = PciGetAdjustedInterruptLine(NewExtension);
+
+            /* Check if this device is used for PCI debugger cards */
+            NewExtension->OnDebugPath = PciIsDeviceOnDebugPath(NewExtension);
+
+            /* Check for devices with invalid/bogus subsystem data */
+            if (HackFlags & PCI_HACK_NO_SUBSYSTEM)
+            {
+                /* Set the subsystem information to zero instead */
+                NewExtension->SubsystemVendorId = 0;
+                NewExtension->SubsystemId = 0;
+            }
+
+            /* Check for IDE controllers */
+            if ((NewExtension->BaseClass == PCI_CLASS_MASS_STORAGE_CTLR) &&
+                (NewExtension->SubClass == PCI_SUBCLASS_MSC_IDE_CTLR))
+            {
+                /* Do not allow them to power down completely */
+                NewExtension->DisablePowerDown = TRUE;
+            }
+
+            /*
+             * Check if this is a legacy bridge. Note that the i82375 PCI/EISA
+             * bridge that is present on certain NT Alpha machines appears as
+             * non-classified so detect it manually by scanning for its VID/PID.
+             */
+            if (((NewExtension->BaseClass == PCI_CLASS_BRIDGE_DEV) &&
+                ((NewExtension->SubClass == PCI_SUBCLASS_BR_ISA) ||
+                 (NewExtension->SubClass == PCI_SUBCLASS_BR_EISA) ||
+                 (NewExtension->SubClass == PCI_SUBCLASS_BR_MCA))) ||
+                ((NewExtension->VendorId == 0x8086) &&
+                 (NewExtension->DeviceId == 0x482)))
+            {
+                /* Do not allow these legacy bridges to be powered down */
+                NewExtension->DisablePowerDown = TRUE;
+            }
+
+            /* Save latency and cache size information */
+            NewExtension->SavedLatencyTimer = PciData->LatencyTimer;
+            NewExtension->SavedCacheLineSize = PciData->CacheLineSize;
+
+            /* The PDO is now ready to go */
+            DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
         }
     }