www.pudn.com > PlxSdk.rar > ApiFunctions.c


/*******************************************************************************
 * Copyright (c) 2007 PLX Technology, Inc.
 *
 * PLX Technology Inc. licenses this software under specific terms and
 * conditions.  Use of any of the software or derviatives thereof in any
 * product without a PLX Technology chip is strictly prohibited.
 *
 * 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.  IN NO EVENT SHALL PLX'S TOTAL LIABILITY EXCEED THE SUM
 * PAID TO PLX FOR THE PRODUCT LICENSED HEREUNDER.
 *
 ******************************************************************************/

/*******************************************************************************
 *
 * File Name:
 *
 *      ApiFunctions.c
 *
 * Description:
 *
 *      Implements the PLX API functions
 *
 * Revision History:
 *
 *      06-01-07 : PLX SDK v5.10
 *
 ******************************************************************************/


#include "ApiFunctions.h"
#include "Eep_8000.h"
#include "PciSupport.h"
#include "PlxInterrupt.h"
#include "SupportFunc.h"




/*******************************************************************************
 *
 * Function   :  PlxDeviceFind
 *
 * Description:  Search for a specific device using device key parameters
 *
 ******************************************************************************/
RETURN_CODE
PlxDeviceFind(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U8               *pDeviceNumber
    )
{
    U8             DeviceCount;
    BOOLEAN        bMatchId;
    BOOLEAN        bMatchLoc;
    DEVICE_OBJECT *fdo;


    DeviceCount = 0;

    // Get first device instance in list
    fdo = pdx->pDeviceObject->DriverObject->DeviceObject;

    // Compare with items in device list
    while (fdo != NULL)
    {
        // Get the device extension
        pdx = fdo->DeviceExtension;

        // Assume successful match
        bMatchLoc = TRUE;
        bMatchId  = TRUE;

        //
        // Compare device key information
        //

        // Compare Bus number
        if (pKey->bus != (U8)PCI_FIELD_IGNORE)
        {
            if (pKey->bus != pdx->Key.bus)
            {
                bMatchLoc = FALSE;
            }
        }

        // Compare Slot number
        if (pKey->slot != (U8)PCI_FIELD_IGNORE)
        {
            if (pKey->slot != pdx->Key.slot)
            {
                bMatchLoc = FALSE;
            }
        }

        // Compare Function number
        if (pKey->function != (U8)PCI_FIELD_IGNORE)
        {
            if (pKey->function != pdx->Key.function)
            {
                bMatchLoc = FALSE;
            }
        }

        //
        // Compare device ID information
        //

        // Compare Vendor ID
        if (pKey->VendorId != (U16)PCI_FIELD_IGNORE)
        {
            if (pKey->VendorId != pdx->Key.VendorId)
            {
                bMatchId = FALSE;
            }
        }

        // Compare Device ID
        if (pKey->DeviceId != (U16)PCI_FIELD_IGNORE)
        {
            if (pKey->DeviceId != pdx->Key.DeviceId)
            {
                bMatchId = FALSE;
            }
        }

        // Compare Subsystem Vendor ID
        if (pKey->SubVendorId != (U16)PCI_FIELD_IGNORE)
        {
            if (pKey->SubVendorId != pdx->Key.SubVendorId)
            {
                bMatchId = FALSE;
            }
        }

        // Compare Subsystem Device ID
        if (pKey->SubDeviceId != (U16)PCI_FIELD_IGNORE)
        {
            if (pKey->SubDeviceId != pdx->Key.SubDeviceId)
            {
                bMatchId = FALSE;
            }
        }

        // Compare Revision
        if (pKey->Revision != (U8)PCI_FIELD_IGNORE)
        {
            if (pKey->Revision != pdx->Key.Revision)
            {
                bMatchId = FALSE;
            }
        }

        // Check if match on location and ID
        if (bMatchLoc && bMatchId)
        {
            // Match found, check if it is the desired device
            if (DeviceCount == *pDeviceNumber)
            {
                // Copy the device information
                *pKey = pdx->Key;

                DebugPrintf((
                    "Criteria matched device %04X_%04X [b:%02x s:%02x f:%02x]\n",
                    pdx->Key.DeviceId, pdx->Key.VendorId,
                    pdx->Key.bus, pdx->Key.slot, pdx->Key.function
                    ));

                return ApiSuccess;
            }

            // Increment device count
            DeviceCount++;
        }

        // Jump to next entry
        fdo = fdo->NextDevice;
    }

    // Return number of matched devices
    *pDeviceNumber = DeviceCount;

    DebugPrintf(("Criteria did not match any devices\n"));

    return ApiInvalidDeviceInfo;
}




/*******************************************************************************
 *
 * Function   :  PlxChipTypeGet
 *
 * Description:  Returns PLX chip type and revision
 *
 ******************************************************************************/
RETURN_CODE
PlxChipTypeGet(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32              *pChipType,
    U8               *pRevision
    )
{
    U32 RegValue;


    // Reset value
    *pChipType = 0;

    // Check PCI E0h for hard-coded ID
    PLX_PCI_REG_READ(
        pdx,
        0xE0,
        &RegValue
        );

    if ((RegValue & 0xFFFF) == PLX_VENDOR_ID)
    {
        *pChipType = (U16)(RegValue >> 16);

        // PLX revision should be in PCI E4h
        PLX_PCI_REG_READ(
            pdx,
            0xE4,
            &RegValue
            );

        *pRevision = (U8)(RegValue & 0xFF);

        goto _Exit_PlxChipTypeGet;
    }

    // Get revision if not yet assigned
    if (pKey->Revision == 0)
    {
        // Read revision ID from device
        PLX_PCI_REG_READ(
            pdx,
            0x08,     // Class code/revision ID
            &RegValue
            );

        pKey->Revision = (U8)RegValue;
    }

    // Set revision
    *pRevision = pKey->Revision;

    // If hard-coded ID doesn't exist, use Device/Vendor ID
    RegValue = ((U32)pKey->DeviceId << 16) | pKey->VendorId;

    switch (RegValue)
    {
        case 0x850810B5:        // 8508
        case 0x851210B5:        // 8512
        case 0x851610B5:        // 8516
        case 0x851710B5:        // 8517
        case 0x851810B5:        // 8518
        case 0x852410B5:        // 8524
        case 0x853210B5:        // 8532
            *pChipType = pKey->DeviceId;
            break;

        default:
            DebugPrintf(("ERROR - Unable to determine chip type\n"));
            return ApiInvalidDeviceInfo;
    }

_Exit_PlxChipTypeGet:
    DebugPrintf((
        "Device %04X_%04X = %04X rev %02X\n",
        pKey->DeviceId, pKey->VendorId, *pChipType, *pRevision
        ));

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxChipTypeSet
 *
 * Description:  Sets the PLX chip type dynamically
 *
 ******************************************************************************/
RETURN_CODE
PlxChipTypeSet(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32               ChipType,
    U8                Revision
    )
{
    // Setting the PLX chip type is not supported in this PnP driver
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxGetPortProperties
 *
 * Description:  Returns the current port information and status
 *
 ******************************************************************************/
RETURN_CODE
PlxGetPortProperties(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    PLX_PORT_PROP    *pPortProp
    )
{
    U16 Offset_PcieCap;
    U32 RegValue;


    // Get the offset of the PCI Express capability 
    Offset_PcieCap =
        PlxGetExtendedCapabilityOffset(
            pdx,
            0x10       // CAP_ID_PCI_EXPRESS
            );

    if (Offset_PcieCap == 0)
    {
        DebugPrintf(("Device does not support PCI Express Capability\n"));

        // Set default value for properties
        RtlZeroMemory(pPortProp, sizeof(PLX_PORT_PROP));
        pPortProp->PortType = PLX_PORT_NON_TRANS;
        return ApiSuccess;
    }

    // Get PCIe Capability
    PLX_PCI_REG_READ(
        pdx,
        Offset_PcieCap,
        &RegValue
        );

    // Get port type
    pPortProp->PortType = (U8)((RegValue >> 20) & 0xF);

    // Get PCIe Device Control
    PLX_PCI_REG_READ(
        pdx,
        Offset_PcieCap + 0x08,
        &RegValue
        );

    // Get max payload size field
    RegValue = (RegValue >> 5) & 0x7;

    // Set max payload size (=128 * (2^MaxPaySizeloadField))
    switch (RegValue)
    {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            pPortProp->MaxPayloadSize = 128 * (2 ^ RegValue);
            break;

        default:
            pPortProp->MaxPayloadSize = 0;
            break;
    }

    // Get PCIe Link Capabilities
    PLX_PCI_REG_READ(
        pdx,
        Offset_PcieCap + 0x0C,
        &RegValue
        );

    // Get port number
    pPortProp->PortNumber = (U8)((RegValue >> 24) & 0xFF);

    // Get max link width
    pPortProp->MaxLinkWidth = (U8)((RegValue >> 4) & 0x3F);

    // Get max link speed
    pPortProp->MaxLinkSpeed = (U8)((RegValue >> 0) & 0xF);

    // Get PCIe Link Status/Control
    PLX_PCI_REG_READ(
        pdx,
        Offset_PcieCap + 0x10,
        &RegValue
        );

    // Get link width
    pPortProp->LinkWidth = (U8)((RegValue >> 20) & 0x3F);

    // Get link speed
    pPortProp->LinkSpeed = (U8)((RegValue >> 16) & 0xF);

    DebugPrintf((
        "PortType=%d  PortNumber=%d  MaxPayload=%d  LinkWidth=x%d (max=x%d)\n",
        pPortProp->PortType, pPortProp->PortNumber,
        pPortProp->MaxPayloadSize, pPortProp->LinkWidth,
        pPortProp->MaxLinkWidth
        ));

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciBoardReset
 *
 * Description:  Resets a device using software reset feature of PLX chip
 *
 ******************************************************************************/
RETURN_CODE
PlxPciBoardReset(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey
    )
{
    // Device reset not implemented
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxRegisterRead
 *
 * Description:  Reads a PLX-specific control register
 *
 ******************************************************************************/
U32
PlxRegisterRead(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32               offset,
    RETURN_CODE      *pReturnCode,
    BOOLEAN           bAdjustForPort
    )
{
    U32 value;


    // Verify register offset
    if (offset >= 0x12000)
    {
        DebugPrintf(("ERROR - Invalid register offset (0x%x)\n", offset));

        if (pReturnCode != NULL)
            *pReturnCode = ApiInvalidOffset;

        return 0;
    }

    if (pReturnCode != NULL)
        *pReturnCode = ApiSuccess;

    // Adjust the offset for correct port
    if (bAdjustForPort)
    {
        if (pdx->bFromLinkSide == TRUE)
        {
            // Link side is 11000h from base of upstream port
            offset += 0x11000;
        }
        else
        {
            // Virtual side is 10000h from base of upstream port
            offset += 0x10000;
        }
    }

    // Read value
    value =
        PLX_8000_REG_READ(
            pdx,
            offset
            );

    return value;
}




/*******************************************************************************
 *
 * Function   :  PlxRegisterWrite
 *
 * Description:  Writes to a PLX-specific control register
 *
 ******************************************************************************/
RETURN_CODE
PlxRegisterWrite(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32               offset,
    U32               value,
    BOOLEAN           bAdjustForPort
    )
{
    // Verify register offset
    if (offset >= 0x12000)
    {
        DebugPrintf(("ERROR - Invalid register offset (0x%x)\n", offset));
        return ApiInvalidOffset;
    }

    // Adjust the offset for correct port
    if (bAdjustForPort)
    {
        if (pdx->bFromLinkSide == TRUE)
        {
            // Link side is 11000h from base of upstream port
            offset += 0x11000;
        }
        else
        {
            // Virtual side is 10000h from base of upstream port
            offset += 0x10000;
        }
    }

    // Write the value
    PLX_8000_REG_WRITE(
        pdx,
        offset,
        value
        );

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciBarProperties
 *
 * Description:  Returns the properties of a PCI BAR space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciBarProperties(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U8                BarIndex,
    PLX_PCI_BAR_PROP *pBarProperties
    )
{
    // Verify BAR number
    switch (BarIndex)
    {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            break;

        default:
            return ApiInvalidIndex;
    }

    // Get the actual BAR value in case OS didn't provide it
    if ((pdx->PciBar[BarIndex].Properties.BarValue == 0) &&
        (pdx->PciBar[BarIndex].Properties.Size != 0))
    {
        PLX_PCI_REG_READ(
            pdx,
            0x10 + (BarIndex * sizeof(U32)),
            &pdx->PciBar[BarIndex].Properties.BarValue
            );
    }

    // Return PCI BAR properties
    *pBarProperties = pdx->PciBar[BarIndex].Properties;

    // Display BAR properties
    DebugPrintf((
        "    PCI BAR %d: %08X\n",
        BarIndex, pdx->PciBar[BarIndex].Properties.BarValue
        ));

    DebugPrintf((
        "    Phys Addr: %08lX\n",
        (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Physical
        ));

    if (pdx->PciBar[BarIndex].Properties.Size >= (1 << 10))
    {
        DebugPrintf((
            "    Size     : %08lx  (%ld Kb)\n",
            (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Size,
            (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Size >> 10
            ));
    }
    else
    {
        DebugPrintf((
            "    Size     : %08lx  (%ld bytes)\n",
            (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Size,
            (PLX_UINT_PTR)pdx->PciBar[BarIndex].Properties.Size
            ));
    }

    DebugPrintf((
        "    Prefetch?: %s\n",
        (pdx->PciBar[BarIndex].Properties.bPrefetchable) ? "Yes" : "No"
        ));

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciBarMap
 *
 * Description:  Map a PCI BAR Space into User virtual space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciBarMap(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U8                BarIndex,
    VOID             *pUserVa,
    VOID             *pOwner
    )
{
    // Handled in Dispatch_mmap() in Linux
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxPciBarUnmap
 *
 * Description:  Unmap a previously mapped PCI BAR Space from User virtual space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciBarUnmap(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    VOID             *UserVa,
    VOID             *pOwner
    )
{
    // Handled at user API level in Linux
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromPresent
 *
 * Description:  Returns the state of the EEPROM as reported by the PLX device
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromPresent(
    DEVICE_EXTENSION  *pdx,
    PLX_DEVICE_KEY    *pKey,
    PLX_EEPROM_STATUS *pStatus
    )
{
    return Plx8000_EepromPresent(
               pdx,
               pStatus
               );
}




/*******************************************************************************
 *
 * Function   :  PlxEepromProbe
 *
 * Description:  Probes for the presence of an EEPROM
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromProbe(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    BOOLEAN          *pFlag
    )
{
    U16         OffsetProbe;
    U32         ValueRead;
    U32         ValueWrite;
    U32         ValueOriginal;
    RETURN_CODE rc;


    // Default to no EEPROM present
    *pFlag = FALSE;

    // Determine EEPROM offset to use for probe (e.g. after CRC)
    switch (pdx->PlxChipType)
    {
        case 0x8508:
        case 0x8512:
        case 0x8517:
        case 0x8518:
            OffsetProbe = (0x78F * sizeof(U32)) + sizeof(U32);
            break;

        case 0x8516:
        case 0x8524:
        case 0x8532:
            OffsetProbe = (0xBE4 * sizeof(U32)) + sizeof(U32);
            break;

        case 0:
        default:
            DebugPrintf((
                "ERROR - Not a supported PLX device (%04X)\n",
                pdx->PlxChipType
                ));
            return ApiUnsupportedFunction;
    }

    DebugPrintf(("Probing EEPROM at offset %02xh\n", OffsetProbe));

    // Get the current value
    rc =
        PlxEepromReadByOffset(
            pdx,
            pKey,
            OffsetProbe,
            &ValueOriginal
            );

    if (rc != ApiSuccess)
        return rc;

    // Prepare inverse value to write
    ValueWrite = ~(ValueOriginal);

    // Write inverse of original value
    rc =
        PlxEepromWriteByOffset(
            pdx,
            pKey,
            OffsetProbe,
            ValueWrite
            );

    if (rc != ApiSuccess)
        return rc;

    // Read updated value
    rc =
        PlxEepromReadByOffset(
            pdx,
            pKey,
            OffsetProbe,
            &ValueRead
            );

    if (rc != ApiSuccess)
        return rc;

    // Check if value was written properly
    if (ValueRead == ValueWrite)
    {
        DebugPrintf(("Probe detected an EEPROM present\n"));

        *pFlag = TRUE;

        // Restore the original value
        PlxEepromWriteByOffset(
            pdx,
            pKey,
            OffsetProbe,
            ValueOriginal
            );
    }
    else
    {
        DebugPrintf(("Probe did not detect an EEPROM\n"));
    }

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromSetAddressWidth
 *
 * Description:  Sets a new EEPROM address width
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromSetAddressWidth(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U8                width
    )
{
    RETURN_CODE rc;


    // Verify the width
    switch (width)
    {
        case 1:
        case 2:
        case 3:
            break;

        default:
            DebugPrintf((
                "ERROR - Invalid address width (%d)\n",
                width
                ));
            return ApiInvalidData;
    }

    rc =
        Plx8000_EepromSetAddressWidth(
            pdx,
            width
            );

    if (rc == ApiSuccess)
    {
        DebugPrintf((
            "Set EEPROM address width to %d bytes\n",
            width
            ));
    }

    return rc;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromCrcGet
 *
 * Description:  Returns the EEPROM CRC and its status
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromCrcGet(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32              *pCrc,
    U8               *pCrcStatus
    )
{
    switch (pdx->PlxChipType)
    {
        case 0x8508:
        case 0x8512:
        case 0x8516:
        case 0x8517:
        case 0x8518:
        case 0x8524:
        case 0x8532:
            return Plx8000_EepromCrcGet(
                pdx,
                pCrc,
                pCrcStatus
                );
    }

    // Clear return values
    *pCrc       = 0;
    *pCrcStatus = PLX_CRC_VALID;

    // Devices don't support CRC
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromCrcUpdate
 *
 * Description:  Updates the EEPROM CRC
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromCrcUpdate(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U32              *pCrc,
    BOOLEAN           bUpdateEeprom
    )
{
    switch (pdx->PlxChipType)
    {
        case 0x8508:
        case 0x8512:
        case 0x8516:
        case 0x8517:
        case 0x8518:
        case 0x8524:
        case 0x8532:
            return Plx8000_EepromCrcUpdate(
                pdx,
                pCrc,
                bUpdateEeprom
                );
    }

    // Clear return value
    *pCrc = 0;

    // Devices don't support CRC
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromReadByOffset
 *
 * Description:  Read a 32-bit value from the EEPROM at a specified offset
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromReadByOffset(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U16               offset,
    U32              *pValue
    )
{
    // Make sure offset is aligned on 32-bit boundary
    if (offset & (3 << 0))
    {
        *pValue = 0;
        return ApiInvalidOffset;
    }

    return Plx8000_EepromReadByOffset(
        pdx,
        offset,
        pValue
        );
}




/*******************************************************************************
 *
 * Function   :  PlxEepromWriteByOffset
 *
 * Description:  Write a 32-bit value to the EEPROM at a specified offset
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromWriteByOffset(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U16               offset,
    U32               value
    )
{
    // Make sure offset is aligned on 32-bit boundary
    if (offset & (3 << 0))
    {
        return ApiInvalidOffset;
    }

    return Plx8000_EepromWriteByOffset(
        pdx,
        offset,
        value
        );
}




/*******************************************************************************
 *
 * Function   :  PlxEepromReadByOffset_16
 *
 * Description:  Read a 16-bit value from the EEPROM at a specified offset
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromReadByOffset_16(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U16               offset,
    U16              *pValue
    )
{
    U32         Value_32;
    RETURN_CODE rc;


    // Make sure offset is aligned on 16-bit boundary
    if (offset & (1 << 0))
    {
        *pValue = 0;
        return ApiInvalidOffset;
    }

    /******************************************
     * For devices that do not support 16-bit
     * EEPROM accesses, use 32-bit access
     *****************************************/

    // Get current 32-bit value
    rc =
        PlxEepromReadByOffset(
            pdx,
            pKey,
            (offset & ~0x3),
            &Value_32
            );

    if (rc != ApiSuccess)
    {
        *pValue = 0;
        return rc;
    }

    // Return desired 16-bit portion
    if ((offset & 0x3) == 2)
    {
        *pValue = (U16)(Value_32 >> 16);
    }
    else
    {
        *pValue = (U16)(Value_32);
    }

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxEepromWriteByOffset_16
 *
 * Description:  Write a 16-bit value to the EEPROM at a specified offset
 *
 ******************************************************************************/
RETURN_CODE
PlxEepromWriteByOffset_16(
    DEVICE_EXTENSION *pdx,
    PLX_DEVICE_KEY   *pKey,
    U16               offset,
    U16               value
    )
{
    U32         Value_32;
    RETURN_CODE rc;


    // Make sure offset is aligned on 16-bit boundary
    if (offset & (1 << 0))
    {
        return ApiInvalidOffset;
    }

    /******************************************
     * For devices that do not support 16-bit
     * EEPROM accesses, use 32-bit access
     *****************************************/

    // Get current 32-bit value
    rc =
        PlxEepromReadByOffset(
            pdx,
            pKey,
            (offset & ~0x3),
            &Value_32
            );

    if (rc != ApiSuccess)
        return rc;

    // Insert new 16-bit value in correct location
    if ((offset & 0x3) == 2)
    {
        Value_32 = ((U32)value << 16) | (Value_32 & 0xFFFF);
    }
    else
    {
        Value_32 = ((U32)value) | (Value_32 & 0xFFFF0000);
    }

    // Update EEPROM
    return PlxEepromWriteByOffset(
        pdx,
        pKey,
        (offset & ~0x3),
        Value_32
        );
}




/*******************************************************************************
 *
 * Function   :  PlxPciIoPortTransfer
 *
 * Description:  Read or Write from/to an I/O port
 *
 ******************************************************************************/
RETURN_CODE
PlxPciIoPortTransfer(
    U64              IoPort,
    VOID            *pBuffer,
    U32              SizeInBytes,
    PLX_ACCESS_TYPE  AccessType,
    BOOLEAN          bReadOperation
    )
{
    if (pBuffer == NULL)
        return ApiNullParam;

    // Verify size & type
    switch (AccessType)
    {
        case BitSize8:
            break;

        case BitSize16:
            if (IoPort & (1 << 0))
            {
                DebugPrintf(("ERROR - I/O port not aligned on 16-bit boundary\n"));
                return ApiInvalidAddress;
            }

            if (SizeInBytes & (1 << 0))
            {
                DebugPrintf(("ERROR - Byte count not aligned on 16-bit boundary\n"));
                return ApiInvalidSize;
            }
            break;

        case BitSize32:
            if (IoPort & 0x3)
            {
                DebugPrintf(("ERROR - I/O port not aligned on 32-bit boundary\n"));
                return ApiInvalidAddress;
            }

            if (SizeInBytes & 0x3)
            {
                DebugPrintf(("ERROR - Byte count not aligned on 32-bit boundary\n"));
                return ApiInvalidSize;
            }
            break;

        default:
            return ApiInvalidAccessType;
    }

    if (bReadOperation)
    {
        switch (AccessType)
        {
            case BitSize8:
                IO_PORT_READ_8_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            case BitSize16:
                IO_PORT_READ_16_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            case BitSize32:
                IO_PORT_READ_32_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            default:
                // Added to avoid compiler warnings
                break;
        }
    }
    else
    {
        switch (AccessType)
        {
            case BitSize8:
                IO_PORT_WRITE_8_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            case BitSize16:
                IO_PORT_WRITE_16_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            case BitSize32:
                IO_PORT_WRITE_32_BUFFER(
                    IoPort,
                    pBuffer,
                    SizeInBytes
                    );
                break;

            default:
                // Added to avoid compiler warnings
                break;
        }
    }

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciPhysicalMemoryAllocate
 *
 * Description:  Allocate physically contiguous page-locked memory
 *
 ******************************************************************************/
RETURN_CODE
PlxPciPhysicalMemoryAllocate(
    DEVICE_EXTENSION *pdx,
    PLX_PHYSICAL_MEM *pPciMem,
    BOOLEAN           bSmallerOk,
    VOID             *pOwner
    )
{
    U32                  DecrementAmount;
    PLX_PHYS_MEM_OBJECT *pBufferObj;


    // Initialize buffer information
    pPciMem->UserAddr     = 0;
    pPciMem->PhysicalAddr = 0;
    pPciMem->CpuPhysical  = 0;

    /*******************************************************
     * Verify size
     *
     * A size of 0 is valid because this function may
     * be called to allocate a common buffer of size 0;
     * therefore, the information is reset & return sucess.
     ******************************************************/
    if (pPciMem->Size == 0)
    {
        return ApiSuccess;
    }

    // Allocate memory for new list object
    pBufferObj =
        kmalloc(
            sizeof(PLX_PHYS_MEM_OBJECT),
            GFP_KERNEL
            );

    if (pBufferObj == NULL)
    {
        DebugPrintf((
            "ERROR - Memory allocation for list object failed\n"
            ));

        return ApiInsufficientResources;
    }

    // Set buffer request size
    pBufferObj->Size = pPciMem->Size;

    // Setup amount to reduce on failure
    DecrementAmount = (pPciMem->Size / 10);

    DebugPrintf((
        "Attempting to allocate physical memory (%d Kb)...\n",
        (pPciMem->Size >> 10)
        ));

    do
    {
        // Attempt to allocate the buffer
        pBufferObj->pKernelVa =
            Plx_dma_buffer_alloc(
                pdx,
                pBufferObj
                );

        if (pBufferObj->pKernelVa == NULL)
        {
            // Reduce memory request size if requested
            if (bSmallerOk && (pBufferObj->Size > PAGE_SIZE))
            {
                pBufferObj->Size -= DecrementAmount;
            }
            else
            {
                // Release the list object
                kfree(
                    pBufferObj
                    );

                DebugPrintf((
                    "ERROR - Physical memory allocation failed\n"
                    ));

                pPciMem->Size = 0;

                return ApiInsufficientResources;
            }
        }
    }
    while (pBufferObj->pKernelVa == NULL);

    // Record buffer owner
    pBufferObj->pOwner = pOwner;

    // Assign buffer to device if provided
    if (pOwner != pGbl_DriverObject)
    {
        // Return buffer information
        pPciMem->Size         = pBufferObj->Size;
        pPciMem->PhysicalAddr = pBufferObj->BusPhysical;
        pPciMem->CpuPhysical  = pBufferObj->CpuPhysical;

        // Add buffer object to list
        spin_lock(
            &(pdx->Lock_PhysicalMemList)
            );

        list_add_tail(
            &(pBufferObj->ListEntry),
            &(pdx->List_PhysicalMem)
            );

        spin_unlock(
            &(pdx->Lock_PhysicalMemList)
            );
    }
    else
    {
        // Store common buffer information
        pGbl_DriverObject->CommonBuffer = *pBufferObj;

        // Release the list object
        kfree(
            pBufferObj
            );
    }

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxPciPhysicalMemoryFree
 *
 * Description:  Free previously allocated physically contiguous page-locked memory
 *
 ******************************************************************************/
RETURN_CODE
PlxPciPhysicalMemoryFree(
    DEVICE_EXTENSION *pdx,
    PLX_PHYSICAL_MEM *pPciMem
    )
{
    struct list_head    *pEntry;
    PLX_PHYS_MEM_OBJECT *pBufferObj;


    spin_lock(
        &(pdx->Lock_PhysicalMemList)
        );

    pEntry = pdx->List_PhysicalMem.next;

    // Traverse list to find the desired list object
    while (pEntry != &(pdx->List_PhysicalMem))
    {
        // Get the object
        pBufferObj =
            list_entry(
                pEntry,
                PLX_PHYS_MEM_OBJECT,
                ListEntry
                );

        // Check if the physical addresses matches
        if (pBufferObj->BusPhysical == pPciMem->PhysicalAddr)
        {
            // Remove the object from the list
            list_del(
                pEntry
                );

            spin_unlock(
                &(pdx->Lock_PhysicalMemList)
                );

            // Release the buffer
            Plx_dma_buffer_free(
                pdx,
                pBufferObj
                );

            // Release the list object
            kfree(
                pBufferObj
                );

            return ApiSuccess;
        }

        // Jump to next item in the list
        pEntry = pEntry->next;
    }

    spin_unlock(
        &(pdx->Lock_PhysicalMemList)
        );

    DebugPrintf((
        "ERROR - buffer object not found in list\n"
        ));

    return ApiInvalidData;
}




/*******************************************************************************
 *
 * Function   :  PlxPciPhysicalMemoryMap
 *
 * Description:  Maps physical memory to User virtual address space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciPhysicalMemoryMap(
    DEVICE_EXTENSION *pdx,
    PLX_PHYSICAL_MEM *pPciMem,
    VOID             *pOwner
    )
{
    // Handled in Dispatch_mmap() in Linux
    return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxPciPhysicalMemoryUnmap
 *
 * Description:  Unmap physical memory from User virtual address space
 *
 ******************************************************************************/
RETURN_CODE
PlxPciPhysicalMemoryUnmap(
    DEVICE_EXTENSION *pdx,
    PLX_PHYSICAL_MEM *pPciMem,
    VOID             *pOwner
    )
{
    // Handled by user-level API in Linux
    return ApiUnsupportedFunction;
}




/******************************************************************************
 *
 * Function   :  PlxInterruptEnable
 *
 * Description:  Enables specific interupts of the PLX Chip
 *
 ******************************************************************************/
RETURN_CODE
PlxInterruptEnable(
    DEVICE_EXTENSION *pdx,
    PLX_INTERRUPT    *pPlxIntr
    )
{
// DBG - Not implemented yet
return ApiUnsupportedFunction;
}




/******************************************************************************
 *
 * Function   :  PlxInterruptDisable
 *
 * Description:  Disables specific interrupts of the PLX Chip
 *
 ******************************************************************************/
RETURN_CODE
PlxInterruptDisable(
    DEVICE_EXTENSION *pdx,
    PLX_INTERRUPT    *pPlxIntr
    )
{
// DBG - Not implemented yet
return ApiUnsupportedFunction;
}




/*******************************************************************************
 *
 * Function   :  PlxNotificationRegisterFor
 *
 * Description:  Registers a wait object for notification on interrupt(s)
 *
 ******************************************************************************/
RETURN_CODE
PlxNotificationRegisterFor(
    DEVICE_EXTENSION  *pdx,
    PLX_INTERRUPT     *pPlxIntr,
    VOID             **pUserWaitObject,
    VOID              *pOwner
    )
{
    unsigned long    flags;
    PLX_WAIT_OBJECT *pWaitObject;


    // Allocate a new wait object
    pWaitObject =
        kmalloc(
            sizeof(PLX_WAIT_OBJECT),
            GFP_KERNEL
            );

    if (pWaitObject == NULL)
    {
        DebugPrintf((
            "ERROR - memory allocation for interrupt wait object failed\n"
            ));

        return ApiInsufficientResources;
    }

    // Provide the wait object to the user app
    *pUserWaitObject = pWaitObject;

    // Record the owner
    pWaitObject->pOwner = pOwner;

    // Clear interrupt source
    pWaitObject->Source_Ints     = INTR_TYPE_NONE;
    pWaitObject->Source_Doorbell = 0;

    // Mark the object as pending
    pWaitObject->bPending = TRUE;

    // Initialize wait queue
    init_waitqueue_head(
        &(pWaitObject->WaitQueue)
        );

    // Set interrupt notification flags
    pWaitObject->Notify_Flags    = INTR_TYPE_NONE;
    pWaitObject->Notify_Doorbell = pPlxIntr->Doorbell;

    // Add to list of waiting objects
    spin_lock_irqsave(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    list_add_tail(
        &(pWaitObject->ListEntry),
        &(pdx->List_WaitObjects)
        );

    spin_unlock_irqrestore(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    DebugPrintf((
        "Registered interrupt wait object (%p)\n",
        pWaitObject
        ));

    return ApiSuccess;
}




/*******************************************************************************
 *
 * Function   :  PlxNotificationWait
 *
 * Description:  Put the process to sleep until wake-up event occurs or timeout
 *
 ******************************************************************************/
RETURN_CODE
PlxNotificationWait(
    DEVICE_EXTENSION *pdx,
    VOID             *pUserWaitObject,
    PLX_UINT_PTR      Timeout_ms
    )
{
    int               Wait_rc;
    RETURN_CODE       rc;
    PLX_UINT_PTR      Timeout_sec;
    unsigned long     flags;
    struct list_head *pEntry;
    PLX_WAIT_OBJECT  *pWaitObject;


    // Find the wait object in the list
    spin_lock_irqsave(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    pEntry = pdx->List_WaitObjects.next;

    // Find the wait object and wait for wake-up event
    while (pEntry != &(pdx->List_WaitObjects))
    {
        // Get the wait object
        pWaitObject =
            list_entry(
                pEntry,
                PLX_WAIT_OBJECT,
                ListEntry
                );

        // Check if the object address matches the Tag
        if (pWaitObject == pUserWaitObject)
        {
            spin_unlock_irqrestore(
                &(pdx->Lock_WaitObjectsList),
                flags
                );

            DebugPrintf((
                "Waiting for Interrupt wait object (%p) to wake-up\n",
                pWaitObject
                ));

            /*********************************************************
             * Convert milliseconds to jiffies.  The following
             * formula is used:
             *
             *                      ms * HZ
             *           jiffies = ---------
             *                       1,000
             *
             *
             *  where:  HZ      = System-defined clock ticks per second
             *          ms      = Timeout in milliseconds
             *          jiffies = Number of HZ's per second
             *
             *  Note: Since the timeout is stored as a "long" integer,
             *        the conversion to jiffies is split into two operations.
             *        The first is on number of seconds and the second on
             *        the remaining millisecond precision.  This mimimizes
             *        overflow when the specified timeout is large and also
             *        keeps millisecond precision.
             ********************************************************/

            // Perform conversion if not infinite wait
            if (Timeout_ms != PLX_TIMEOUT_INFINITE)
            {
                // Get number of seconds
                Timeout_sec = Timeout_ms / 1000;

                // Store milliseconds precision
                Timeout_ms = Timeout_ms - (Timeout_sec * 1000);

                // Convert to jiffies
                Timeout_sec = Timeout_sec * HZ;
                Timeout_ms  = (Timeout_ms * HZ) / 1000;

                // Compute total jiffies
                Timeout_ms = Timeout_sec + Timeout_ms;
            }

            // Timeout parameter is signed and can't be negative
            if ((signed long)Timeout_ms < 0)
            {
                // Shift out negative bit
                Timeout_ms = Timeout_ms >> 1;
            }

            do
            {
                // Wait for interrupt event
                Wait_rc =
                    Plx_wait_event_interruptible_timeout(
                        pWaitObject->WaitQueue,
                        (pWaitObject->bPending == FALSE),
                        Timeout_ms
                        );
            }
            while ((Wait_rc == 0) && (Timeout_ms == PLX_TIMEOUT_INFINITE));

            if (Wait_rc > 0)
            {
                // Condition met or interrupt occurred
                DebugPrintf(("Interrupt wait object awakened\n"));
                rc = ApiSuccess;
            }
            else if (Wait_rc == 0)
            {
                // Timeout reached
                DebugPrintf(("Timeout waiting for interrupt\n"));
                return ApiWaitTimeout;
            }
            else
            {
                // Interrupted by a signal
                DebugPrintf(("Interrupt wait object interrupted by signal or error\n"));
                rc = ApiWaitCanceled;
            }

            // Mark the object as pending to reset it
            pWaitObject->bPending = TRUE;

            return rc;
        }

        // Jump to next item in the list
        pEntry = pEntry->next;
    }

    spin_unlock_irqrestore(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    DebugPrintf((
        "Interrupt wait object (%p) not found or previously canceled\n",
        pUserWaitObject
        ));

    // Object not found at this point
    return ApiFailed;
}




/*******************************************************************************
 *
 * Function   :  PlxNotificationStatus
 *
 * Description:  Returns the interrupt(s) that have caused notification events
 *
 ******************************************************************************/
RETURN_CODE
PlxNotificationStatus(
    DEVICE_EXTENSION *pdx,
    VOID             *pUserWaitObject,
    PLX_INTERRUPT    *pPlxIntr
    )
{
    unsigned long       flags;
    struct list_head   *pEntry;
    PLX_WAIT_OBJECT    *pWaitObject;
    PLX_INTERRUPT_DATA  IntData;


    spin_lock_irqsave(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    pEntry = pdx->List_WaitObjects.next;

    // Traverse list to find the desired list object
    while (pEntry != &(pdx->List_WaitObjects))
    {
        // Get the object
        pWaitObject =
            list_entry(
                pEntry,
                PLX_WAIT_OBJECT,
                ListEntry
                );

        // Check if desired object
        if (pWaitObject == pUserWaitObject)
        {
            // Copy the interrupt sources
            IntData.Source_Ints     = pWaitObject->Source_Ints;
            IntData.Source_Doorbell = pWaitObject->Source_Doorbell;

            // Reset interrupt sources
            pWaitObject->Source_Ints     = INTR_TYPE_NONE;
            pWaitObject->Source_Doorbell = 0;

            spin_unlock_irqrestore(
                &(pdx->Lock_WaitObjectsList),
                flags
                );

            DebugPrintf((
                "Returning status for interrupt wait object (%p)...\n",
                pWaitObject
                ));

            // Set triggered interrupts
            RtlZeroMemory(
                pPlxIntr,
                sizeof(PLX_INTERRUPT)
                );

            pPlxIntr->Doorbell = IntData.Source_Doorbell;

            return ApiSuccess;
        }

        // Jump to next item in the list
        pEntry = pEntry->next;
    }

    spin_unlock_irqrestore(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    return ApiFailed;
}




/*******************************************************************************
 *
 * Function   :  PlxNotificationCancel
 *
 * Description:  Cancels a registered notification event
 *
 ******************************************************************************/
RETURN_CODE
PlxNotificationCancel(
    DEVICE_EXTENSION *pdx,
    VOID             *pUserWaitObject,
    VOID             *pOwner
    )
{
    BOOLEAN           bRemove;
    unsigned long     flags;
    struct list_head *pEntry;
    PLX_WAIT_OBJECT  *pWaitObject;


    spin_lock_irqsave(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    pEntry = pdx->List_WaitObjects.next;

    // Find the object and remove it
    while (pEntry != &(pdx->List_WaitObjects))
    {
        // Get the object
        pWaitObject =
            list_entry(
                pEntry,
                PLX_WAIT_OBJECT,
                ListEntry
                );

        // Default to not remove
        bRemove = FALSE;

        // Determine if object should be removed
        if (pOwner == pWaitObject->pOwner)
        {
            if (pUserWaitObject == NULL)
            {
                bRemove = TRUE;
            }
            else if (pWaitObject == pUserWaitObject)
            {
                bRemove = TRUE;
            }
        }

        // Remove object
        if (bRemove)
        {
            DebugPrintf((
                "Removing interrupt wait object (%p)...\n",
                pWaitObject
                ));

            // Remove the object from the list
            list_del(
                pEntry
                );

            spin_unlock_irqrestore(
                &(pdx->Lock_WaitObjectsList),
                flags
                );

            // Wake-up processes only if wait object is pending
            if (pWaitObject->bPending == TRUE)
            {
                // Wake-up any process waiting on the object
                wake_up_interruptible(
                    &(pWaitObject->WaitQueue)
                    );
            }

            // Release the list object
            kfree(
                pWaitObject
                );

            // Return if removing only a specific object
            if (pUserWaitObject != NULL)
                return ApiSuccess;

            // Reset to beginning of list
            spin_lock_irqsave(
                &(pdx->Lock_WaitObjectsList),
                flags
                );

            pEntry = pdx->List_WaitObjects.next;
        }
        else
        {
            // Jump to next item in the list
            pEntry = pEntry->next;
        }
    }

    spin_unlock_irqrestore(
        &(pdx->Lock_WaitObjectsList),
        flags
        );

    return ApiFailed;
}