/*******************************************************************************
 * Copyright (c) PLX Technology, Inc.
 *
 * PLX Technology Inc. licenses this source file under the GNU Lesser General Public
 * License (LGPL) version 2.  This source file may be modified or redistributed
 * under the terms of the LGPL and without express permission from PLX Technology.
 *
 * PLX Technology, Inc. provides this software AS IS, WITHOUT ANY WARRANTY,
 * EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTY OF
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  PLX makes no guarantee
 * or representations regarding the use of, or the results of the use of,
 * the software and documentation in terms of correctness, accuracy,
 * reliability, currentness, or otherwise; and you rely on the software,
 * documentation and results solely at your own risk.
 *
 * IN NO EVENT SHALL PLX BE LIABLE FOR ANY LOSS OF USE, LOSS OF BUSINESS,
 * LOSS OF PROFITS, INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES
 * OF ANY KIND.
 *
 ******************************************************************************/

/******************************************************************************
 *
 * File Name:
 *
 *      PciFunc.c
 *
 * Description:
 *
 *      This file contains the PCI support functions
 *
 * Revision History:
 *
 *      05-01-13 : PLX SDK v7.10
 *
 ******************************************************************************/


#include "PciFunc.h"
#include "PlugPlay.h"
#include "SuppFunc.h"




/***********************************************
 *               Globals
 **********************************************/
// Global ECAM 64-bit address from ACPI table (0=Not Probed, -1=Not Available)
static U32 Gbl_Acpi_Addr_ECAM[2] = {ACPI_PCIE_NOT_PROBED, ACPI_PCIE_NOT_PROBED};




/*******************************************************************************
 *
 * Function   :  PlxPciRegisterRead_UseOS
 *
 * Description:  Reads a PCI register using OS services
 *
 ******************************************************************************/
PLX_STATUS
PlxPciRegisterRead_UseOS(
    DEVICE_EXTENSION *pdx,
    U16               offset,
    U32              *pValue
    )
{
    PIRP                pIrp;
    KEVENT              event;
    NTSTATUS            status;
    PIO_STACK_LOCATION  stack;


    // For PCIe extended register, use Enhanced PCIe Mechanism
    if (offset >= 0x100)
    {
        return PlxPciExpressRegRead(
                   pdx->Key.bus,
                   pdx->Key.slot,
                   pdx->Key.function,
                   offset,
                   pValue
                   );
    }

    // Default to error
    *pValue = (U32)-1;

    // Offset must be on a 4-byte boundary
    if (offset & 0x3)
        return ApiInvalidOffset;

    pIrp =
        IoAllocateIrp(
            pdx->pDeviceObject->StackSize,
            FALSE
            );

    if (pIrp == NULL)
    {
        DebugPrintf(("ERROR - Unable to allocate IRP\n"));
        return ApiInsufficientResources;
    }

    // Initialize kernel event
    KeInitializeEvent(
        &event,
        NotificationEvent,
        FALSE
        );

    stack =
        IoGetNextIrpStackLocation(
            pIrp
            );

    // Fill the IRP
    pIrp->IoStatus.Status                        = STATUS_NOT_SUPPORTED;
    stack->MajorFunction                         = IRP_MJ_PNP;
    stack->MinorFunction                         = IRP_MN_READ_CONFIG;
    stack->Parameters.ReadWriteConfig.WhichSpace = PCI_WHICHSPACE_CONFIG;
    stack->Parameters.ReadWriteConfig.Buffer     = pValue;
    stack->Parameters.ReadWriteConfig.Offset     = offset;
    stack->Parameters.ReadWriteConfig.Length     = sizeof(U32);

    IoSetCompletionRoutine(
        pIrp,
        PnP_OnRequestComplete,
        &event,
        TRUE,
        TRUE,
        TRUE
        );

    // Send the packet
    status =
        IoCallDriver(
            pdx->pLowerDeviceObject,
            pIrp
            );

    if (status == STATUS_PENDING)
    {
        // Wait for completion
        KeWaitForSingleObject(
            &event,
            Executive,
            KernelMode,
            FALSE,
            NULL
            );

        status = pIrp->IoStatus.Status;
    }

    // Release the IRP
    IoFreeIrp(
        pIrp
        );

    if (status != STATUS_SUCCESS)
        return ApiConfigAccessFailed;

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciRegisterWrite_UseOS
 *
 * Description:  Writes a value to a PCI register using OS services
 *
 ******************************************************************************/
PLX_STATUS
PlxPciRegisterWrite_UseOS(
    DEVICE_EXTENSION *pdx,
    U16               offset,
    U32               value
    )
{
    PIRP                pIrp;
    KEVENT              event;
    NTSTATUS            status;
    PIO_STACK_LOCATION  stack;


    // For PCIe extended register, use Enhanced PCIe Mechanism
    if (offset >= 0x100)
    {
        return PlxPciExpressRegWrite(
                   pdx->Key.bus,
                   pdx->Key.slot,
                   pdx->Key.function,
                   offset,
                   value
                   );
    }

    // Offset must be on a 4-byte boundary
    if (offset & 0x3)
        return ApiInvalidOffset;

    pIrp =
        IoAllocateIrp(
            pdx->pDeviceObject->StackSize,
            FALSE
            );

    if (pIrp == NULL)
    {
        DebugPrintf(("ERROR - Unable to allocate IRP\n"));
        return ApiInsufficientResources;
    }

    // Initialize kernel event
    KeInitializeEvent(
        &event,
        NotificationEvent,
        FALSE
        );

    stack =
        IoGetNextIrpStackLocation(
            pIrp
            );

    // Fill the IRP
    pIrp->IoStatus.Status                        = STATUS_NOT_SUPPORTED;
    stack->MajorFunction                         = IRP_MJ_PNP;
    stack->MinorFunction                         = IRP_MN_WRITE_CONFIG;
    stack->Parameters.ReadWriteConfig.WhichSpace = PCI_WHICHSPACE_CONFIG;
    stack->Parameters.ReadWriteConfig.Buffer     = &value;
    stack->Parameters.ReadWriteConfig.Offset     = offset;
    stack->Parameters.ReadWriteConfig.Length     = sizeof(U32);

    IoSetCompletionRoutine(
        pIrp,
        PnP_OnRequestComplete,
        &event,
        TRUE,
        TRUE,
        TRUE
        );

    // Send the packet
    status =
        IoCallDriver(
            pdx->pLowerDeviceObject,
            pIrp
            );

    if (status == STATUS_PENDING)
    {
        // Wait for completion
        KeWaitForSingleObject(
            &event,
            Executive,
            KernelMode,
            FALSE,
            NULL
            );

        status = pIrp->IoStatus.Status;
    }

    IoFreeIrp(
        pIrp
        );

    if (status != STATUS_SUCCESS)
        return ApiConfigAccessFailed;

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciExpressRegRead
 *
 * Description:  Reads a PCI Express register using the enhanced configuration mechanism
 *
 ******************************************************************************/
PLX_STATUS
PlxPciExpressRegRead(
    U8   bus,
    U8   slot,
    U8   function,
    U16  offset,
    U32 *pValue
    )
{
    PLX_UINT_PTR address;


    // Default to error
    *pValue = (U32)-1;

    // Offset must be on a 4-byte boundary
    if (offset & 0x3)
        return ApiInvalidOffset;

    // Check if PCIe ECAM was probed for
    if (Gbl_Acpi_Addr_ECAM[0] == ACPI_PCIE_NOT_PROBED)
        PlxProbeForEcamBase();

    // Check if Enhanced mechanism available through ACPI
    if (Gbl_Acpi_Addr_ECAM[0] == ACPI_PCIE_NOT_AVAILABLE)
        return ApiUnsupportedFunction;

    // Setup base address for register access
    address =
        Gbl_Acpi_Addr_ECAM[0] |
        (bus      << 20)      |
        (slot     << 15)      |
        (function << 12)      |
        (offset   <<  0);

    // Read the register
    *pValue =
        (U32)PlxPhysicalMemRead(
            PLX_INT_TO_PTR(address),
            sizeof(U32)
            );

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciExpressRegWrite
 *
 * Description:  Writes a PCI Express register using the enhanced configuration mechanism
 *
 ******************************************************************************/
PLX_STATUS
PlxPciExpressRegWrite(
    U8  bus,
    U8  slot,
    U8  function,
    U16 offset,
    U32 value
    )
{
    PLX_UINT_PTR address;


    // Offset must be on a 4-byte boundary
    if (offset & 0x3)
        return ApiInvalidOffset;

    // Check if PCIe ECAM was probed for
    if (Gbl_Acpi_Addr_ECAM[0] == ACPI_PCIE_NOT_PROBED)
        PlxProbeForEcamBase();

    // Check if Enhanced mechanism available through ACPI
    if (Gbl_Acpi_Addr_ECAM[0] == ACPI_PCIE_NOT_AVAILABLE)
        return ApiUnsupportedFunction;

    // Setup base address for register access
    address =
        Gbl_Acpi_Addr_ECAM[0] |
        (bus      << 20)      |
        (slot     << 15)      |
        (function << 12)      |
        (offset   <<  0);

    // Write the register
    PlxPhysicalMemWrite(
        PLX_INT_TO_PTR(address),
        value,
        sizeof(U32)
        );

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciRegisterRead_BypassOS
 *
 * Description:  Reads a PCI register by bypassing the OS services
 *
 ******************************************************************************/
PLX_STATUS
PlxPciRegisterRead_BypassOS(
    U8   bus,
    U8   slot,
    U8   function,
    U16  offset,
    U32 *pValue
    )
{
    U32   RegSave;
    KIRQL IrqlSave;


    // For PCIe extended register, use Enhanced PCIe Mechanism
    if (offset >= 0x100)
    {
        return PlxPciExpressRegRead(
                   bus,
                   slot,
                   function,
                   offset,
                   pValue
                   );
    }

    // Offset must be on a 4-byte boundary
    if (offset & 0x3)
    {
        *pValue = (U32)-1;
        return ApiInvalidOffset;
    }

    /***************************************************************
     * Access of a PCI register involves using I/O addresses 0xcf8
     * and 0xcfc. These addresses must be used together and no other
     * process must interrupt.
     **************************************************************/

    // Make sure slot is only 5-bits
    slot &= 0x1F;

    // Make sure function is only 3-bits
    function &= 0x7;

    // Minimize chance of interrupt
    KeRaiseIrql( DISPATCH_LEVEL, &IrqlSave );

    // Save the content of the command register
    RegSave =
        IO_PORT_READ_32(
            0xcf8
            );

    // Configure the command register to access the desired location
    IO_PORT_WRITE_32(
        0xcf8,
        (1 << 31) | (bus << 16) | (slot << 11) | (function << 8) | offset
        );

    // Read the register
    *pValue =
        IO_PORT_READ_32(
            0xcfc
            );

    // Restore the command register
    IO_PORT_WRITE_32(
        0xcf8,
        RegSave
        );

    // Restore interrupt state
    KeLowerIrql( IrqlSave );

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciRegisterWrite_BypassOS
 *
 * Description:  Writes to a PCI register by bypassing the OS services
 *
 ******************************************************************************/
PLX_STATUS
PlxPciRegisterWrite_BypassOS(
    U8  bus,
    U8  slot,
    U8  function,
    U16 offset,
    U32 value
    )
{
    U32   RegSave;
    KIRQL IrqlSave;


    // For PCIe extended register, use Enhanced PCIe Mechanism
    if (offset >= 0x100)
    {
        return PlxPciExpressRegWrite(
                   bus,
                   slot,
                   function,
                   offset,
                   value
                   );
    }

    // Offset must be on a 4-byte boundary
    if (offset & 0x3)
    {
        return ApiInvalidOffset;
    }

    /***************************************************************
     * Access of a PCI register involves using I/O addresses 0xcf8
     * and 0xcfc. These addresses must be used together and no other
     * process must interrupt.
     **************************************************************/

    // Make sure slot is only 5-bits
    slot &= 0x1F;

    // Make sure function is only 3-bits
    function &= 0x7;

    // Minimize chance of interrupt
    KeRaiseIrql( DISPATCH_LEVEL, &IrqlSave );

    // Save the content of the command register
    RegSave =
        IO_PORT_READ_32(
            0xcf8
            );

    // Configure the command register to access the desired location
    IO_PORT_WRITE_32(
        0xcf8,
        (1 << 31) | (bus << 16) | (slot << 11) | (function << 8) | offset
        );

    // Write the register
    IO_PORT_WRITE_32(
        0xcfc,
        value
        );

    // Restore the command register
    IO_PORT_WRITE_32(
        0xcf8,
        RegSave
        );

    // Restore interrupt state
    KeLowerIrql( IrqlSave );

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxProbeForEcamBase
 *
 * Description:  Probes for Enhanced Configuration Access Mechanism base
 *               address through ACPI
 *
 ******************************************************************************/
VOID
PlxProbeForEcamBase(
    VOID
    )
{
    U8                Str_ID[9];
    U8               *pEntry;
    U8               *pAddress;
    U8               *Va_BiosRom;
    U8               *Va_RSDT;
    U8               *Va_Table;
    U8               *pAcpi_Addr_RSDP;
    U8               *pAcpi_Addr_RSDT;
    U16               NumEntries;
    U32               value;
    BOOLEAN           bFound;
    ACPI_RSDT_v1_0    Acpi_Rsdt;
    PHYSICAL_ADDRESS  PhysAddr;


    // Do not probe again if previously did
    if (Gbl_Acpi_Addr_ECAM[0] != ACPI_PCIE_NOT_PROBED)
       return;

    // Default to ACPI and/or ECAM not detected
    Gbl_Acpi_Addr_ECAM[0] = ACPI_PCIE_NOT_AVAILABLE;

    // Default to ECAM not found
    bFound = FALSE;

    // Initialize virtual addresses
    Va_BiosRom = NULL;
    Va_RSDT    = NULL;

    // Mark end of string
    Str_ID[8] = '\0';

    // Setup starting address on 16-byte boundary
    PhysAddr.QuadPart = BIOS_MEM_START;

    // Map BIOS ROM into kernel space
    Va_BiosRom =
        MmMapIoSpace(
            PhysAddr,
            (BIOS_MEM_END - BIOS_MEM_START),
            MmNonCached
            );

    if (Va_BiosRom == NULL)
        goto _Exit_PlxProbeForEcamBase;

    // Set physical and virtual starting addresses
    pAcpi_Addr_RSDP = (U8*)BIOS_MEM_START;
    pAddress        = Va_BiosRom;

    // Scan system ROM for ACPI RSDP pointer
    do
    {
        // Read 8-bytes
        *(U32*)Str_ID       = PHYS_MEM_READ_32( (U32*)pAddress );
        *(U32*)(Str_ID + 4) = PHYS_MEM_READ_32( (U32*)(pAddress + 4) );

        // Check for header signature
        if (memcmp(
                "RSD PTR ",
                Str_ID,
                8       // 8 bytes
                ) == 0)
        {
            bFound = TRUE;
        }
        else
        {
            // Increment to next 16-byte boundary
            pAddress        += 16;
            pAcpi_Addr_RSDP += 16;
        }
    }
    while (!bFound && (pAcpi_Addr_RSDP < (U8*)BIOS_MEM_END));

    if (!bFound)
    {
        DebugPrintf(("ACPI Probe - ACPI not detected\n"));
        goto _Exit_PlxProbeForEcamBase;
    }

    // Reset flag
    bFound = FALSE;

    // Get ACPI revision
    value = PHYS_MEM_READ_8( (U8*)(pAddress + 15) );

    DebugPrintf((
        "ACPI Probe - ACPI is v%s (rev=%d)\n",
        (value == 0) ? "1.0" : "2.0 or higher",
        (int)value
        ));

    DebugPrintf((
        "ACPI Probe - 'RSD PTR ' found at %p\n",
        pAcpi_Addr_RSDP
        ));

    // Store RSDT address
    pAcpi_Addr_RSDT = (U8*)PLX_INT_TO_PTR( PHYS_MEM_READ_32( (U32*)(pAddress + 16) ) );

    PhysAddr.QuadPart = PLX_PTR_TO_INT( pAcpi_Addr_RSDT );

    // Map RSDT table
    Va_RSDT =
        MmMapIoSpace(
            PhysAddr,
            1024,
            MmNonCached
            );

    if (Va_RSDT == NULL)
        goto _Exit_PlxProbeForEcamBase;

    // Get RSDT size
    Acpi_Rsdt.Length = PHYS_MEM_READ_32( (U32*)(Va_RSDT + 4) );

    if (Acpi_Rsdt.Length == 0)
    {
        DebugPrintf(("ACPI Probe - Unable to read RSDT table length\n"));
        goto _Exit_PlxProbeForEcamBase;
    }

    // Calculate number of entries
    NumEntries = (U16)((Acpi_Rsdt.Length - sizeof(ACPI_RSDT_v1_0)) / sizeof(U32));

    DebugPrintf((
        "ACPI Probe - RSD table at %p has %d entries\n",
        pAcpi_Addr_RSDT,
        NumEntries
        ));

    if (NumEntries > 100)
    {
        DebugPrintf(("ACPI Probe - Unable to determine number of entries in RSDT table\n"));
        goto _Exit_PlxProbeForEcamBase;
    }

    // Get address of first entry
    pEntry = Va_RSDT + sizeof(ACPI_RSDT_v1_0);

    // Parse entry pointers for MCFG table
    while ((bFound == FALSE) && (NumEntries != 0))
    {
        // Get address of entry
        pAddress = (U8*)PLX_INT_TO_PTR( PHYS_MEM_READ_32( (U32*)pEntry ) );

        PhysAddr.QuadPart = PLX_PTR_TO_INT( pAddress );

        // Map table
        Va_Table =
            MmMapIoSpace(
                PhysAddr,
                200,
                MmNonCached
                );

        if (Va_Table == NULL)
            goto _Exit_PlxProbeForEcamBase;

        // Get table signature
        value = PHYS_MEM_READ_32( (U32*)Va_Table );

        DebugPrintf((
            "ACPI Probe - %c%c%c%c table at %p\n",
            (char)(value >>  0),
            (char)(value >>  8),
            (char)(value >> 16),
            (char)(value >> 24),
            pAddress
            ));

        // Check if MCFG table
        if (memcmp(
                "MCFG",
                &value,
                sizeof(U32)
                ) == 0)
        {
            // Get 64-bit base address of Enhanced Config Access Mechanism
            Gbl_Acpi_Addr_ECAM[0] = PHYS_MEM_READ_32( (U32*)(Va_Table + 44) );
            Gbl_Acpi_Addr_ECAM[1] = PHYS_MEM_READ_32( (U32*)(Va_Table + 48) );

            bFound = TRUE;
        }

        // Unmap table
        MmUnmapIoSpace(
            Va_Table,
            200
            );

        // Get address of next entry
        pEntry += sizeof(U32);

        // Decrement count
        NumEntries--;
    }

_Exit_PlxProbeForEcamBase:

    // Release the BIOS ROM mapping
    if (Va_BiosRom != NULL)
    {
        MmUnmapIoSpace(
            Va_BiosRom,
            (BIOS_MEM_END - BIOS_MEM_START)
            );
    }

    // Release RSDT mapping
    if (Va_RSDT != NULL)
    {
        MmUnmapIoSpace(
            Va_RSDT,
            1024
            );
    }

    if (bFound)
    {
        DebugPrintf((
            "ACPI Probe - PCIe ECAM at %08X_%08X\n",
            (unsigned int)Gbl_Acpi_Addr_ECAM[1], (unsigned int)Gbl_Acpi_Addr_ECAM[0]
            ));
    }
    else
    {
        DebugPrintf(("ACPI Probe - MCFG entry not found (PCIe ECAM not supported)\n"));
    }
}




/*******************************************************************************
 *
 * Function   :  PlxPhysicalMemRead
 *
 * Description:  Maps a memory location and performs a read from it
 *
 ******************************************************************************/
U64
PlxPhysicalMemRead(
    VOID *pAddress,
    U8    size
    )
{
    U64               value;
    VOID             *KernelVa;
    PHYSICAL_ADDRESS  PhysAddr;


    PhysAddr.QuadPart = PLX_PTR_TO_INT( pAddress );

    // Map address into kernel space
    KernelVa =
        MmMapIoSpace(
            PhysAddr,
            sizeof(U64),
            MmNonCached
            );

    if (KernelVa == NULL)
    {
        DebugPrintf(("ERROR - Unable to map address into kernel space\n"));
        return -1;
    }

    // Read memory
    switch (size)
    {
        case sizeof(U8):
            value = PHYS_MEM_READ_8( KernelVa );
            break;

        case sizeof(U16):
            value = PHYS_MEM_READ_16( KernelVa );
            break;

        case sizeof(U32):
            value = PHYS_MEM_READ_32( KernelVa );
            break;

        default:
            value = 0;
            break;
    }

    // Release the mapping
    MmUnmapIoSpace(
        KernelVa,
        sizeof(U64)
        );

    return value;
}





/*******************************************************************************
 *
 * Function   :  PlxPhysicalMemWrite
 *
 * Description:  Maps a memory location and performs a write to it
 *
 ******************************************************************************/
U32
PlxPhysicalMemWrite(
    VOID *pAddress,
    U64   value,
    U8    size
    )
{
    VOID             *KernelVa;
    PHYSICAL_ADDRESS  PhysAddr;


    PhysAddr.QuadPart = PLX_PTR_TO_INT( pAddress );

    // Map address into kernel space
    KernelVa =
        MmMapIoSpace(
            PhysAddr,
            sizeof(U64),
            MmNonCached
            );

    if (KernelVa == NULL)
    {
        DebugPrintf(("ERROR - Unable to map address into kernel space\n"));
        return -1;
    }

    // Write memory
    switch (size)
    {
        case sizeof(U8):
            PHYS_MEM_WRITE_8( KernelVa, (U8)value );
            break;

        case sizeof(U16):
            PHYS_MEM_WRITE_16( KernelVa, (U16)value );
            break;

        case sizeof(U32):
            PHYS_MEM_WRITE_32( KernelVa, (U32)value );
            break;
    }

    // Release the mapping
    MmUnmapIoSpace(
        KernelVa,
        sizeof(U64)
        );

    return 0;
}




/*******************************************************************************
 *
 * Function   :  GetBusSlotNumber
 *
 * Description:  Get the bus and slot number of a supported device
 *
 ******************************************************************************/
NTSTATUS
GetBusSlotNumber(
    DEVICE_OBJECT    *pdo,
    DEVICE_EXTENSION *pdx,
    U32               CompareValue
    )
{
    U32      ResultLength;
    U32      PropertyBuffer;
    U32      RegValue;
    U32      RegisterSave;
    NTSTATUS status;


    ResultLength = 0;

    // Get the bus number
    status =
        IoGetDeviceProperty(
             pdo,
             DevicePropertyBusNumber,
             sizeof(U32),
             &PropertyBuffer,
             &ResultLength
             );

    if ( !NT_SUCCESS(status) )
    {
        DebugPrintf((
            "ERROR - IoGetDeviceProperty() unable to get bus number, code = %Xh\n",
            status
            ));

        return status;
    }

    // Verify that the function did write back a U32
    if (ResultLength != sizeof(U32))
    {
        DebugPrintf((
            "ERROR - IoGetDeviceProperty() invalid ResultLength (%d)\n",
            ResultLength
            ));

        return STATUS_UNSUCCESSFUL;
    }

    // Store the Bus number
    pdx->Key.bus = (U8)PropertyBuffer;


    /**************************************************************************
     * Now attempt to get the slot number
     *
     * Note: The IoGetDeviceProperty() function is not implemented correctly in
     *       Windows 98.  The code has been left here in case this is fixed in a
     *       future release.  If the IoGetDeviceProperty() function is unsuccessful,
     *       a workaround is provided to retrieve the correct slot number.
     **************************************************************************/

    ResultLength = 0;

    status =
        IoGetDeviceProperty(
            pdo,
            DevicePropertyAddress,
            sizeof(U32),
            &PropertyBuffer,
            &ResultLength
            );

    if ( NT_SUCCESS(status) )
    {
        if (ResultLength != sizeof(U32))
        {
            DebugPrintf((
                "ERROR - IoGetDeviceProperty() invalid ResultLength (%d)\n",
                ResultLength
                ));

            return STATUS_UNSUCCESSFUL;
        }

        pdx->Key.slot     = (U8)(PropertyBuffer >> 16);
        pdx->Key.function = (U8)PropertyBuffer;

        DebugPrintf((
            "Device information - PCI bus %02x, slot %02x, function %d\n",
            pdx->Key.bus,
            pdx->Key.slot,
            pdx->Key.function
            ));

        return STATUS_SUCCESS;
    }


    /****************************************************************
    * We were unable to get the slot number, so another method is
    * required.  Our workaround is to scan the PCI bus and find the
    * device whose BAR0 matches the current device.
    ****************************************************************/

    DebugPrintf(("WARNING - IoGetDeviceProperty() unable to get slot number, code = "));

    switch (status)
    {
        case STATUS_BUFFER_TOO_SMALL:
            DebugPrintf_Cont(("STATUS_BUFFER_TOO_SMALL (req=%d bytes)\n", ResultLength));
            break;

        case STATUS_NOT_IMPLEMENTED:
            DebugPrintf_Cont(("STATUS_NOT_IMPLEMENTED\n"));
            break;

        case STATUS_NOT_FOUND:
            DebugPrintf_Cont(("STATUS_NOT_FOUND\n"));
            break;

        default:
            DebugPrintf_Cont(("%08xh\n", status));
            break;
    }

    DebugPrintf(("NOTE: Implementing workaround to get Slot number\n"));

    // Device is always function 0
    pdx->Key.function = 0;

    // Scan the PCI bus to find our device
    for (pdx->Key.slot = 0; pdx->Key.slot < MAX_PCI_DEV; pdx->Key.slot++)
    {
        // Read the BAR register
        PlxPciRegisterRead_BypassOS(
            pdx->Key.bus,
            pdx->Key.slot,
            pdx->Key.function,
            0x10,               // PCI BAR 0
            &RegValue
            );

        // Compare with our device
        if (RegValue == CompareValue)
        {
            DebugPrintf((
                "Workaround successful, device information - PCI bus %02x, slot %02x, function %d\n",
                pdx->Key.bus,
                pdx->Key.slot,
                pdx->Key.function
                ));

            return STATUS_SUCCESS;
        }
    }

    DebugPrintf(("ERROR - Unable to implement workaround to get slot number\n"));

    pdx->Key.slot = -1;

    return STATUS_UNSUCCESSFUL;
}
