//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//

//
// PCIbus bus driver and enumerator.
//

#include <windows.h>
#include <nkintr.h>
#include <pkfuncs.h>
#include <devload.h>
#include <resmgr.h>
#include <ceddk.h>
#include <stdarg.h>
#include <PCIbus.h>
#include "PCIproto.h"
#include "PCIdbg.h"
#include "PciEnum.h"
#ifndef _PREFAST_
#pragma warning(disable: 4068) // Disable pragma warnings
#endif

#ifdef DEBUG
//
// These defines must match the ZONE_* defines in PCIdbg.h
//
#define DBG_ERROR      1
#define DBG_WARNING    2
#define DBG_FUNCTION   4
#define DBG_INIT       8
#define DBG_ENUM      16
#define DBG_ORDER     32
#define DBG_RSRC     64

DBGPARAM dpCurSettings = {
    TEXT("PCIBUS"), {
    TEXT("Errors"),TEXT("Warnings"),TEXT("Functions"),TEXT("Initialization"),
    TEXT("Enumeration"),TEXT("Load Order"),TEXT("Resource"),TEXT("Undefined"),
    TEXT("Undefined"),TEXT("Undefined"),TEXT("Undefined"),TEXT("Undefined"),
    TEXT("Undefined"),TEXT("Undefined"),TEXT("Undefined"),TEXT("Undefined") },
    DBG_ERROR | DBG_WARNING
};
#endif  // DEBUG

//
// Function prototypes
//
static HANDLE
Enumerate(
    LPCWSTR EnumKey, LPCWSTR lpActiveKey
    );

static BOOL
RegGetFlags(
    PPCI_DEV_INFO pInfo 
    );
    
static DWORD
MinVal(
    DWORD List[],
    DWORD Len
    );
    
static BOOL
PCIEnum(
    LPCWSTR EnumKey, DWORD dwBusNumberBase, DWORD dwBusNumberLength
    );

static BOOL
PCIRegClean(
    LPCWSTR EnumKey
    );

static BOOL
PCIReg(
    PPCI_DEV_INFO pInfo
    );

static DWORD
RegMatchOne(
    PPCI_DEV_INFO pInfo,
    HKEY Key,
    LPCWSTR DevName
    );

static BOOL
RegCopy(
    PPCI_DEV_INFO pInfo
    );

static BOOL
RegCopyKey(
    HKEY TKey,
    HKEY IKey
    );

static BOOL
RegSetVal(
    HKEY Key,
    LPCWSTR ValName,
    DWORD ValType, 
    const PBYTE ValData,
    DWORD ValLen
    );

static BOOL
RegSetList(
    HKEY Key,
    LPCWSTR ValName,
    DWORD Num,
    DWORD List[]
    );
    
static BOOL
RegGetList(
    HKEY Key,
    LPCWSTR ValName,
    PDWORD pNum,
    DWORD List[]
    );

static BOOL 
RegSetConfig(
    PPCI_DEV_INFO pInfo 
    );
    
static BOOL
RequestSysIntr(
    DWORD Irq,
    DWORD *pSysIntr
    );

static PPCI_CFG_INFO
AddConfigInfo(
    HMODULE hDll,
    PFN_CONFIGENTRY ConfigEntryFn
    );

static void
DelConfigInfoList(
    void
    );
    
//
// Global variables
//
PPCI_CFG_INFO g_ConfigInfoList = NULL;
CRITICAL_SECTION    g_CSection;

//
// Process attach/detach API.
//
BOOL WINAPI
DllMain(
     HINSTANCE hinstDll,             /*@parm Instance pointer. */
     DWORD    dwReason,              /*@parm Reason routine is called. */
     LPVOID    lpReserved            /*@parm system parameter. */
     )
{
    if (dwReason == DLL_PROCESS_ATTACH) {
        DEBUGREGISTER(hinstDll);
        DEBUGMSG(ZONE_INIT, (L"PCIBUS!DllMain: process attach\r\n"));
        InitializeCriticalSection( &g_CSection );
        DisableThreadLibraryCalls((HMODULE)hinstDll);
    }

    if (dwReason == DLL_PROCESS_DETACH) {
        DEBUGMSG(ZONE_INIT, (L"PCIBUS!DllMain: process detach\r\n"));
        DeleteCriticalSection( &g_CSection );
    }

    return TRUE;
}


//
// PCIbus driver entry point.
//
HANDLE
Init(
    LPCWSTR ActiveKey
    )
{
    HKEY Key;
    DWORD Status;
    WCHAR EnumKey[DEVKEY_LEN];
    DWORD KeyLen;
    HANDLE hReturn = NULL;

    // Signal Entry Allow.
    EnterCriticalSection( &g_CSection ) ;
    //
    // Open the Active key and find the client key.
    //
    Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, ActiveKey, 0, 0, &Key);
    if (Status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!Init RegOpenKeyEx(%s) returned %d.\r\n", ActiveKey, Status));
        LeaveCriticalSection( &g_CSection );
        return (HANDLE)0;
    }

    KeyLen = sizeof(EnumKey);
    Status = RegQueryValueEx(Key, DEVLOAD_DEVKEY_VALNAME, NULL, NULL, (PUCHAR)EnumKey, &KeyLen);
    if (Status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!Init RegQueryValueEx(%s\\%s) returned %d.\r\n", ActiveKey, DEVLOAD_DEVKEY_VALNAME, Status));
        RegCloseKey(Key);
        LeaveCriticalSection( &g_CSection );
        return (HANDLE)0;
    }

    RegCloseKey(Key);
    
    hReturn = Enumerate(EnumKey,ActiveKey);
    LeaveCriticalSection( &g_CSection );
    
    return hReturn;
}


//
//  De-init entry point.
//
void
Deinit(
    HANDLE hInitKey
    )
{
    if (hInitKey)
        DeletePciBus(hInitKey);
}


//
// Enumerate PCIbus.
//
static HANDLE
Enumerate(
    LPCWSTR EnumKey,LPCWSTR lpActiveKey
    )
{
    WCHAR RegPath[DEVKEY_LEN];
    PCI_DEV_INFO_EX Info;
    size_t MaxKeyLen = DEVKEY_LEN - (wcslen(L"\\") + wcslen(PCIBUS_INSTANCE_KEYNAME) + 1);

    // If EnumKey too long, return with error
    if (wcslen(EnumKey) > MaxKeyLen) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!Enumerate: ERROR: EnumKey '%s' exceeds the allowed %d characters\r\n", EnumKey, MaxKeyLen));
        return NULL;
    }

    PCIInitInfo(EnumKey, 0, 0, 0, NULL, &Info.pciDevice);

    if (!RegGetInfo(&Info.pciDevice)) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!Enumerate: RegGetInfo failed\r\n"));
        return NULL;
    }
    if (!RegGetExtraDeviceInfo(&Info)) {
        Info.PrefetchMemBase.Num = 0;
        Info.PrefetchMemLen.Num = 0;
        Info.dwBusNumberBase = 0;
        Info.dwBusNumberLength = PCI_MAX_BUS;
    }
    
    if (Info.pciDevice.Configure) {
        // Configure bus
        if (!PCICfg(&Info)) {
            DEBUGMSG(ZONE_ERROR, (L"PCIBUS!Enumerate: PCICfg failed\r\n"));
            return NULL;
        }
    }

    // Clean out devices from under PCI\Instance sub-key that have no physical device available.  This is
    // to prevent device drivers from being loaded for devices that have been removed while suspended and while
    // retaining the registry settings.
    wcscpy(RegPath, EnumKey);
    wcscat(RegPath, L"\\");
    wcscat(RegPath, PCIBUS_INSTANCE_KEYNAME);

    PCIRegClean(RegPath);
    
    // Enumerate bus
    if (!PCIEnum(EnumKey,Info.dwBusNumberBase, Info.dwBusNumberLength)) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!Enumerate: PCIEnum failed\r\n"));
        return NULL;
    }

    // Start DLL under PCI\Instance key to load drivers (usually RegEnum.dll)
    return CreatePciBus(lpActiveKey,RegPath);
}


//
// Get registry information.
//
BOOL
RegGetInfo(
    PPCI_DEV_INFO pInfo
    )
{
    HKEY Key;
    DWORD Status;
    DWORD ValLen;
    DWORD ValType;
    DWORD Val;
    BOOL ConfigDllExists = TRUE;
    BOOL DllExists = TRUE;

    //
    // Open key to be enumerated
    //
    Status = RegOpenKeyEx(
                HKEY_LOCAL_MACHINE,
                pInfo->RegPath,
                0,
                0,
                &Key);
                
    if (Status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR,
            (TEXT("PCIBUS!RegGetInfo RegOpenKeyEx(%s) returned %d.\r\n"),
            pInfo->RegPath, Status));
        return FALSE;
    }

    //
    // Read NoConfig value
    //
    ValLen = sizeof(Val);
    Status = RegQueryValueEx(Key, PCIBUS_NOCONFIG_VALNAME, NULL, &ValType, (PUCHAR)&Val, &ValLen);            
    if ((Status == ERROR_SUCCESS) && (Val != 0)) {
        // If NoConfig exists and non-zero, don't configure bus
        pInfo->Configure = FALSE;
        RegCloseKey(Key);
        return TRUE;
    } else {
        pInfo->Configure = TRUE;
    }

    //
    // Read MemBase and MemLen values.
    //
    pInfo->MemBase.Num = PCI_TYPE0_ADDRESSES;
    if (!RegGetList(Key, PCIBUS_MEMBASE_VALNAME, &pInfo->MemBase.Num, pInfo->MemBase.Reg) && (pInfo->MemBase.Num == PCI_TYPE0_ADDRESSES)) {
        DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegGetInfo: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
            pInfo->RegPath, PCIBUS_MEMBASE_VALNAME, PCI_TYPE0_ADDRESSES));
    }

    pInfo->MemLen.Num = PCI_TYPE0_ADDRESSES;
    if (!RegGetList(Key, PCIBUS_MEMLEN_VALNAME, &pInfo->MemLen.Num, pInfo->MemLen.Reg) && (pInfo->MemLen.Num == PCI_TYPE0_ADDRESSES)) {
        DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegGetInfo: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
            pInfo->RegPath, PCIBUS_MEMLEN_VALNAME, PCI_TYPE0_ADDRESSES));
    }

    //
    // Read IoBase and IoLen values
    //
    pInfo->IoBase.Num = PCI_TYPE0_ADDRESSES;
    if (!RegGetList(Key, PCIBUS_IOBASE_VALNAME, &pInfo->IoBase.Num, pInfo->IoBase.Reg) && (pInfo->IoBase.Num == PCI_TYPE0_ADDRESSES)) {
        DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegGetInfo: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items\r\n",
            pInfo->RegPath, PCIBUS_IOBASE_VALNAME, PCI_TYPE0_ADDRESSES));
    }

    pInfo->IoLen.Num = PCI_TYPE0_ADDRESSES;
    if (!RegGetList(Key, PCIBUS_IOLEN_VALNAME, &pInfo->IoLen.Num, pInfo->IoLen.Reg) && (pInfo->IoLen.Num == PCI_TYPE0_ADDRESSES)) {
        DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegGetInfo: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items\r\n",
            pInfo->RegPath, PCIBUS_IOLEN_VALNAME, PCI_TYPE0_ADDRESSES));
    }

    //
    // Read ConfigEntry value (if it exists)
    //
    ValLen = sizeof(pInfo->ConfigEntryName);
    Status = RegQueryValueEx(Key, PCIBUS_CONFIGENTRY_VALNAME, NULL, &ValType, (PUCHAR)pInfo->ConfigEntryName, &ValLen);            
    if ((Status == ERROR_SUCCESS) && (ValType == PCIBUS_CONFIGENTRY_VALTYPE)) {
        // ConfigEntry value exists
        pInfo->ConfigEntry = TRUE;
    } else {
        pInfo->ConfigEntry = FALSE;
        wcscpy(pInfo->ConfigEntryName, L"");
        ConfigDllExists = FALSE;
    }
    
    if (pInfo->ConfigEntry) {
        //
        // Read ConfigDllName value (if it exists)
        //
        ValLen = sizeof(pInfo->ConfigDllName);
        Status = RegQueryValueEx(Key, PCIBUS_CONFIGDLL_VALNAME, NULL, &ValType, (PUCHAR)pInfo->ConfigDllName, &ValLen);            
        if ((Status != ERROR_SUCCESS) || (ValType != PCIBUS_CONFIGDLL_VALTYPE)) {
            // ConfigDllName value doesn't exist
            wcscpy(pInfo->ConfigDllName, L"");
            ConfigDllExists = FALSE;
        }
    }
        
    //
    // Read Dll value (if it exists)
    //
    ValLen = sizeof(pInfo->DllName);
    Status = RegQueryValueEx(Key, DEVLOAD_DLLNAME_VALNAME, NULL, &ValType, (PUCHAR)pInfo->DllName, &ValLen);            
    if ((Status != ERROR_SUCCESS) || (ValType != DEVLOAD_DLLNAME_VALTYPE)) {
        // Dll value doesn't exist
        wcscpy(pInfo->DllName, L"");
        DllExists = FALSE;
    }

    // Make sure there is a DLL to load with the ConfigEntry
    if (pInfo->ConfigEntry) {
        if (!ConfigDllExists && !DllExists) {
            // Neither ConfigDll or Dll names exist, so invalidate ConfigEntry
            DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegGetInfo: Registry value '%s\\%s' exists but no '%s' or '%s' values are specified.  Ignoring '%s' value.\r\n",
                pInfo->RegPath, PCIBUS_CONFIGENTRY_VALNAME, PCIBUS_CONFIGDLL_VALNAME, DEVLOAD_DLLNAME_VALNAME, PCIBUS_CONFIGENTRY_VALNAME));
            
            pInfo->ConfigEntry = FALSE;
            wcscpy(pInfo->ConfigEntryName, L"");
        } else if (!ConfigDllExists && DllExists) {
            // No ConfigDll value, so use Dll value for ConfigEntry
            wcscpy(pInfo->ConfigDllName, pInfo->DllName);
        }
    }

    //
    // Read Flags value (if it exists)
    //
    ValLen = sizeof(pInfo->DevFlags);
    Status = RegQueryValueEx(Key, DEVLOAD_FLAGS_VALNAME, NULL, &ValType, (PUCHAR)&Val, &ValLen);            
    if ((Status == ERROR_SUCCESS) && (ValType == DEVLOAD_FLAGS_VALTYPE)) {
        // Flags value exists
        pInfo->DevFlags = Val;
    }

    //
    // Read Command value (if it exists)
    //
    ValLen = sizeof(Val);
    Status = RegQueryValueEx(Key, PCIBUS_COMMAND_VALNAME, NULL, &ValType, (PUCHAR)&Val, &ValLen);            
    if ((Status == ERROR_SUCCESS) && (ValType == PCIBUS_COMMAND_VALTYPE)) {
        // Command value exists
        pInfo->Command = Val;
    }

    //
    // Read BridgeCommand value (if it exists)
    //
    ValLen = sizeof(Val);
    Status = RegQueryValueEx(Key, PCIBUS_BRIDGECONTROL_VALNAME, NULL, &ValType, (PUCHAR)&Val, &ValLen);            
    if ((Status == ERROR_SUCCESS) && (ValType == PCIBUS_BRIDGECONTROL_VALTYPE)) {
        // BridgeControl value exists
        pInfo->BridgeControl = Val;
    }

    //
    // Read Latency value (if it exists)
    //
    ValLen = sizeof(Val);
    Status = RegQueryValueEx(Key, PCIBUS_LATENCY_VALNAME, NULL, &ValType, (PUCHAR)&Val, &ValLen);            
    if ((Status == ERROR_SUCCESS) && (ValType == PCIBUS_LATENCY_VALTYPE)) {
        // Latency value exists
        pInfo->Latency = Val;
    }

    //
    // Read Latency value (if it exists)
    //
    ValLen = sizeof(Val);
    Status = RegQueryValueEx(Key, PCIBUS_SECONDARYLATENCY_VALNAME, NULL, &ValType, (PUCHAR)&Val, &ValLen);            
    if ((Status == ERROR_SUCCESS) && (ValType == PCIBUS_SECONDARYLATENCY_VALTYPE)) {
        // Latency value exists
        pInfo->SecondaryLatency = Val;
    }
    
    RegCloseKey(Key);
    
    return TRUE;
}
//
// Get Prefetch Memory Window from registry.
//
BOOL    
RegGetExtraDeviceInfo(
    PPCI_DEV_INFO_EX pInfo
    )
{
    HKEY Key;
    DWORD Status;
    BOOL ConfigDllExists = TRUE;
    BOOL DllExists = TRUE;
    DWORD dwValType, ValLen;

    //
    // Open key to be enumerated
    //
    Status = RegOpenKeyEx(
                HKEY_LOCAL_MACHINE,
                pInfo->pciDevice.RegPath,
                0,
                0,
                &Key);
                
    if (Status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR,
            (TEXT("PCIBUS!RegGetExtraDeviceInfo RegOpenKeyEx(%s) returned %d.\r\n"),
            pInfo->pciDevice.RegPath, Status));
        return FALSE;
    }
    //
    // Read BusNumber Base and Length.
    //
    ValLen = sizeof(pInfo->dwBusNumberBase);
    if (RegQueryValueEx(Key, PCIBUS_BUSNUMBERBASE_VALNAME, NULL, &dwValType, (PBYTE)&(pInfo->dwBusNumberBase), &ValLen) == ERROR_SUCCESS && 
            dwValType == PCIBUS_BUSNUMBERBASE_VALTYPE ) {
        if (RegQueryValueEx(Key, PCIBUS_BUSNUMBERLENGTH_VALNAME, NULL, &dwValType, (PBYTE)&(pInfo->dwBusNumberLength), &ValLen) == ERROR_SUCCESS && 
                dwValType == PCIBUS_BUSNUMBERLENGTH_VALTYPE ) {
        }
        else {
            pInfo->dwBusNumberLength = PCI_MAX_BUS ;
        }
    }
    else {
        pInfo->dwBusNumberBase = 0;
        pInfo->dwBusNumberLength = PCI_MAX_BUS;
    }
    pInfo->dwBusNumberLength = min (pInfo->dwBusNumberLength, PCI_MAX_BUS);
    //
    // Read MemBase and MemLen values.
    //
    pInfo->PrefetchMemBase.Num = PCI_TYPE0_ADDRESSES;
    if (!RegGetList(Key, PCIBUS_PREFETCH_MEMBASE_VALNAME, &pInfo->PrefetchMemBase.Num, pInfo->PrefetchMemBase.Reg) && (pInfo->PrefetchMemBase.Num == PCI_TYPE0_ADDRESSES)) {
        DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegGetExtraDeviceInfo: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
            pInfo->pciDevice.RegPath, PCIBUS_PREFETCH_MEMBASE_VALNAME, PCI_TYPE0_ADDRESSES));
    }

    pInfo->PrefetchMemLen.Num = PCI_TYPE0_ADDRESSES;
    if (!RegGetList(Key, PCIBUS_PREFETCH_MEMLEN_VALNAME, &pInfo->PrefetchMemLen.Num, pInfo->PrefetchMemLen.Reg) && (pInfo->PrefetchMemLen.Num == PCI_TYPE0_ADDRESSES)) {
        DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegGetExtraDeviceInfo: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
            pInfo->pciDevice.RegPath, PCIBUS_PREFETCH_MEMLEN_VALNAME, PCI_TYPE0_ADDRESSES));
    }
    RegCloseKey(Key);    
    return TRUE;

}


//
// Get Flags value from registry.
//
static BOOL
RegGetFlags(
    PPCI_DEV_INFO pInfo 
    )
{
    HKEY Key;
    DWORD Status;
    DWORD ValLen, ValType, Val;
    
    //
    // Open key to be enumerated
    //
    Status = RegOpenKeyEx(
                HKEY_LOCAL_MACHINE,
                pInfo->RegPath,
                0,
                0,
                &Key);
                
    if (Status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR,
            (TEXT("PCIBUS!RegGetFlags RegOpenKeyEx(%s) returned %d.\r\n"),
            pInfo->RegPath, Status));
        return FALSE;
    }
    
    //
    // Read Flags value (if it exists)
    //
    ValLen = sizeof(pInfo->DevFlags);
    Status = RegQueryValueEx(Key, DEVLOAD_FLAGS_VALNAME, NULL, &ValType, (PUCHAR)&Val, &ValLen);            
    if ((Status == ERROR_SUCCESS) && (ValType == DEVLOAD_FLAGS_VALTYPE)) {
        // Flags value exists
        pInfo->DevFlags = Val;
    } else {
        pInfo->DevFlags = DEVFLAGS_NONE;
    }

    RegCloseKey(Key);
    
    return TRUE;
}


//
// Load configuration routine DLL and map entry point.
//
BOOL
LoadConfigEntry(
    PPCI_DEV_INFO pInfo
    )
{
    PFN_CONFIGENTRY ConfigEntryFn;
    HMODULE hDll;
    
    // If ConfigEntry, load config DLL and get ConfigEntry address
    if (pInfo->ConfigEntry) {
        if (pInfo->DevFlags & DEVFLAGS_LOADLIBRARY) {
           hDll = LoadLibrary(pInfo->ConfigDllName);
        } else {
           hDll = LoadDriver(pInfo->ConfigDllName);
        }

        if (hDll != NULL) {
            // Driver/library load successful, get ConfigEntry address
            ConfigEntryFn = (PFN_CONFIGENTRY)GetProcAddress(hDll, pInfo->ConfigEntryName);
            if (ConfigEntryFn == NULL) {
                DEBUGMSG(ZONE_ERROR, (L"PCIBUS!GetConfigEntry: GetProcAddr(%s, %s) failed with error %d.\r\n",
                    pInfo->DllName, pInfo->ConfigEntryName, GetLastError()));

                FreeLibrary(hDll);
                    
                goto GetConfigEntry_EXIT;
            }

            // Create ConfigInfo node and place into list
            pInfo->ConfigInfo = AddConfigInfo(hDll, ConfigEntryFn);
            if (pInfo->ConfigInfo == NULL) {
                DEBUGMSG(ZONE_ERROR, (L"PCIBUS!GetConfigEntry: AddConfigInfo( ) failed, out of memory.\r\n"));

                FreeLibrary(hDll);
                    
                goto GetConfigEntry_EXIT;
            }

            return TRUE;
        } else {
            // Driver/library load not successful, ConfigEntry becomes false
            DEBUGMSG(ZONE_ERROR, (L"PCIBUS!GetConfigEntry: LoadDriver/Library(%s) failed with error %d.\r\n", pInfo->ConfigDllName, GetLastError()));

            goto GetConfigEntry_EXIT;
        }
    } 
    
GetConfigEntry_EXIT:
    pInfo->ConfigEntry = FALSE;
    wcscpy(pInfo->ConfigEntryName, L"");
    wcscpy(pInfo->DllName, L"");
    pInfo->ConfigInfo = NULL;

    return FALSE;
}


//
// Unload configuration routine DLLs.
//
void
UnloadConfigEntrys(
    void
    )
{
    DelConfigInfoList( );
}


//
// Add configuration DLL handles to tracking list.
//
static PPCI_CFG_INFO
AddConfigInfo(
    HMODULE hDll,
    PFN_CONFIGENTRY ConfigEntryFn
    )
{
    PPCI_CFG_INFO ci = (PPCI_CFG_INFO)LocalAlloc(0, sizeof(PCI_CFG_INFO));

    if (!ci) return NULL;

    ci->hDll = hDll;
    ci->ConfigEntryFn = ConfigEntryFn;
    ci->Next = g_ConfigInfoList;

    g_ConfigInfoList = ci;
    
    return ci;
}


//
// Delete configuration DLL handles from tracking list and unload them.
//
static void
DelConfigInfoList(
    void
    )
{
    PPCI_CFG_INFO ni;
    PPCI_CFG_INFO ci = g_ConfigInfoList;
    
    while (ci) {
        ni = ci->Next;
        FreeLibrary(ci->hDll);
        LocalFree(ci);
        ci = ni;
    }

    g_ConfigInfoList = NULL;
}             


#ifdef DEBUG
#define DUMPPCICFG(X) { \
    if (ZONE_INIT) DumpPciConfig(X); \
}
#else
#define DUMPPCICFG(X) ((void)0)
#endif
BOOL IsSystemPhase1()
{
    HANDLE hEvent;
    static BOOL Phase1 = TRUE;

    // Find out whether or not we're in boot phase 1 or 2
    if ( Phase1 && (hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, TEXT("SYSTEM/BootPhase2")))!=NULL) {
        if (WaitForSingleObject(hEvent, 0) == WAIT_OBJECT_0) {
            // We're in boot phase 2
            Phase1 = FALSE;
        }
        CloseHandle(hEvent);
    }
    return Phase1;
}

//
// Enumerate PCIbus.
//
static BOOL
PCIEnum(
    LPCWSTR EnumKey, DWORD dwBusNumberBase, DWORD dwBusNameLength
    )
{
    PCI_SLOT_NUMBER slotNumber;
    PCI_COMMON_CONFIG pciConfig;
    PCI_DEV_INFO Info;
    DWORD bus;
    int device, function;
    int length;
    BOOL Phase1 =  IsSystemPhase1();
    DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!PCIEnum(%s), Boot phase %d\r\n", EnumKey, (Phase1) ? 1 : 2));
    
    slotNumber.u.AsULONG = 0;
    
    // Scan bus
    for (bus = dwBusNumberBase; bus < dwBusNumberBase + dwBusNameLength; bus++) {
        for (device = 0; device < PCI_MAX_DEVICES; device++) {
            slotNumber.u.bits.DeviceNumber = device;

            for (function = 0; function < PCI_MAX_FUNCTION; function++) {
                slotNumber.u.bits.FunctionNumber = function;
                pciConfig.VendorID = 0xFFFF;
                pciConfig.HeaderType = 0;

                length = HalGetBusData(
                    PCIConfiguration,
                    bus,
                    slotNumber.u.AsULONG,
                    &pciConfig,
                    sizeof(pciConfig) - sizeof(pciConfig.DeviceSpecific)
                );

                if (length != (sizeof(pciConfig) - sizeof(pciConfig.DeviceSpecific)) ||
                    (pciConfig.DeviceID == PCI_INVALID_DEVICEID) || (pciConfig.VendorID == PCI_INVALID_VENDORID) || (pciConfig.VendorID == 0)) {
                    // No function found
                    if (function != 0) {
                        // If a multi-function device, continue to next function
                        continue;
                    } else {
                        // If not a multi-function device, continue to next device
                        break;
                    }
                }

                // Initialize out device info
                PCIInitInfo(EnumKey, bus, device, function, &pciConfig, &Info);

                // Get BAR length information, either from registry or from h/w
                PCIGetBARs(&Info);

                // Complete device's final initialization step (if it has one)
                PCIInitDevice(&Info);

                // Request I/O and IRQ resources for device (phase 1 only)
                if (Phase1 && !PCIRequestResources(&Info)) {
                    RETAILMSG(1, (L"PCIbus!PCIEnum: WARNING: Resource request for device %d:%d:%d failed\r\n", bus, device, function));
                }
                
                // Search for match in registry and configure
                PCIReg(&Info);

                // Dump PCI configuration register to debug out (DEBUG only)
                DUMPPCICFG(&Info);

                if (function == 0 && !(pciConfig.HeaderType & PCI_MULTIFUNCTION)) {
                    // Not a multi-function device, continue on to next device
                    break;
                }
            }

            // Error on HalGetBusData, move on to next bus
            if (length == 0) {
                break;
            }
        }

        if (length == 0 && device == 0) {
            // Bus doesn't exist, exit
            break;
        }
    }

    return TRUE;
} 


//
// Find a match for a device in the registry.
//
static BOOL
PCIReg(
    PPCI_DEV_INFO pInfo
    )
{
    WCHAR SavePath[DEVKEY_LEN];

    DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!PCIReg+(%s, %d, %d, %d)\r\n", pInfo->RegPath, pInfo->Bus, pInfo->Device, pInfo->Function));

    wcsncpy(SavePath, pInfo->RegPath, DEVKEY_LEN - 1);
    SavePath[DEVKEY_LEN - 1] = L'\0';    // make sure longest string is terminated

    // Fail if key name too long
    if (wcslen(SavePath) + wcslen(L"\\") + wcslen(PCIBUS_INSTANCE_KEYNAME) >= DEVKEY_LEN) {
        wcscpy(pInfo->RegPath, SavePath);
        return FALSE;
    }
    
    // Check for exact match under Instance sub-key
    wcscat(pInfo->RegPath, L"\\");
    wcscat(pInfo->RegPath, PCIBUS_INSTANCE_KEYNAME);
    
    if (RegMatch(pInfo) == PCIBUS_MATCH_ALL) {
        // Get registry flags
        RegGetFlags(pInfo);
        
        // There is an exact instance match already, so don't do anything except re-request SYSINTR
        return RegSetConfig(pInfo);
    }

    // Fail if key name too long
    if (wcslen(SavePath) + wcslen(L"\\") + wcslen(PCIBUS_TEMPLATE_KEYNAME) >= DEVKEY_LEN) {
        wcscpy(pInfo->RegPath, SavePath);
        return FALSE;
    }
    
    // No instance exists, so check for potential match under Template key
    wcscpy(pInfo->RegPath, SavePath);
    wcscat(pInfo->RegPath, L"\\");
    wcscat(pInfo->RegPath, PCIBUS_TEMPLATE_KEYNAME);
    
    if (RegMatch(pInfo) == PCIBUS_MATCH_NONE) {
        // There is nothing that matches the device in the registry, return with error
        wcscpy(pInfo->RegPath, SavePath);
        return FALSE;
    }

    // Get registry flags
    RegGetFlags(pInfo);
    
    // A match in the registry exists, copy Template to Instance and generate device location info
    return RegCopy(pInfo);   
}
    

//
// Function to clean stale registry entries
//
static BOOL
PCIRegClean(LPCWSTR EnumKey)
{
    HKEY Key;
    DWORD NumDevKeys;
    DWORD LongSubKey;
    DWORD status;
    DWORD ValLen;
    PWCHAR DelBuf;
    DWORD RegEnum;

    PCI_DEV_INFO Info;
    PCI_COMMON_CONFIG PciCfg;
    DWORD NumValues;

    PCI_SLOT_NUMBER SlotNumber;
    DWORD Length;

    DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!PCIRegClean(%s)\r\n", EnumKey));

    // Open key to be cleaned
    status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, EnumKey, 0, 0, &Key);
                
    if (status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!PCIRegClean RegOpenKeyEx(%s) returned %d.\r\n", EnumKey, status));
        return FALSE;
    }

    // Find out how many sub-keys there are
    status = RegQueryInfoKey(Key, NULL, NULL, NULL, &NumDevKeys, &LongSubKey, NULL, NULL, NULL, NULL, NULL, NULL);                 
    if (status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!PCIRegClean RegQueryInfoKey(Class) returned %d.\r\n", status));
        RegCloseKey(Key);
        return FALSE;
    }
    
    DEBUGMSG(ZONE_ENUM, (L"PCIBUS!PCIRegClean %d devices to check\r\n", NumDevKeys));

    // Allocate buffer for keys to delete
    LongSubKey++;
    if (!(DelBuf = (PWCHAR)LocalAlloc(LPTR, (NumDevKeys * LongSubKey) * sizeof(WCHAR)))) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!PCIRegClean LocalAlloc failed.\r\n"));
        RegCloseKey(Key);
        return FALSE;
    }
        
    //
    // Read the device list from the registry and check to make sure a device exists for each one.
    //
    for (RegEnum = 0; RegEnum < NumDevKeys; RegEnum++) {
        // Get sub-key according to RegEnum
        ValLen = LongSubKey;
        status = RegEnumKeyEx(Key, RegEnum, &DelBuf[RegEnum * LongSubKey], &ValLen, NULL, NULL, NULL, NULL);
        if (status != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_ERROR, (L"PCIBUS!PCIRegClean RegEnumKeyEx(%d) returned %d\r\n", RegEnum, status));
            LocalFree(DelBuf);
            RegCloseKey(Key);
            return FALSE;
        }

        // Get information about device in registry
        wcscpy(Info.RegPath, EnumKey);
        wcscat(Info.RegPath, TEXT("\\"));
        wcscat(Info.RegPath, &DelBuf[RegEnum * LongSubKey]);
        
        NumValues = RegReadValues(Key, &DelBuf[RegEnum * LongSubKey], NULL, NULL,
                        PCIBUS_BUSNUMBER_VALNAME, PCIBUS_BUSNUMBER_VALTYPE, 0, &Info.Bus, sizeof(Info.Bus),
                        PCIBUS_DEVICENUMBER_VALNAME, PCIBUS_DEVICENUMBER_VALTYPE, 0, &Info.Device, sizeof(Info.Device),
                        PCIBUS_FUNCTIONNUMBER_VALNAME, PCIBUS_FUNCTIONNUMBER_VALTYPE, 0, &Info.Function, sizeof(Info.Function),
                        NULL);

        if (NumValues != 3) {
            // No device location, skip
            DelBuf[RegEnum * LongSubKey] = (WCHAR)'\0';
            continue;
        }

        DEBUGMSG(ZONE_ENUM, (L"PCIBUS!PCIRegClean: Examining '%s' (%d/%d/%d)\r\n", Info.RegPath, Info.Bus, Info.Device, Info.Function));
        
        // Read device information
        SlotNumber.u.bits.DeviceNumber = Info.Device;
        SlotNumber.u.bits.FunctionNumber = Info.Function;
        PciCfg.VendorID = 0xFFFF;
        Length = HalGetBusData(PCIConfiguration, Info.Bus, SlotNumber.u.AsULONG, &PciCfg, sizeof(PciCfg) - sizeof(PciCfg.DeviceSpecific));            
        if (Length == 0 || PciCfg.VendorID == 0xFFFF) {
            // Device does not exist, remove registry entry
            DEBUGMSG(ZONE_WARNING, (L"PCIBUS!PCIRegClean: No device exists (%d/%d/%d) for key '%s', removing.\r\n",
                Info.Bus, Info.Device, Info.Function, Info.RegPath));

            continue;
        }

        Info.Cfg = &PciCfg;

        // Match device with registry information
        if (RegMatchOne(&Info, Key, &DelBuf[RegEnum * LongSubKey]) != PCIBUS_MATCH_ALL) {
            // Not an exact match (i.e. another card is in slot), remove registry entry
            DEBUGMSG(ZONE_WARNING, (L"PCIBUS!PCIRegClean: No device match (%d/%d/%d) for key '%s', removing.\r\n",
                Info.Bus, Info.Device, Info.Function, Info.RegPath));

            continue;
        }

        // Device matches, go onto next one
        DelBuf[RegEnum * LongSubKey] = (WCHAR)'\0';
    }

    // Remove registry entries that didn't have matching device
    //
    // Note: this removes everything, including any instance information that was originally set in the
    // registry.  Unfortunately, even if we tried to keep that information around, if the device that
    // was removed was again inserted, it wouldn't be the same instance.  Effectively this instance is
    // lost.
    for (RegEnum = 0; RegEnum < NumDevKeys; RegEnum++) {
        if (DelBuf[RegEnum * LongSubKey] != (WCHAR)'\0')
            RegDeleteKey(Key, &DelBuf[RegEnum * LongSubKey]);
    }

    LocalFree(DelBuf);
    RegCloseKey(Key);
    return TRUE;
}   // Enum


//
// Device/registry matching routine.
//
static DWORD
RegMatchOne(
    PPCI_DEV_INFO pInfo,
    HKEY Key,
    LPCWSTR DevName
    )
{
    HKEY DevKey;
    DWORD status;
    DWORD ValType;
    DWORD ValLen;
    DWORD Match = 0;
    DWORD BestMatch = 0;
    WCHAR RegPath[DEVKEY_LEN];

    DWORD ClassVal;
    DWORD SubClassVal;
    DWORD Val32;

    DWORD ProgIFs[PCI_MAX_REG_LIST], ProgIFNum;
    DWORD VendorIDs[PCI_MAX_REG_LIST], VendorIDNum;
    DWORD DeviceIDs[PCI_MAX_REG_LIST], DeviceIDNum;
    DWORD SubVendorIDs[PCI_MAX_REG_LIST], SubVendorIDNum;
    DWORD SubSystemIDs[PCI_MAX_REG_LIST], SubSystemIDNum;
    DWORD RevisionIDs[PCI_MAX_REG_LIST], RevisionIDNum;

    DWORD ListNum[4], MaxListNum;
    DWORD i;
    

    DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!RegMatchOne+(%s, %d, %d, %d)\r\n", pInfo->RegPath, pInfo->Bus, pInfo->Device, pInfo->Function));

        // Open sub-key
        status = RegOpenKeyEx(Key, DevName, 0, 0, &DevKey);
        
        if (status != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (TEXT("PCIBUS!RegMatchOne RegOpenKeyEx(%s) returned %d\r\n"), DevName, status));
            return 0;
        }

        DEBUGMSG(ZONE_ENUM, (TEXT("PCIBUS!RegMatchOne Checking key %s\\%s.\r\n"), pInfo->RegPath, DevName));
                
        //
        // Examine Class value, check against device's base class
        //
        ValLen = sizeof(ClassVal);
        status = RegQueryValueEx(DevKey, PCIBUS_CLASS_VALNAME, NULL, &ValType, (PUCHAR)&ClassVal, &ValLen);
                    
        if (status != ERROR_SUCCESS) {
            // No Class value found
            goto RegMatchOne_Exit;
        }

        if ((UCHAR)ClassVal != pInfo->Cfg->BaseClass) {
            // Class doesn't match
            Match = 0;
            goto RegMatchOne_Exit;
        } else {
            DEBUGMSG(ZONE_ENUM, (TEXT("PCIBUS!RegMatchOne Match for Class (0x%X) found.\r\n"), ClassVal));
                
            Match++;
        }

        // There is at least a partial match, construct path to device key
        wcscpy(RegPath, pInfo->RegPath);
        wcscat(RegPath, TEXT("\\"));
        wcscat(RegPath, DevName);

        //
        // Examine SubClass value, check against device's sub-class
        //
        ValLen = sizeof(SubClassVal);
        status = RegQueryValueEx(DevKey, PCIBUS_SUBCLASS_VALNAME, NULL, &ValType, (PUCHAR)&SubClassVal, &ValLen);
                    
        if (status != ERROR_SUCCESS) {
            // No SubClass value found
            goto RegMatchOne_Exit;
        }

        if ((UCHAR)SubClassVal != pInfo->Cfg->SubClass) {
            // SubClass doesn't match
            Match = 0;
            goto RegMatchOne_Exit;
        } else {
            DEBUGMSG(ZONE_ENUM, (TEXT("PCIBUS!RegMatchOne Match for SubClass (0x%X) found.\r\n"), SubClassVal));
                
            Match++;
        }

        //
        // Get ProgIF value or list and check against device's programing interface
        //
        ProgIFNum = PCI_MAX_REG_LIST;
        if (!RegGetList(DevKey, PCIBUS_PROGIF_VALNAME, &ProgIFNum, ProgIFs) && (ProgIFNum == PCI_MAX_REG_LIST)) {
            DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
                RegPath, PCIBUS_PROGIF_VALNAME, PCI_MAX_REG_LIST));
        }

        if (!ProgIFNum) {
            // No ProgIF value found
            goto RegMatchOne_Exit;
        }
        
        for (i = 0; i < ProgIFNum; i++) {
            if ((UCHAR)ProgIFs[i] == pInfo->Cfg->ProgIf) break;
        }

        if (i == ProgIFNum) {
            // No match found
            Match = 0;
            goto RegMatchOne_Exit;
        } else {
            DEBUGMSG(ZONE_ENUM, (TEXT("PCIBUS!RegMatchOne Match for ProgIF (0x%X) found.\r\n"), ProgIFs[i]));
            
            Match++;
        }

        //
        // Get Vendor ID, Device ID lists and look for match
        //
        VendorIDNum = PCI_MAX_REG_LIST;
        if (!RegGetList(DevKey, PCIBUS_VENDORID_VALNAME, &VendorIDNum, VendorIDs) && (VendorIDNum == PCI_MAX_REG_LIST)) {
            DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
                RegPath, PCIBUS_VENDORID_VALNAME, PCI_MAX_REG_LIST));
        }

        if (!VendorIDNum) {
            // No VendorID value found
            goto RegMatchOne_Exit;
        }

        DeviceIDNum = PCI_MAX_REG_LIST;
        if (!RegGetList(DevKey, PCIBUS_DEVICEID_VALNAME, &DeviceIDNum, DeviceIDs) && (DeviceIDNum == PCI_MAX_REG_LIST)) {
            DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
                RegPath, PCIBUS_DEVICEID_VALNAME, PCI_MAX_REG_LIST));
        }

        if (!DeviceIDNum) {
            // No DeviceID value found
            goto RegMatchOne_Exit;
        }

        if ((pInfo->Cfg->HeaderType & ~PCI_MULTIFUNCTION) == PCI_DEVICE_TYPE) {
            // Get Subsystem Vendor ID and Subsystem ID lists only if h/w is a device
            SubVendorIDNum = PCI_MAX_REG_LIST;
            if (!RegGetList(DevKey, PCIBUS_SUBVENDORID_VALNAME, &SubVendorIDNum, SubVendorIDs) && (VendorIDNum == PCI_MAX_REG_LIST)) {
                DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
                    RegPath, PCIBUS_SUBVENDORID_VALNAME, PCI_MAX_REG_LIST));
            }

            SubSystemIDNum = PCI_MAX_REG_LIST;
            if (!RegGetList(DevKey, PCIBUS_SUBSYSTEMID_VALNAME, &SubSystemIDNum, SubSystemIDs) && (SubSystemIDNum == PCI_MAX_REG_LIST)) {
                DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
                    RegPath, PCIBUS_SUBSYSTEMID_VALNAME, PCI_MAX_REG_LIST));
            }

            if (!SubVendorIDNum && SubSystemIDNum) {
                // If the Subsystem ID exists, but not Subsytem Vendor ID, then ignore SubSystem ID
                DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: If registry value '%s\\%s' exists, then '%s\\%s' must exist.  Ignoring.\r\n",
                    RegPath, PCIBUS_SUBSYSTEMID_VALNAME, RegPath, PCIBUS_SUBVENDORID_VALNAME));

                SubSystemIDNum = 0;
            }
        } else {
            SubVendorIDNum = 0;
            SubSystemIDNum = 0;
        }
        
        // Find smallest list length
        ListNum[0] = VendorIDNum;
        ListNum[1] = DeviceIDNum;
        ListNum[2] = SubVendorIDNum ? SubVendorIDNum : PCI_MAX_REG_LIST;
        ListNum[3] = SubSystemIDNum ? SubSystemIDNum : PCI_MAX_REG_LIST;
        MaxListNum = MinVal(ListNum, 4);

        //
        // Adjust list lengths
        //
        if (VendorIDNum > MaxListNum) {
            VendorIDNum = MaxListNum;

             DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value list '%s\\%s' truncated to %d items.\r\n",
                    RegPath, PCIBUS_VENDORID_VALNAME, MaxListNum));
        }

        if (DeviceIDNum > MaxListNum) {
            DeviceIDNum = MaxListNum;

             DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value list '%s\\%s' truncated to %d items.\r\n",
                    RegPath, PCIBUS_DEVICEID_VALNAME, MaxListNum));
        }

        if (SubVendorIDNum > MaxListNum) {
            SubVendorIDNum = MaxListNum;

             DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value list '%s\\%s' truncated to %d items.\r\n",
                    RegPath, PCIBUS_SUBVENDORID_VALNAME, MaxListNum));
        }

        if (SubSystemIDNum > MaxListNum) {
            SubSystemIDNum = MaxListNum;

             DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value list '%s\\%s' truncated to %d items.\r\n",
                    RegPath, PCIBUS_SUBSYSTEMID_VALNAME, MaxListNum));
        }

        // Walk list pairs looking for match
        for (i = 0; i < MaxListNum; i++) {
            if (((USHORT)VendorIDs[i] == pInfo->Cfg->VendorID) && ((USHORT)DeviceIDs[i] == pInfo->Cfg->DeviceID)) {
                if (!SubVendorIDNum) break;
                
                if ((USHORT)SubVendorIDs[i] == pInfo->Cfg->u.type0.SubVendorID) {
                    if (!SubSystemIDNum) break;

                    if ((USHORT)SubSystemIDs[i] == pInfo->Cfg->u.type0.SubSystemID) break;
                }
            }
        }

        if (i == MaxListNum) {
            // No match found
            Match = 0;
            goto RegMatchOne_Exit;
        } else {
            DEBUGMSG(ZONE_ENUM,
                (TEXT("PCIBUS!RegMatchOne: Match for VendorID (0x%X), DeviceID (0x%X) found.\r\n"), VendorIDs[i], DeviceIDs[i]));

#ifdef DEBUG
            if (SubVendorIDNum) {
                if (SubSystemIDNum) {
                    DEBUGMSG(ZONE_ENUM, (L"PCIBUS!RegMatchOne: Match for SubVendorID (0x%X), SubSystemID (0x%X) found.\r\n",
                        SubVendorIDs[i], SubSystemIDs[i]));
                } else {
                    DEBUGMSG(ZONE_ENUM, (L"PCIBUS!RegMatchOne: Match for SubVendorID (0x%X) found.\r\n",
                        SubVendorIDs[i]));
                }
            }
#endif
            
            Match++;
        }

        //
        // Examine RevisionID value, check against device's revision identification
        //
        RevisionIDNum = PCI_MAX_REG_LIST;
        if (!RegGetList(DevKey, PCIBUS_REVISIONID_VALNAME, &RevisionIDNum, RevisionIDs) && (RevisionIDNum == PCI_MAX_REG_LIST)) {
            DEBUGMSG(ZONE_WARNING, (L"PCIBUS!RegMatchOne: Registry value '%s\\%s' has more than %d items in multi_sz list.  Ignoring the additional items.\r\n",
                RegPath, PCIBUS_REVISIONID_VALNAME, PCI_MAX_REG_LIST));
        }

        if (!RevisionIDNum) {
            // No VendorID value found
            goto RegMatchOne_Exit;
        }

        // Scan list for match
        for (i = 0; i < RevisionIDNum; i++) {
            if ((UCHAR)RevisionIDs[i] == pInfo->Cfg->RevisionID) break;
        }

        if (i == RevisionIDNum) {
            // No match found
            Match = 0;
            goto RegMatchOne_Exit;
        } else {
            DEBUGMSG(ZONE_ENUM, (L"PCIBUS!RegMatchOne: Match for RevisionID (0x%X) found.\r\n", RevisionIDs[i]));
            
            Match++;
        }

        //
        // Examine BusNumber value, check against device's bus number
        //
        ValLen = sizeof(Val32);
        status = RegQueryValueEx(DevKey, PCIBUS_BUSNUMBER_VALNAME, NULL, &ValType, (PUCHAR)&Val32, &ValLen);
                    
        if (status != ERROR_SUCCESS) {
            // No BusNumber value found
            goto RegMatchOne_Exit;
        }

        if (Val32 != pInfo->Bus) {
            // Bus number doesn't match
            Match = 0;
            goto RegMatchOne_Exit;
        } else {
            DEBUGMSG(ZONE_ENUM, (TEXT("PCIBUS!RegMatchOne Match for BusNumber (0x%X) found.\r\n"), Val32));
            
            Match++;
        }

        //
        // Examine DeviceNumber value, check against device's device number
        //
        ValLen = sizeof(Val32);
        status = RegQueryValueEx(DevKey, PCIBUS_DEVICENUMBER_VALNAME, NULL, &ValType, (PUCHAR)&Val32, &ValLen);
                    
        if (status != ERROR_SUCCESS) {
            // No DeviceNumber value found
            goto RegMatchOne_Exit;
        }

        if (Val32 != pInfo->Device) {
            // Device number doesn't match
            Match = 0;
            goto RegMatchOne_Exit;
        } else {
            DEBUGMSG(ZONE_ENUM, (TEXT("PCIBUS!RegMatchOne Match for DeviceNumber (0x%X) found.\r\n"), Val32));
            
            Match++;
        }

        //
        // Examine FunctionNumber value, check against device's function number
        //
        ValLen = sizeof(Val32);
        status = RegQueryValueEx(DevKey, PCIBUS_FUNCTIONNUMBER_VALNAME, NULL, &ValType, (PUCHAR)&Val32, &ValLen);
                    
        if (status != ERROR_SUCCESS) {
            // No FunctionNumber value found
            goto RegMatchOne_Exit;
        }

        if (Val32 != pInfo->Function) {
            // Function number doesn't match
            Match = 0;
            goto RegMatchOne_Exit;
        } else {
            DEBUGMSG(ZONE_ENUM, (TEXT("PCIBUS!RegMatchOne Match for FunctionNumber (0x%X) found.\r\n"), Val32));
            
            Match++;
        }
 
RegMatchOne_Exit:
    RegCloseKey(DevKey);
        
    return Match;
}


//
// Search registry for best device/registry match.
//
DWORD
RegMatch(
    PPCI_DEV_INFO pInfo
    )
{
    HKEY Key;
    DWORD DevLoadOrder;
    DWORD LoadOrder;
    DWORD NumDevKeys;
    DWORD RegEnum;
    DWORD status;
    DWORD ValType;
    DWORD ValLen;
    DWORD Match = 0;
    DWORD BestMatch = 0;
    WCHAR DevName[DEVKEY_LEN];
    WCHAR RegPath[DEVKEY_LEN]; 

    DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!RegMatch+(%s, %d, %d, %d)\r\n", pInfo->RegPath, pInfo->Bus, pInfo->Device, pInfo->Function));

    //
    // Open key to be enumerated
    //
    status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pInfo->RegPath, 0, 0, &Key);
                
    if (status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (TEXT("PCIBUS!RegMatch RegOpenKeyEx(%s) returned %d.\r\n"), pInfo->RegPath, status));
        return 0;
    }

    //
    // Find out how many sub-keys there are
    //
    status = RegQueryInfoKey(
                    Key,
                    NULL,         // class name buffer (lpszClass)
                    NULL,       // ptr to length of class name buffer (lpcchClass)
                    NULL,           // reserved
                    &NumDevKeys,    // ptr to number of subkeys (lpcSubKeys)
                    &ValType,       // ptr to longest subkey name length (lpcchMaxSubKeyLen)
                    &ValLen,        // ptr to longest class string length (lpcchMaxClassLen)
                    &LoadOrder,     // ptr to number of value entries (lpcValues)
                    &DevLoadOrder,  // ptr to longest value name length (lpcchMaxValueNameLen)
                    &ValLen,        // ptr to longest value data length (lpcbMaxValueData)
                    NULL,           // ptr to security descriptor length
                    NULL);          // ptr to last write time
                    
    if (status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (TEXT("PCIBUS!RegMatch RegQueryInfoKey(Class) returned %d.\r\n"), status));
        RegCloseKey(Key);
        return 0;
    }
    
    DEBUGMSG(ZONE_ENUM,
        (TEXT("PCIBUS!RegMatch %d devices to search\r\n"), NumDevKeys));

    //
    // Base registry key
    //
    wcsncpy(RegPath, pInfo->RegPath, DEVKEY_LEN);
    RegPath[DEVKEY_LEN-1]=0;

    //
    // Search for closest match
    //
    RegEnum = 0; 
    
    for (RegEnum = 0; RegEnum < NumDevKeys; RegEnum++) {
        DEBUGMSG(ZONE_ENUM, (TEXT("PCIBUS!RegMatch RegEnum = %d\r\n"), RegEnum));

        // Get sub-key according to RegEnum
        ValLen = sizeof(DevName) / sizeof(WCHAR);
        status = RegEnumKeyEx(Key, RegEnum, DevName, &ValLen, NULL, NULL, NULL, NULL);

        if (status != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_ENUM, (TEXT("PCIBUS!RegMatch RegEnumKeyEx(%d) returned %d\r\n"), RegEnum, status));
            break;
        }

        Match = RegMatchOne(pInfo, Key, DevName);
        
        if (Match > BestMatch) {
            if (wcslen(DevName) + wcslen(pInfo->RegPath) + 1 < DEVKEY_LEN) { // We can handle this registry.
                BestMatch = Match;
                
                // There is at least a partial match, construct path to device key
                wcscpy(RegPath, pInfo->RegPath);
                wcscat(RegPath, TEXT("\\"));
                wcscat(RegPath, DevName);
                RegPath[DEVKEY_LEN-1]=0;
            }
        }
    }

    RegCloseKey(Key);

    wcsncpy(pInfo->RegPath, RegPath,DEVKEY_LEN);
    pInfo->RegPath[DEVKEY_LEN-1] = 0;
    
    return BestMatch;
}
//
// Open Non-Exist Instance Registry Key 
//
#define NUMBER_OF_APPENDIX_BYTES 2
#define MAX_APPENDIX_NUMBER 100
static 
HKEY OpenNonExistInstanceKey(LPCWSTR lpInstancePreFix)
{
    HKEY hReturn=NULL;
    WCHAR IndexStr[DEVKEY_LEN];
    DWORD dwLen;
    DWORD iIndex;
    DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!OpenNonExistInstanceKey+(%s)\r\n", lpInstancePreFix ));
    wcsncpy(IndexStr,lpInstancePreFix,DEVKEY_LEN-1);
    IndexStr[DEVKEY_LEN-1] = 0;
    dwLen=wcslen(IndexStr);
    if (dwLen>= DEVKEY_LEN -NUMBER_OF_APPENDIX_BYTES-1) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS!OpenNonExistInstanceKey Error: instance key prefix '%s' len=%d exceeds %d character limit\r\n", IndexStr,dwLen, DEVKEY_LEN- 3));
        return NULL;
    }
    // Assign an index, starting with index 1
    for (iIndex=1;iIndex<MAX_APPENDIX_NUMBER;iIndex++) {
        DWORD Disposition =0 ;
        LONG status;
        StringCchPrintf(IndexStr+dwLen, DEVKEY_LEN - dwLen , L"%d", iIndex);
        // Create/open Instance key
        status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, IndexStr, 0, NULL, 0, 0, NULL, &hReturn, &Disposition);
        if (status==ERROR_SUCCESS) {
            if (Disposition == REG_OPENED_EXISTING_KEY) { // If found it is existing, continue search.
                DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS::OpenNonExistInstanceKey(%s) returned open exist.continue search\r\n",
                    IndexStr));
                RegCloseKey(hReturn);
                hReturn = NULL;                
            }
            else {
                DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!OpenNonExistInstanceKey-(%s) returns key \r\n",IndexStr));
                // Write new InstanceIndex value.. This instance value is need for DDKReg_GetPciInfo
                status = RegSetValueEx(hReturn, PCIBUS_INSTANCEINDEX_VALNAME, 0, PCIBUS_INSTANCEINDEX_VALTYPE, (PUCHAR)&iIndex, sizeof(iIndex));
                if (status != ERROR_SUCCESS) {
                    DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS!RegCopy RegSetValueEx(%s\\%s) returned %d.\r\n",
                        IndexStr, PCIBUS_INSTANCEINDEX_VALNAME, status));
                    RegCloseKey(hReturn);
                    hReturn = NULL;                                    
                }                
                return hReturn;
            }
        }
        else {
            DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS::OpenNonExistInstanceKey(%s) returned fails\r\n", IndexStr));
            break;
        }
            
    }
    return NULL;
}
//
// Copy registry information for device from template key to instance key.  Write device-specific information
// to instance key.
//
static BOOL
RegCopy(
    PPCI_DEV_INFO pInfo
    )
{
    HKEY TKey;
    HKEY IKey;
    DWORD status;
    WCHAR InstKeyPath[DEVKEY_LEN];
    PWCHAR KeyPtr;
    PWCHAR TmpKeyPtr;
    INTERFACE_TYPE IfcType = PCIBus;
    DWORD Irq, Pin, SysIntr;
    BOOL RetVal = TRUE;
    
    DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!RegCopy+(%s, %d, %d, %d)\r\n", pInfo->RegPath, pInfo->Bus, pInfo->Device, pInfo->Function));

    //
    // Open Template key
    //
    status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pInfo->RegPath, 0, 0, &TKey);            
    if (status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS!RegCopy RegOpenKeyEx(%s) returned %d.\r\n", pInfo->RegPath, status));
        return FALSE;
    }
    
    //
    // Construct Instance key name
    //

    // Find last occurence of "Template" in key
    KeyPtr = NULL;
    TmpKeyPtr = wcsstr(pInfo->RegPath, PCIBUS_TEMPLATE_KEYNAME);
    while (TmpKeyPtr != NULL) {
        KeyPtr = TmpKeyPtr;
        TmpKeyPtr += wcslen(PCIBUS_TEMPLATE_KEYNAME);
        TmpKeyPtr = wcsstr(TmpKeyPtr, PCIBUS_TEMPLATE_KEYNAME);
    };
    
    if (KeyPtr == NULL) {
        // Template key didn't have the word Template in it, report error
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS!RegCopy Key %s doesn't contain %s.\r\n",
            pInfo->RegPath, PCIBUS_TEMPLATE_KEYNAME));

        RetVal = FALSE;
        goto rc_Close_TKey;
    }
    
    StringCchCopy(InstKeyPath, DEVKEY_LEN,pInfo->RegPath);
    {
        DWORD dwIndex = (KeyPtr - pInfo->RegPath);
        if (dwIndex < DEVKEY_LEN)
            InstKeyPath[dwIndex] = (WCHAR)'\0';
    }
    
    wcscat(InstKeyPath, PCIBUS_INSTANCE_KEYNAME);
    wcscat(InstKeyPath, (PWCHAR)(KeyPtr + wcslen(PCIBUS_TEMPLATE_KEYNAME)));
    IKey = OpenNonExistInstanceKey(InstKeyPath);
                
    if (IKey ==NULL ) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS!RegCopy RegCreateKeyEx(%s) returned %d.\r\n",
            InstKeyPath, status));

        RetVal = FALSE;
        goto rc_Close_TKey;
    }

    //
    // Copy Template values to Instance values, without overwriting them
    //
    if (RegCopyKey(TKey, IKey) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }
    
    // Set device-specific indentifier information
    if (RegSetVal(IKey, PCIBUS_CLASS_VALNAME, PCIBUS_CLASS_VALTYPE, (PBYTE)&pInfo->Cfg->BaseClass, sizeof(pInfo->Cfg->BaseClass)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (RegSetVal(IKey, PCIBUS_SUBCLASS_VALNAME, PCIBUS_SUBCLASS_VALTYPE, (PBYTE)&pInfo->Cfg->SubClass, sizeof(pInfo->Cfg->SubClass)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (RegSetVal(IKey, PCIBUS_PROGIF_VALNAME, PCIBUS_PROGIF_VALTYPE, (PBYTE)&pInfo->Cfg->ProgIf, sizeof(pInfo->Cfg->ProgIf)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (RegSetVal(IKey, PCIBUS_VENDORID_VALNAME, PCIBUS_VENDORID_VALTYPE, (PBYTE)&pInfo->Cfg->VendorID, sizeof(pInfo->Cfg->VendorID)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (RegSetVal(IKey, PCIBUS_DEVICEID_VALNAME, PCIBUS_DEVICEID_VALTYPE, (PBYTE)&pInfo->Cfg->DeviceID, sizeof(pInfo->Cfg->DeviceID)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (RegSetVal(IKey, PCIBUS_REVISIONID_VALNAME, PCIBUS_REVISIONID_VALTYPE, (PBYTE)&pInfo->Cfg->RevisionID, sizeof(pInfo->Cfg->RevisionID)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    switch (pInfo->Cfg->HeaderType & ~PCI_MULTIFUNCTION) {
    case PCI_DEVICE_TYPE:
        if (RegSetVal(IKey, PCIBUS_SUBVENDORID_VALNAME, PCIBUS_SUBVENDORID_VALTYPE, (PBYTE)&pInfo->Cfg->u.type0.SubVendorID, sizeof(pInfo->Cfg->u.type0.SubVendorID)) == FALSE) {
            RetVal = FALSE;
            goto rc_Close_IKey;
        }

        if (RegSetVal(IKey, PCIBUS_SUBSYSTEMID_VALNAME, PCIBUS_SUBSYSTEMID_VALTYPE, (PBYTE)&pInfo->Cfg->u.type0.SubSystemID, sizeof(pInfo->Cfg->u.type0.SubSystemID)) == FALSE) {
            RetVal = FALSE;
            goto rc_Close_IKey;
        }

        break;
        
    case PCI_BRIDGE_TYPE:
        break;
        
    case PCI_CARDBUS_TYPE:
        if (RegSetVal(IKey, PCIBUS_SUBVENDORID_VALNAME, PCIBUS_SUBVENDORID_VALTYPE, (PBYTE)&pInfo->Cfg->u.type2.SubVendorID, sizeof(pInfo->Cfg->u.type2.SubVendorID)) == FALSE) {
            RetVal = FALSE;
            goto rc_Close_IKey;
        }

        if (RegSetVal(IKey, PCIBUS_SUBSYSTEMID_VALNAME, PCIBUS_SUBSYSTEMID_VALTYPE, (PBYTE)&pInfo->Cfg->u.type2.SubSystemID, sizeof(pInfo->Cfg->u.type2.SubSystemID)) == FALSE) {
            RetVal = FALSE;
            goto rc_Close_IKey;
        }
        
        break;
        
    default:
        break;
    }

    // Set device location information: interface type (PCI) and bus, device and function numbers
    if (RegSetVal(IKey, PCIBUS_IFCTYPE_VALNAME, PCIBUS_IFCTYPE_VALTYPE, (PBYTE)&IfcType, sizeof(IfcType)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (RegSetVal(IKey, PCIBUS_BUSNUMBER_VALNAME, PCIBUS_BUSNUMBER_VALTYPE, (PBYTE)&pInfo->Bus, sizeof(pInfo->Bus)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (RegSetVal(IKey, PCIBUS_DEVICENUMBER_VALNAME, PCIBUS_DEVICENUMBER_VALTYPE, (PBYTE)&pInfo->Device, sizeof(pInfo->Device)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (RegSetVal(IKey, PCIBUS_FUNCTIONNUMBER_VALNAME, PCIBUS_FUNCTIONNUMBER_VALTYPE, (PBYTE)&pInfo->Function, sizeof(pInfo->Function)) == FALSE) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }
    
    // Set Memory Base and Length values
    if (!RegSetList(IKey, PCIBUS_MEMBASE_VALNAME, pInfo->MemBase.Num, pInfo->MemBase.Reg)) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (!RegSetList(IKey, PCIBUS_MEMLEN_VALNAME, pInfo->MemLen.Num, pInfo->MemLen.Reg)) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }
    
    // Set I/O Base and Length values
    if (!RegSetList(IKey, PCIBUS_IOBASE_VALNAME, pInfo->IoBase.Num, pInfo->IoBase.Reg)) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    if (!RegSetList(IKey, PCIBUS_IOLEN_VALNAME, pInfo->IoLen.Num, pInfo->IoLen.Reg)) {
        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    // Get IRQ and interrupt pin
    switch (pInfo->Cfg->HeaderType & ~PCI_MULTIFUNCTION) {   
    case PCI_DEVICE_TYPE: // Devices
        Irq = pInfo->Cfg->u.type0.InterruptLine;
        Pin = pInfo->Cfg->u.type0.InterruptPin;
        break;

    case PCI_BRIDGE_TYPE: // PCI-PCI bridge
        Irq = pInfo->Cfg->u.type1.InterruptLine;
        Pin = pInfo->Cfg->u.type1.InterruptPin;
        break;

    case PCI_CARDBUS_TYPE: // PCI-Cardbus bridge
        Irq = pInfo->Cfg->u.type2.InterruptLine;
        Pin = pInfo->Cfg->u.type2.InterruptPin;
        break;

    default:
        DEBUGMSG(ZONE_ERROR, (L"PCIbus!RegCopy ERROR: invalid header type %d detected.\r\n", pInfo->Cfg->HeaderType));

        RetVal = FALSE;
        goto rc_Close_IKey;
    }

    // If Interrupt Pin register is 0, or if there is an invalid Interrupt Line value, there is no Irq
    if (!((Pin == 0) || (Irq == 0) || (Irq == 0xFF))) {      
        // Write Irq value
        if (RegSetVal(IKey, PCIBUS_IRQ_VALNAME, PCIBUS_IRQ_VALTYPE, (PBYTE)&Irq, sizeof(Irq)) == FALSE) {
            RetVal = FALSE;
            goto rc_Close_IKey;
        }

        // Request new SysIntr from Irq value
        if (RequestSysIntr(Irq, &SysIntr) == FALSE) {
            RetVal = FALSE;
            goto rc_Close_IKey;
        }

        // Write SysIntr value
        if (RegSetVal(IKey, PCIBUS_SYSINTR_VALNAME, PCIBUS_SYSINTR_VALTYPE, (PBYTE)&SysIntr, sizeof(SysIntr)) == FALSE) {
            RetVal = FALSE;
            goto rc_Close_IKey;
        }
    }
    
rc_Close_IKey:
    RegCloseKey(IKey);
    
rc_Close_TKey:
    RegCloseKey(TKey);

    return RetVal;
}

#pragma prefast(disable: 53, "Fails alarm Reg Copy does not need null-terminate for data.")
//
// Copy contents of one registry key to another.
//
static BOOL
RegCopyKey(
    HKEY TKey,
    HKEY IKey
    )
{
    HKEY TSubKey;
    HKEY ISubKey;
    DWORD NumSubKeys;
    DWORD MaxSubKeyLen;
    DWORD MaxClassLen;
    DWORD NumValues;
    DWORD MaxValueNameLen;
    DWORD MaxValueLen;
    DWORD status;
    DWORD Key;
    DWORD Val;
    WCHAR ValName[PCI_MAX_REG_NAME];
    BYTE ValData[PCI_MAX_REG_DATA];
    DWORD ValLen;
    DWORD ValType;
    DWORD Disposition;
    BOOL RetVal = TRUE;
    
    // Get info on Template Key
    status = RegQueryInfoKey(
                TKey,
                NULL,               // class name buffer (lpszClass)
                NULL,               // ptr to length of class name buffer (lpcchClass)
                NULL,               // reserved
                &NumSubKeys,        // ptr to number of sub-keys (lpcSubKeys)
                &MaxSubKeyLen,      // ptr to longest subkey name length (lpcchMaxSubKeyLen)
                &MaxClassLen,       // ptr to longest class string length (lpcchMaxClassLen)
                &NumValues,         // ptr to number of value entries (lpcValues)
                &MaxValueNameLen,  // ptr to longest value name length (lpcchMaxValueNameLen)
                &MaxValueLen,       // ptr to longest value data length (lpcbMaxValueData)
                NULL,               // ptr to security descriptor length
                NULL);              // ptr to last write time
                    
    if (status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR,
            (TEXT("PCIBUS!RegCopyKey RegQueryInfoKey returned %d.\r\n"),
            status));

        return FALSE;
    }

    // Recurse for each sub-key
    for (Key = 0; Key < NumSubKeys; Key++) {
        // Get TKey sub-key according to Key
        ValLen = sizeof(ValName) / sizeof(WCHAR);
        status = RegEnumKeyEx(
                    TKey,
                    Key,
                    ValName,
                    &ValLen,
                    NULL,
                    NULL,
                    NULL,
                    NULL);

        if (status != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_ENUM,
                (TEXT("PCIBUS!RegCopyKey RegEnumKeyEx(%d) returned %d\r\n"),
                ValName, status));
                
            break;
        }

        // Open sub-key under TKey
        status = RegOpenKeyEx(
                    TKey,
                    ValName,
                    0,
                    0,
                    &TSubKey);
                    
        if (status != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_ENUM|ZONE_ERROR,
                (TEXT("PCIBUS!RegCopyKey RegOpenKeyEx(%s) returned %d\r\n"),
                ValName, status));
                
            continue;
        }

        // Open/create corresponding sub-key under IKey
        status = RegCreateKeyEx(
                    IKey,
                    ValName,
                    0,
                    NULL,
                    0,
                    0,
                    NULL,
                    &ISubKey,
                    &Disposition);
                    
        if (status != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_ENUM|ZONE_ERROR,
                (TEXT("PCIBUS!RegCopyKey RegCreateKeyEx(%s) returned %d\r\n"),
                ValName, status));
                
            RegCloseKey(TSubKey);
            continue;
        }

        RegCopyKey(TSubKey, ISubKey);

        RegCloseKey(TSubKey);
        RegCloseKey(ISubKey);
    }

    // Copy values
    for (Val = 0; Val < NumValues; Val++) {
        // Get value names under TKey according to Val
        ValLen = sizeof(ValName) / sizeof(WCHAR);
        status = RegEnumValue(
                    TKey,
                    Val,
                    ValName,
                    &ValLen,
                    NULL,
                    NULL,
                    NULL,
                    NULL);

        if (status != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_ENUM,
                (TEXT("PCIBUS!RegCopyKey RegEnumValue(%d) returned %d\r\n"),
                Val, status));
            break;
        }

        // If corresponding value under IKey already exists, skip
        status = RegQueryValueEx(
                    IKey,
                    ValName,
                    NULL,
                    NULL,
                    NULL,
                    NULL);

        if (status == ERROR_SUCCESS) {
            // Value exists, don't overwrite
            continue;
        }
                  
        // Read value from TKey
        ValLen = sizeof(ValData);
        status = RegQueryValueEx(
                    TKey,
                    ValName,
                    NULL,
                    &ValType,
                    ValData,
                    &ValLen);

        if (status != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_ENUM,
                (TEXT("PCIBUS!RegCopyKey RegQueryValueEx(%s) returned %d\r\n"),
                ValName, status));
            break;
        }

        // Write value to IKey
        status = RegSetValueEx(
                    IKey,
                    ValName,
                    0,
                    ValType,
                    ValData,
                    ValLen);

        if (status != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_ENUM|ZONE_ERROR,
                (TEXT("PCIBUS!RegCopyKey RegSetValueEx(%s) returned %d.\r\n"),
                ValName, status));

            break;
        }
    }

    return RetVal;
}
#pragma prefast(pop)

//
// Set value in registry.
//
static BOOL
RegSetVal(
    HKEY Key,
    LPCWSTR ValName,
    DWORD ValType, 
    const PBYTE ValData,
    DWORD ValLen
    )
{
    DWORD Status;
    DWORD dwData = 0;

    // If Type is DWORD, pad ValData and write as DWORD
    if (ValType == REG_DWORD) {
        if (ValLen > sizeof(DWORD)) {
            // ValLen too big to fit into DWORD
            DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS!RegSetVal ERROR: ValLen (%d) > %d when ValType == REG_DWORD\r\n", ValLen, sizeof(DWORD)));
            
            return FALSE;
        }
        
        memcpy(&dwData, ValData, ValLen);
        ValLen = sizeof(dwData);

        Status = RegSetValueEx(Key, ValName, 0, ValType, (PBYTE)&dwData, ValLen);
    } else {
        Status = RegSetValueEx(Key, ValName, 0, ValType, ValData, ValLen);
    }

    if (Status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS!RegSetVal RegSetValueEx(%s) returned %d.\r\n", ValName, Status));

        return FALSE;
    }

    return TRUE;
}


//
// Set list of values in registry.
//
static BOOL
RegSetList(
    HKEY Key,
    LPCWSTR ValName,
    DWORD Num,
    DWORD List[]
    )
{
    WCHAR ValList[2*DEVKEY_LEN];
    DWORD Status;

    if (Num == 0) {
        return TRUE;
    } else if (Num == 1) {
        Status = RegSetValueEx(Key, ValName, 0, REG_DWORD, (PBYTE)List, sizeof(List[0]));
    } else {
        DWORD i;
        PWCHAR bp = ValList;
        WCHAR Item[11];          // Largest number of characters produced by _ultow is 10 + termination character
        DWORD ItemSize, Size = 0;

        for (i = 0; i < Num; i++) {
            _ultow(List[i], Item, 16);
            ItemSize = wcslen(Item) + 1;

            if (Size + ItemSize >= 2*DEVKEY_LEN) {
                // List too big
                return FALSE;
            }

            wcscpy(bp, Item);
            bp += ItemSize;
            Size += ItemSize;
        }

        *bp = (WCHAR)'\0';
        Size++;

        Status = RegSetValueEx(Key, ValName, 0, REG_MULTI_SZ, (PBYTE)ValList, Size * sizeof(WCHAR));
    }

    if (Status == ERROR_SUCCESS) {
        return TRUE;
    } else {
        return FALSE;
    }
}


//
// Read list of values from registry.
//
static BOOL
RegGetList(
    HKEY Key,
    LPCWSTR ValName,
    PDWORD pNum,
    DWORD List[]
    )
{
    union {
        WCHAR ValList[2*DEVKEY_LEN];
        DWORD dwValue;
    } uRegValue;
    
    DWORD ValLen = sizeof(uRegValue.ValList);
    DWORD ValType;
    DWORD MaxItems = *pNum;
    DWORD Status = RegQueryValueEx(Key, ValName, NULL, &ValType, (PBYTE)uRegValue.ValList, &ValLen);

    *pNum = 0;
    
    if (Status != ERROR_SUCCESS) {
        return FALSE;
    }

    if (ValType == REG_MULTI_SZ) {
        // Registry entry is multiple strings
        DWORD i;
        PWCHAR bp;
        uRegValue.ValList[2*DEVKEY_LEN-1]=uRegValue.ValList[2*DEVKEY_LEN-2]=0;
        
        for (bp = uRegValue.ValList, i = 0; i < MaxItems; i++) {
            PWCHAR ep;
            
            if (*bp == (WCHAR)'\0') break;

            List[i] = wcstoul(bp, &ep, 16);

            if (bp == ep) break;

            (*pNum)++;
            bp = ++ep;
        }
        PREFAST_SUPPRESS(23,"False Positive. This is multi string with double zero at end.")
        if ((i == MaxItems) && (*bp != (WCHAR)'\0')) {
            // There are more strings in the registry entry than there are locations on the List
            return FALSE;
        }
    } else if (ValType == REG_DWORD) {
        // Registry entry is DWORD
        List[0] = uRegValue.dwValue ;
        *pNum = 1;
    } else {
        // Registry entry is some other type; ignore
        return FALSE;
    }

    return TRUE;
}


//
// Request SysIntr value and set in registry.
//
static BOOL 
RegSetConfig(
    PPCI_DEV_INFO pInfo 
    )
{
    HKEY Key;
    DWORD Status;
    BOOL RetVal = TRUE;
    DWORD Irq, Pin;
    DWORD SysIntr;

    DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!RegSetConfig '%s' (%d/%d/%d)\r\n", pInfo->RegPath, pInfo->Bus, pInfo->Device, pInfo->Function));
    
    // Open  key
    Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pInfo->RegPath, 0, 0, &Key);            
    if (Status != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_ENUM|ZONE_ERROR, (L"PCIBUS!RegSetConfig RegOpenKeyEx(%s) returned %d.\r\n", pInfo->RegPath, Status));
        return FALSE;
    }
    
    // Set Memory Base and Length values
    if (!RegSetList(Key, PCIBUS_MEMBASE_VALNAME, pInfo->MemBase.Num, pInfo->MemBase.Reg)) {
        RetVal = FALSE;
        goto RegSetConfig_Exit;
    }

    if (!RegSetList(Key, PCIBUS_MEMLEN_VALNAME, pInfo->MemLen.Num, pInfo->MemLen.Reg)) {
        RetVal = FALSE;
        goto RegSetConfig_Exit;
    }
    
    // Set I/O Base and Length values
    if (!RegSetList(Key, PCIBUS_IOBASE_VALNAME, pInfo->IoBase.Num, pInfo->IoBase.Reg)) {
        RetVal = FALSE;
        goto RegSetConfig_Exit;
    }

    if (!RegSetList(Key, PCIBUS_IOLEN_VALNAME, pInfo->IoLen.Num, pInfo->IoLen.Reg)) {
        RetVal = FALSE;
        goto RegSetConfig_Exit;
    }
    
    // Get IRQ and interrupt pin
    switch (pInfo->Cfg->HeaderType & ~PCI_MULTIFUNCTION) {   
    case PCI_DEVICE_TYPE: // Devices
        Irq = pInfo->Cfg->u.type0.InterruptLine;
        Pin = pInfo->Cfg->u.type0.InterruptPin;
        break;

    case PCI_BRIDGE_TYPE: // PCI-PCI bridge
        Irq = pInfo->Cfg->u.type1.InterruptLine;
        Pin = pInfo->Cfg->u.type1.InterruptPin;
        break;

    case PCI_CARDBUS_TYPE: // PCI-Cardbus bridge
        Irq = pInfo->Cfg->u.type2.InterruptLine;
        Pin = pInfo->Cfg->u.type2.InterruptPin;
        break;

    default:
        DEBUGMSG(ZONE_ERROR, (L"PCIbus!RegSetConfig ERROR: invalid header type %d detected.\r\n", pInfo->Cfg->HeaderType));

        RetVal = FALSE;
        goto RegSetConfig_Exit;
    }

    // If Interrupt Pin register is 0, or if there is an invalid Interrupt Line value, there is no Irq
    if (!((Pin == 0) || (Irq == 0) || (Irq == 0xFF))) {      
        // Write Irq value
        if (RegSetVal(Key, PCIBUS_IRQ_VALNAME, PCIBUS_IRQ_VALTYPE, (PBYTE)&Irq, sizeof(Irq)) == FALSE) {
            RetVal = FALSE;
            goto RegSetConfig_Exit;
        }

        // Request new SysIntr from Irq value
        if (RequestSysIntr(Irq, &SysIntr) == FALSE) {
            RetVal = FALSE;
            goto RegSetConfig_Exit;
        }

        // Write SysIntr value
        if (RegSetVal(Key, PCIBUS_SYSINTR_VALNAME, PCIBUS_SYSINTR_VALTYPE, (PBYTE)&SysIntr, sizeof(SysIntr)) == FALSE) {
            RetVal = FALSE;
            goto RegSetConfig_Exit;
        }
    }

RegSetConfig_Exit:
    RegCloseKey(Key);
    return RetVal;
}


//
// Initialize Info structure.
//
void
PCIInitInfo(
    LPCWSTR RegPath,
    DWORD Bus,
    DWORD Device,
    DWORD Function,
    PPCI_COMMON_CONFIG pCfg,
    PPCI_DEV_INFO pInfo
    )
{
    wcsncpy(pInfo->RegPath, RegPath, DEVKEY_LEN);
    pInfo->Bus = Bus;
    pInfo->Device = Device;
    pInfo->Function = Function;
    pInfo->Cfg = pCfg;
    
    pInfo->MemBase.Num = 0;
    pInfo->MemLen.Num = 0;
    pInfo->IoBase.Num = 0;
    pInfo->IoLen.Num = 0;
    pInfo->Matched = FALSE;
    pInfo->Configure = TRUE;

    pInfo->ConfigEntry = FALSE;
    wcscpy(pInfo->ConfigEntryName, L"");
    wcscpy(pInfo->ConfigDllName, L"");

    wcscpy(pInfo->DllName, L"");
    pInfo->DevFlags = DEVFLAGS_NONE;

    pInfo->ConfigInfo = NULL;

    pInfo->Command = 0;
    pInfo->BridgeControl = 0x0;     // Don't enable ISA aliasing
    pInfo->Latency = 0;
    pInfo->SecondaryLatency = 0;
}


//
// Request I/O and Irq resources from I/O Resource Manager.
//
static BOOL
PCIRequestResources(
    PPCI_DEV_INFO pInfo
    )
{
    DWORD Io, Irq, i,IrqFlag;
    
    // Request I/O resources from I/O Resource Manager (IORM)
    for (Io = 0; Io < pInfo->IoBase.Num; Io++) {
        DEBUGMSG(ZONE_INIT, (L"PCIBUS!PCIRequestResources: I/O Resource Manager request for I/O range 0x%X-0x%X\r\n",
                pInfo->IoBase.Reg[Io], pInfo->IoBase.Reg[Io] + pInfo->IoLen.Reg[Io] - 1));
        
        if (!ResourceRequest(RESMGR_IOSPACE, pInfo->IoBase.Reg[Io], pInfo->IoLen.Reg[Io])) {
            DEBUGMSG(ZONE_ERROR, (L"PCIBUS!PCIRequestResources: I/O Resource Manager request for I/O range 0x%X-0x%X failed\r\n",
                pInfo->IoBase.Reg[Io], pInfo->IoBase.Reg[Io] + pInfo->IoLen.Reg[Io] - 1));

            goto PCIRequestResources_Fail;
        }
    }

    // Retrieve IRQ
    switch (pInfo->Cfg->HeaderType & ~PCI_MULTIFUNCTION) {    
    case PCI_DEVICE_TYPE: // Devices
        // If Interrupt Pin register is 0, or if there is an invalid Interrupt Line value, there is no Irq
        if ((pInfo->Cfg->u.type0.InterruptPin == 0) || (pInfo->Cfg->u.type0.InterruptLine == 0) || (pInfo->Cfg->u.type0.InterruptLine == 0xFF)) {
            return TRUE;
        }

        Irq = pInfo->Cfg->u.type0.InterruptLine;
        break;

    case PCI_BRIDGE_TYPE: // PCI-PCI bridges
        // If Interrupt Pin register is 0, or if there is an invalid Interrupt Line value, there is no Irq
        if ((pInfo->Cfg->u.type1.InterruptPin == 0) || (pInfo->Cfg->u.type1.InterruptLine == 0) || (pInfo->Cfg->u.type1.InterruptLine == 0xFF)) {
            return TRUE;
        }

        Irq = pInfo->Cfg->u.type1.InterruptLine;
        break;

    case PCI_CARDBUS_TYPE: // Cardbus controller
        // If Interrupt Pin register is 0, or if there is an invalid Interrupt Line value, there is no Irq
        if ((pInfo->Cfg->u.type2.InterruptPin == 0) || (pInfo->Cfg->u.type2.InterruptLine == 0) || (pInfo->Cfg->u.type2.InterruptLine == 0xFF)) {
            return TRUE;
        }

        Irq = pInfo->Cfg->u.type2.InterruptLine;
        break;

    default:
        return FALSE;
    }
        
    // Request Irq resource from IORM
    DEBUGMSG(ZONE_INIT, (L"PCIBUS!PCIRequestResources: I/O Resource Manager request for IRQ %d\r\n", Irq));

    IrqFlag = ((pInfo->DevFlags & DEVFLAGS_IRQ_EXCLUSIVE)!=0?RREXF_REQUEST_EXCLUSIVE:0);
    if (!ResourceRequestEx(RESMGR_IRQ, Irq, 1, IrqFlag)) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!PCIRequestResourcesEx: I/O Resource Manager request for IRQ %d with flag %x failed\r\n", Irq,IrqFlag));

        goto PCIRequestResources_Fail;
    } 

    return TRUE;
    
PCIRequestResources_Fail:
    // Release those resources that were requested successfully
    for (i = 0; i < Io; i++) {
        ResourceRelease(RESMGR_IOSPACE, pInfo->IoBase.Reg[i], pInfo->IoLen.Reg[i]);
    }

    return FALSE;
}


//
// Request SysIntr from OAL given an IRQ.
//
static BOOL
RequestSysIntr(
    DWORD Irq,
    DWORD *pSysIntr
    )
{
    BOOL RetVal = KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &Irq, sizeof(Irq), pSysIntr, sizeof(*pSysIntr), NULL);

    DEBUGMSG(ZONE_FUNCTION, (L"PCIBUS!RequestSysIntr(%d) -> %d\r\n", Irq, *pSysIntr));
    
    if (*pSysIntr == SYSINTR_UNDEFINED) {
        RetVal = FALSE;
    }

    if (!RetVal) {
        DEBUGMSG(ZONE_ERROR, (L"PCIBUS!RequestSysIntr: Invalid SysIntr returned for Irq %d\r\n", Irq));
    }

    return RetVal;
}


//
// Returns String describing a device based on Class, SubClass and Programming Interface.
//
static PWCHAR
DeviceType(UCHAR ucBaseClass, UCHAR ucSubClass, UCHAR ucProgIf)
{
    static  struct  
    {
        ULONG   ulClass;
        ULONG   ulMask;
        PWCHAR  pszDescription;
    }   ClassNames[] = 
    {
        // Class code 0 (pre rev 2.0)
        { 0x000000, 0xFFFFFF, TEXT("Pre 2.0 Non VGA device") },
        { 0x000101, 0xFFFFFF, TEXT("Pre 2.0 VGA device") },

        // Class code 1: mass storage controllers
        { 0x010000, 0xFFFFFF, TEXT("SCSI Controller") },
        { 0x010100, 0xFFFF00, TEXT("IDE Controller") },
        { 0x010180, 0xFFFFFF, TEXT("Busmaster IDE Controller") },
        { 0x010200, 0xFFFFFF, TEXT("Floppy Controller") },
        { 0x010300, 0xFFFFFF, TEXT("IPI Controller") },
        { 0x010400, 0xFFFFFF, TEXT("RAID Controller") },
        { 0x018000, 0xFFFFFF, TEXT("Other Mass Storage Device") },

        // Class code 2: network controllers
        { 0x020000, 0xFFFFFF, TEXT("Ethernet Network Controller") },
        { 0x020100, 0xFFFFFF, TEXT("Token Ring Network Controller") },
        { 0x020200, 0xFFFFFF, TEXT("FDDI Network Controller") },
        { 0x020300, 0xFFFFFF, TEXT("ATM Network Controller") },
        { 0x028000, 0xFFFFFF, TEXT("Other Network Controller") },

        // Class code 3: display controllers
        { 0x030000, 0xFFFFFF, TEXT("VGA Compatible Display Adapter") },
        { 0x030001, 0xFFFFFF, TEXT("8514 Compatible Display Adapter") },
        { 0x030100, 0xFFFFFF, TEXT("XGA Compatible Display Adapter") },
        { 0x038000, 0xFFFFFF, TEXT("Other Display Adapter") },

        // Class code 4: multimedia devices
        { 0x040000, 0xFFFFFF, TEXT("Video Multimedia Device") },
        { 0x040100, 0xFFFFFF, TEXT("Audio Multimedia Device") },
        { 0x048000, 0xFFFFFF, TEXT("Other Multimedia Device") },

        // Class code 5: memory controllers
        { 0x050000, 0xFFFFFF, TEXT("RAM Memory Controller") },
        { 0x050100, 0xFFFFFF, TEXT("Flash Memory Controller") },
        { 0x058000, 0xFFFFFF, TEXT("Other Memory Controller") },

        // Class code 6: bridge devices
        { 0x060000, 0xFFFFFF, TEXT("Host / PCI Bridge") },
        { 0x060100, 0xFFFFFF, TEXT("PCI / ISA Bridge") },
        { 0x060200, 0xFFFFFF, TEXT("PCI / EISA Bridge") },
        { 0x060300, 0xFFFFFF, TEXT("PCI / MCA Bridge") },
        { 0x060400, 0xFFFFFF, TEXT("PCI / PCI Bridge") },
        { 0x060500, 0xFFFFFF, TEXT("PCI / PCMCIA Bridge") },
        { 0x060600, 0xFFFFFF, TEXT("PCI / NuBus Bridge") },
        { 0x060700, 0xFFFFFF, TEXT("PCI / CardBus Bridge") },
        { 0x068000, 0xFFFFFF, TEXT("Other Bridge") },

        // Class code 7: simple communications controllers
        { 0x070000, 0xFFFFFF, TEXT("8250 Compatible Communications Device") },
        { 0x070001, 0xFFFFFF, TEXT("16450 Compatible Communications Device") },
        { 0x070002, 0xFFFFFF, TEXT("16550 Compatible Communications Device") },
        { 0x070100, 0xFFFFFF, TEXT("Parallel Port") },
        { 0x070101, 0xFFFFFF, TEXT("Bidirectional Parallel Port") },
        { 0x070102, 0xFFFFFF, TEXT("ECP 1.X Parallel Port") },
        { 0x078000, 0xFFFFFF, TEXT("Other Communications Device") },

        // Class code C: serial bus controllers
        { 0x0C0000, 0xFFFFFF, TEXT("Firewire (IEEE 1394) Bus Controller") },
        { 0x0C0100, 0xFFFFFF, TEXT("ACCESS.bus Controller") },
        { 0x0C0200, 0xFFFFFF, TEXT("SSA Controller") },
        { 0x0C0300, 0xFFFFFF, TEXT("UHCI USB Bus Controller") },
        { 0x0C0310, 0xFFFFFF, TEXT("OHCI USB Bus Controller") },
        { 0x0C0400, 0xFFFFFF, TEXT("Fibre Channel Controller") },
    };
    
#define NUMBER_CLASS_NAMES  (sizeof(ClassNames) / sizeof(ClassNames[0]))

    ULONG   ulClassKey;
    int     i;

    ulClassKey = (ucBaseClass << 16) | (ucSubClass << 8) | ucProgIf;

    for (i = 0; i < NUMBER_CLASS_NAMES; i++)
    {
        if ((ClassNames[i].ulClass & ClassNames[i].ulMask) == (ulClassKey & ClassNames[i].ulMask))
        {
            return ClassNames[i].pszDescription;
        }
    }

    return TEXT("Unknown Device Type");
}


//
// Displays PCI device configuration space information.
//
VOID
DumpPciConfig(
    PPCI_DEV_INFO pInfo
    )
{
    RETAILMSG(1,
        (TEXT("-------------------------------------------------------------------------------\r\n")));
        
    RETAILMSG(1,
        (TEXT("Bus/Device/Function = %d/%d/%d, %s\r\n"),
        pInfo->Bus, pInfo->Device, pInfo->Function,
        DeviceType(pInfo->Cfg->BaseClass, pInfo->Cfg->SubClass, pInfo->Cfg->ProgIf)));

    RETAILMSG(1,
        (TEXT("  VendorID = 0x%4.4X, DeviceID = 0x%4.4X, Command = 0x%4.4X, Status = 0x%4.4X\r\n"),
        pInfo->Cfg->VendorID, pInfo->Cfg->DeviceID, pInfo->Cfg->Command, pInfo->Cfg->Status));

    RETAILMSG(1,
        (TEXT("  RevisionID = 0x%2.2X, ProgIf = 0x%2.2X, SubClass = 0x%2.2X, BaseClass = 0x%2.2X\r\n"),
        pInfo->Cfg->RevisionID, pInfo->Cfg->ProgIf, pInfo->Cfg->SubClass, pInfo->Cfg->BaseClass));

    RETAILMSG(1,
        (TEXT("  CacheLineSize = 0x%2.2X, LatencyTimer = 0x%2.2X, HeaderType = 0x%2.2X, BIST = 0x%2.2X\r\n"),
        pInfo->Cfg->CacheLineSize, pInfo->Cfg->LatencyTimer, pInfo->Cfg->HeaderType, pInfo->Cfg->BIST));

    switch (pInfo->Cfg->HeaderType & ~PCI_MULTIFUNCTION)
    {
    case PCI_DEVICE_TYPE:
        RETAILMSG(1,(TEXT("  BaseAddresses:\r\n")));

        RETAILMSG(1,
            (TEXT("    0x%8.8X  0x%8.8X  0x%8.8X  0x%8.8X  0x%8.8X  0x%8.8X\r\n"),
            pInfo->Cfg->u.type0.BaseAddresses[0], pInfo->Cfg->u.type0.BaseAddresses[1],
            pInfo->Cfg->u.type0.BaseAddresses[2], pInfo->Cfg->u.type0.BaseAddresses[3],
            pInfo->Cfg->u.type0.BaseAddresses[4], pInfo->Cfg->u.type0.BaseAddresses[5]));

        RETAILMSG(1,
            (TEXT("  CIS = 0x%8.8X, SubVendorID = 0x%4.4X, SubSystemID = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type0.CIS, pInfo->Cfg->u.type0.SubVendorID,
            pInfo->Cfg->u.type0.SubSystemID));

        RETAILMSG(1,
            (TEXT("  ROMBaseAddress = 0x%8.8X\r\n"),
            pInfo->Cfg->u.type0.ROMBaseAddress));

        RETAILMSG(1,
            (TEXT("  InterruptLine = 0x%2.2X, InterruptPin = 0x%2.2X, MinGrant = 0x%2.2X, MaxLatency = 0x%2.2X\r\n"),
            pInfo->Cfg->u.type0.InterruptLine, pInfo->Cfg->u.type0.InterruptPin,
            pInfo->Cfg->u.type0.MinimumGrant, pInfo->Cfg->u.type0.MaximumLatency));
        break;

    case PCI_BRIDGE_TYPE:
        RETAILMSG(1,
            (TEXT("  BaseAddresses = 0x%8.8X 0x%8.8X\r\n"),
            pInfo->Cfg->u.type1.BaseAddresses[0], pInfo->Cfg->u.type1.BaseAddresses[1]));

        RETAILMSG(1,
            (TEXT("  PrimaryBusNumber = %d, SecondaryBusNumber = %d\r\n"),
            pInfo->Cfg->u.type1.PrimaryBusNumber, pInfo->Cfg->u.type1.SecondaryBusNumber));

        RETAILMSG(1,
            (TEXT("  SubordinateBusNumber = %d, SecondaryLatencyTimer = %d\r\n"),
            pInfo->Cfg->u.type1.SubordinateBusNumber, pInfo->Cfg->u.type1.SecondaryLatencyTimer));

        RETAILMSG(1,
            (TEXT("  IOBase = 0x%2.2X, IOLimit = 0x%2.2X, SecondaryStatus = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type1.IOBase, pInfo->Cfg->u.type1.IOLimit,
            pInfo->Cfg->u.type1.SecondaryStatus));

        RETAILMSG(1,
            (TEXT("  MemoryBase = 0x%4.4X, MemoryLimit = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type1.MemoryBase, pInfo->Cfg->u.type1.MemoryLimit));

        RETAILMSG(1,
            (TEXT("  PrefetchableMemoryBase = 0x%4.4X, PrefetchableMemoryLimit = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type1.PrefetchableMemoryBase, pInfo->Cfg->u.type1.PrefetchableMemoryLimit));

        RETAILMSG(1,
            (TEXT("  PrefetchableMemoryBaseUpper32 = 0x%8.8X\r\n"),
            pInfo->Cfg->u.type1.PrefetchableMemoryBaseUpper32));

        RETAILMSG(1,
            (TEXT("  PrefetchableMemoryLimitUpper32 = 0x%8.8X\r\n"),
            pInfo->Cfg->u.type1.PrefetchableMemoryLimitUpper32));

        RETAILMSG(1,
            (TEXT("  IOBaseUpper = 0x%4.4X, IOLimitUpper = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type1.IOBaseUpper, pInfo->Cfg->u.type1.IOLimitUpper));

        RETAILMSG(1,
            (TEXT("  Reserved2 = 0x%8.8X, ExpansionROMBase = 0x%8.8X\r\n"),
            pInfo->Cfg->u.type1.Reserved2, pInfo->Cfg->u.type1.ExpansionROMBase));

        RETAILMSG(1,
            (TEXT("  InterruptLine = 0x%2.2X, InterruptPin = 0x%2.2X, BridgeControl = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type1.InterruptLine, pInfo->Cfg->u.type1.InterruptPin,
            pInfo->Cfg->u.type1.BridgeControl));

        break;

    case PCI_CARDBUS_TYPE:
        RETAILMSG(1,
            (TEXT("  BaseAddress (CardBus Socket/ExCA Base Address Register) = 0x%8.8X\r\n"),
            pInfo->Cfg->u.type2.BaseAddress));
        
        RETAILMSG(1,
            (TEXT("  CapabilitiesPtr = 0x%2.2X, Reserved2 = 0x%2.2X, SecondaryStatus = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type2.CapabilitiesPtr, pInfo->Cfg->u.type2.Reserved2,
            pInfo->Cfg->u.type1.SecondaryStatus));
        
        RETAILMSG(1,
            (TEXT("  PrimaryBusNumber = %d, CardbusBusNumber = %d\r\n"),
            pInfo->Cfg->u.type2.PrimaryBusNumber, pInfo->Cfg->u.type2.CardbusBusNumber));

        RETAILMSG(1,
            (TEXT("  SubordinateBusNumber = %d, CardbusLatencyTimer = %d\r\n"),
            pInfo->Cfg->u.type2.SubordinateBusNumber, pInfo->Cfg->u.type2.CardbusLatencyTimer));

        RETAILMSG(1,
            (TEXT("  MemoryBase0 = 0x%8.8X, MemoryLimit0 = 0x%8.8X\r\n"),
            pInfo->Cfg->u.type2.MemoryBase0, pInfo->Cfg->u.type2.MemoryLimit0));

        RETAILMSG(1,
            (TEXT("  MemoryBase1 = 0x%8.8X, MemoryLimit1 = 0x%8.8X\r\n"),
            pInfo->Cfg->u.type2.MemoryBase1, pInfo->Cfg->u.type2.MemoryLimit1));

        RETAILMSG(1,
            (TEXT("  IOBase0_LO = 0x%4.4X, IOBase0_HI = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type2.IOBase0_LO, pInfo->Cfg->u.type2.IOBase0_HI));

        RETAILMSG(1,
            (TEXT("  IOLimit0_LO = 0x%4.4X, IOLimit0_HI = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type2.IOLimit0_LO, pInfo->Cfg->u.type2.IOLimit0_HI));

        RETAILMSG(1,
            (TEXT("  IOBase1_LO = 0x%4.4X, IOBase1_HI = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type2.IOBase1_LO, pInfo->Cfg->u.type2.IOBase1_HI));

        RETAILMSG(1,
            (TEXT("  IOLimit1_LO = 0x%4.4X, IOLimit1_HI = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type2.IOLimit1_LO, pInfo->Cfg->u.type2.IOLimit1_HI));

        RETAILMSG(1,
            (TEXT("  InterruptLine = 0x%2.2X, InterruptPin = 0x%2.2X, BridgeControl = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type2.InterruptLine, pInfo->Cfg->u.type2.InterruptPin,
            pInfo->Cfg->u.type2.BridgeControl));

        RETAILMSG(1,
            (TEXT("  SubVendorID = 0x%4.4X, SubSystemID = 0x%4.4X\r\n"),
            pInfo->Cfg->u.type2.SubVendorID, pInfo->Cfg->u.type2.SubSystemID));

        RETAILMSG(1,
            (TEXT("  LegactyBaseAddress = 0x%8.8X\r\n"),
            pInfo->Cfg->u.type2.LegacyBaseAddress));
        
        break;

    default:
        RETAILMSG(1, (L"ERROR: Unrecognized header type 0x%X\r\n", pInfo->Cfg->HeaderType & ~PCI_MULTIFUNCTION));
    }

    RETAILMSG(1,
        (TEXT("-------------------------------------------------------------------------------\r\n")));
}


//
// Find minimum value in a list of values.
//
static DWORD
MinVal(
    DWORD List[],
    DWORD Len
    )
{
    DWORD i;
    DWORD Min = List[0];

    for (i = 1; i < Len; i++) {
        if (List[i] < Min) Min = List[i];
    }

    return Min;
}
