www.pudn.com > PlxSdk.rar > Driver.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:
 *
 *      Driver.c
 *
 * Description:
 *
 *      Initializes the driver and claims system resources for the device
 *
 * Revision History:
 *
 *      06-01-07 : PLX SDK v5.10
 *
 *****************************************************************************/


#include 
#include 
#include 
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))
    #include 
#endif
#include "ApiFunctions.h"
#include "Dispatch.h"
#include "Driver.h"
#include "PciSupport.h"
#include "PlxChipFn.h"
#include "PlxInterrupt.h"
#include "Plx_sysdep.h"
#include "SupportFunc.h"




/***********************************************
 *     List of IDs the driver will support
 *
 * Note: Static allocation for now.  Will move
 *       to some dynamic configuration later.
 **********************************************/
char SupportedDevices[] =
    "850810B5 851210B5 851610B5 851710B5 851810B5 852410B5 853210B5";




/***********************************************
 *               Globals
 **********************************************/
// Pointer to the main driver object
DRIVER_OBJECT *pGbl_DriverObject_8000;




/******************************************************************************
 *
 * Function   :  Plx_init_module
 *
 * Description:  Entry point for the driver
 *
 ******************************************************************************/
int __init
Plx_init_module(
    void
    )
{
    PLX_PHYSICAL_MEM PhysicalMem;


    DebugPrintf_NoInfo(("\n"));
    DebugPrintf(("<========================================================>\n"));
    DebugPrintf((
        "PLX driver v%d.%d%d (%d-bit) - built on %s %s\n",
        PLX_SDK_VERSION_MAJOR, PLX_SDK_VERSION_MINOR,
        PLX_SDK_VERSION_REVISION, (U32)(sizeof(PLX_UINT_PTR) * 8),
        __DATE__, __TIME__
        ));

    DebugPrintf((
        "Supports Linux kernel version %s\n",
        UTS_RELEASE
        ));

    // Allocate memory for the Driver Object
    pGbl_DriverObject =
        kmalloc(
            sizeof(DRIVER_OBJECT),
            GFP_KERNEL
            );

    if (pGbl_DriverObject == NULL)
    {
        ErrorPrintf(("ERROR - memory allocation for Driver Object failed\n"));
        return (-ENOMEM);
    }

    DebugPrintf((
        "Allocated global driver object (%p)\n",
        pGbl_DriverObject
        ));

    // Clear the driver object
    RtlZeroMemory(
        pGbl_DriverObject,
        sizeof(DRIVER_OBJECT)
        );

    // Initialize driver object
    pGbl_DriverObject->DeviceObject = NULL;
    pGbl_DriverObject->SupportedIDs = SupportedDevices;
    pGbl_DriverObject->DeviceCount  = 0;

    // Fill in the appropriate dispatch handlers
    pGbl_DriverObject->DispatchTable.owner   = THIS_MODULE;
    pGbl_DriverObject->DispatchTable.ioctl   = Dispatch_IoControl;
    pGbl_DriverObject->DispatchTable.mmap    = Dispatch_mmap;
    pGbl_DriverObject->DispatchTable.open    = Dispatch_open;
    pGbl_DriverObject->DispatchTable.release = Dispatch_release;

    // Initialize spin locks
    spin_lock_init(
        &(pGbl_DriverObject->Lock_DeviceList)
        );

    /*********************************************************
     * Register the driver with the OS
     *
     * NOTE: This driver still uses the old method for registering
     * the device (register_chrdev) for compatability with 2.4 kernels.
     * A future version of the driver may use the new interface
     * (cdev_init, cdev_add, & cdev_del).
     ********************************************************/
    pGbl_DriverObject->MajorID =
        register_chrdev(
            0,              // 0 = system chooses Major ID
            PLX_DRIVER_NAME,
            &(pGbl_DriverObject->DispatchTable)
            );

    DebugPrintf((
        "Registered driver (MajorID = %03d)\n",
        pGbl_DriverObject->MajorID
        ));

    // Scan the system for supported devices
    PlxDeviceListBuild(
        pGbl_DriverObject
        );

    // Check if any devices were found
    if (pGbl_DriverObject->DeviceCount == 0)
    {
        ErrorPrintf(("ERROR - No supported devices found\n"));

        // Unload the driver
        Plx_cleanup_module();

        return (-ENODEV);
    }

    DebugPrintf((
        "Allocating common buffer...\n"
        ));

    // Initialize common buffer
    pGbl_DriverObject->CommonBuffer.Size = 0;

    // Set requested size
    PhysicalMem.Size = DEFAULT_SIZE_COMMON_BUFFER;

    // Allocate common buffer
    PlxPciPhysicalMemoryAllocate(
        pGbl_DriverObject->DeviceObject->DeviceExtension,  // Assign buffer to first device
        &PhysicalMem,
        TRUE,                                              // Smaller buffer is ok
        pGbl_DriverObject                                  // Assign Driver object as owner
        );

    // Probe ACPI tables for PCI Express mechanism
    PlxProbeForEcamBase();

    DebugPrintf(("...driver loaded\n"));

    return 0;
}




/******************************************************************************
 *
 * Function   :  Plx_cleanup_module
 *
 * Description:  Unload the driver
 *
 ******************************************************************************/
void __exit
Plx_cleanup_module(
    void
    )
{
    DEVICE_OBJECT *fdo;
    DEVICE_OBJECT *pNext;


    DebugPrintf_NoInfo(("\n"));
    DebugPrintf(("Unloading driver...\n"));

    // Release common buffer
    if (pGbl_DriverObject->CommonBuffer.Size != 0)
    {
        DebugPrintf(("De-allocating Common Buffer...\n"));

        // Release the buffer
        Plx_dma_buffer_free(
            pGbl_DriverObject->DeviceObject->DeviceExtension,   // First device
            &(pGbl_DriverObject->CommonBuffer)
            );
    }

    // Get the device list
    fdo = pGbl_DriverObject->DeviceObject;

    // Remove all devices
    while (fdo != NULL)
    {
        // Store next device
        pNext = fdo->NextDevice;

        // Stop device and release its resources
        StopDevice(
            fdo
            );

        // Delete the device & remove from device list
        RemoveDevice(
            fdo
            );

        // Jump to next device object
        fdo = pNext;
    }

    DebugPrintf((
        "De-register driver (MajorID = %03d)\n",
        pGbl_DriverObject->MajorID
        ));

    /*********************************************************
     * De-register the driver with the OS
     *
     * NOTE: This driver still uses the old method for de-registering
     * the device (unregister_chrdev) for compatability with 2.4 kernels.
     * A future version of the driver may use the new interface
     * (cdev_init, cdev_add, & cdev_del).
     ********************************************************/
    unregister_chrdev(
        pGbl_DriverObject->MajorID,
        PLX_DRIVER_NAME
        );

    DebugPrintf((
        "Releasing global driver object (%p)...\n",
        pGbl_DriverObject
        ));

    // Release driver object
    kfree(
        pGbl_DriverObject
        );

    pGbl_DriverObject = NULL;

    DebugPrintf(("...driver unloaded\n"));
}




/**************************************************
 * The module_XX macros are only needed if the
 * driver entry and exit routine differ from the
 * default of init_module() and exit_module().
 * These macros must be placed AFTER the module code.
 **************************************************/
module_init(Plx_init_module);
module_exit(Plx_cleanup_module);




/******************************************************************************
 *
 * Function   :  AddDevice
 *
 * Description:  Add a new device object to the driver
 *
 ******************************************************************************/
int
AddDevice(
    DRIVER_OBJECT  *pDriverObject,
    struct pci_dev *pPciDev
    )
{
    DEVICE_OBJECT    *fdo;
    DEVICE_OBJECT    *pDevice;
    DEVICE_EXTENSION *pdx;


    // Allocate memory for the device object
    fdo =
        kmalloc(
            sizeof(DEVICE_OBJECT),
            GFP_KERNEL
            );

    if (fdo == NULL)
    {
        ErrorPrintf(("ERROR - memory allocation for device object failed\n"));
        return (-ENOMEM);
    }

    // Initialize device object
    RtlZeroMemory(
        fdo,
        sizeof(DEVICE_OBJECT)
        );

    fdo->DriverObject    = pDriverObject;         // Save parent driver object
    fdo->DeviceExtension = &(fdo->DeviceInfo);


    //
    // Initialize the device extension
    //

    pdx = fdo->DeviceExtension;

    // Clear device extension
    RtlZeroMemory(
        pdx,
        sizeof(DEVICE_EXTENSION)
        );

    // Store parent device object
    pdx->pDeviceObject = fdo;

    // Save the OS-supplied PCI object
    pdx->pPciDevice = pPciDev;

    // Set initial power state
    pdx->PowerState = D0;

    // Store device location information
    pdx->Key.bus          = pPciDev->bus->number;
    pdx->Key.slot         = PCI_SLOT(pPciDev->devfn);
    pdx->Key.function     = PCI_FUNC(pPciDev->devfn);
    pdx->Key.DeviceId     = pPciDev->device;
    pdx->Key.VendorId     = pPciDev->vendor;
    pdx->Key.DeviceNumber = pDriverObject->DeviceCount;

    // Build device name
    sprintf(
        pdx->LinkName,
        PLX_DRIVER_NAME "-%d",
        pDriverObject->DeviceCount
        );

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0))
    // Initialize task element for ISR DPC queueing
    pdx->Task_DpcForIsr.sync    = 0;
    pdx->Task_DpcForIsr.routine = DpcForIsr;               // DPC routine
    pdx->Task_DpcForIsr.data    = &(pdx->Task_DpcForIsr);  // DPC parameter
#else
    // Initialize work queue for ISR DPC queueing
    PLX_INIT_WORK(
        &(pdx->Task_DpcForIsr),
        DpcForIsr,                // DPC routine
        &(pdx->Task_DpcForIsr)    // DPC parameter (pre-2.6.20 only)
        );
#endif

    // Initialize device open spinlock
    spin_lock_init(
        &(pdx->Lock_DeviceOpen)
        );

    // Initialize ISR spinlock
    spin_lock_init(
        &(pdx->Lock_Isr)
        );

    // Initialize interrupt wait list
    INIT_LIST_HEAD(
        &(pdx->List_WaitObjects)
        );

    spin_lock_init(
        &(pdx->Lock_WaitObjectsList)
        );

    // Initialize physical memories list
    INIT_LIST_HEAD(
        &(pdx->List_PhysicalMem)
        );

    spin_lock_init(
        &(pdx->Lock_PhysicalMemList)
        );


    //
    // Add to driver device list
    //

    // Acquire Device List lock
    spin_lock(
        &(pDriverObject->Lock_DeviceList)
        );

    // Get device list head
    pDevice = pDriverObject->DeviceObject;

    if (pDevice == NULL)
    {
        // Add device as first in list
        pDriverObject->DeviceObject = fdo;
    }
    else
    {
        // Go to end of list
        while (pDevice->NextDevice != NULL)
            pDevice = pDevice->NextDevice;

        // Add device to end of list
        pDevice->NextDevice = fdo;
    }

    // Increment device count
    pDriverObject->DeviceCount++;

    // Release Device List lock
    spin_unlock(
        &(pDriverObject->Lock_DeviceList)
        );

    DebugPrintf((
        "Created Device (%s)...\n",
        pdx->LinkName
        ));

    // Enable the device
    if (pci_enable_device(
            pPciDev
            ) != 0)
    {
        DebugPrintf(("ERROR - PCI device enable failed\n"));
    }

    DebugPrintf_NoInfo(("\n"));

    return 0;
}




/******************************************************************************
 *
 * Function   :  RemoveDevice
 *
 * Description:  Remove a functional device object
 *
 ******************************************************************************/
int
RemoveDevice(
    DEVICE_OBJECT *fdo
    )
{
    DEVICE_OBJECT    *pDevice;
    DEVICE_EXTENSION *pdx;


    pdx = fdo->DeviceExtension;

    DebugPrintf((
        "Removing device (%s)...\n",
        pdx->LinkName
        ));

    // Acquire Device List lock
    spin_lock(
        &(fdo->DriverObject->Lock_DeviceList)
        );

    // Get device list head
    pDevice = fdo->DriverObject->DeviceObject;

    if (pDevice == NULL)
    {
        // Release Device List lock
        spin_unlock(
            &(fdo->DriverObject->Lock_DeviceList)
            );

        ErrorPrintf(("ERROR - Unable to remove device, device list is empty\n"));
        return (-ENODEV);
    }

    if (pDevice == fdo)
    {
        // Remove device from first in list
        fdo->DriverObject->DeviceObject = fdo->NextDevice;
    }
    else
    {
        // Scan list for the device
        while (pDevice->NextDevice != fdo)
        {
            pDevice = pDevice->NextDevice;

            if (pDevice == NULL)
            {
                // Release Device List lock
                spin_unlock(
                    &(fdo->DriverObject->Lock_DeviceList)
                    );

                ErrorPrintf((
                    "ERROR - Device object (%p) not found in device list\n",
                    fdo
                    ));

                return (-ENODEV);
            }
        }

        // Remove device from list
        pDevice->NextDevice = fdo->NextDevice;
    }

    // Decrement device count
    pGbl_DriverObject->DeviceCount--;

    // Release Device List lock
    spin_unlock(
        &(fdo->DriverObject->Lock_DeviceList)
        );

    DebugPrintf((
        "Deleting device object (%p)...\n",
        fdo
        ));

    // Release device object
    kfree(
        fdo
        );

    return 0;
}




/******************************************************************************
 *
 * Function   :  StartDevice
 *
 * Description:  Start a device
 *
 ******************************************************************************/
int
StartDevice(
    DEVICE_OBJECT *fdo
    )
{
    int               rc;
    U8                i;
    U8                ResourceCount;
    DEVICE_EXTENSION *pdx;


    if (fdo->DeviceExtension->Flag_started == TRUE)
        return 0;

    pdx           = fdo->DeviceExtension;
    ResourceCount = 0;

    for (i = 0; i < PCI_NUM_BARS_TYPE_00; ++i)
    {
        // Verify the address is valid
        if (pci_resource_start(
                pdx->pPciDevice,
                i
                ) == 0)
        {
            continue;
        }

        DebugPrintf(("   Resource %02d\n", ResourceCount));

        // Increment resource count
        ResourceCount++;

        // Get PCI physical address
        pdx->PciBar[i].Properties.Physical =
            pci_resource_start(
                pdx->pPciDevice,
                i
                );

        // Determine resource type
        if (pci_resource_flags(
                pdx->pPciDevice,
                i
                ) & IORESOURCE_IO)
        {
            DebugPrintf(("     Type     : I/O Port\n"));

            // Make sure flags are cleared properly
            pdx->PciBar[i].Properties.Physical &= ~(0x3);
            pdx->PciBar[i].Properties.bIoSpace  = TRUE;
        }
        else
        {
            DebugPrintf(("     Type     : Memory Space\n"));

            // Make sure flags are cleared properly
            pdx->PciBar[i].Properties.Physical &= ~(0xf);
            pdx->PciBar[i].Properties.bIoSpace  = FALSE;
        }

        // Get the actual BAR value
        PLX_PCI_REG_READ(
            pdx,
            0x10 + (i * sizeof(U32)),
            &(pdx->PciBar[i].Properties.BarValue)
            );

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

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

        // Get the size
        pdx->PciBar[i].Properties.Size =
            pci_resource_len(
                pdx->pPciDevice,
                i
                );

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

        // Claim and map the resource
        rc =
            PlxPciBarResourceMap(
                pdx,
                i
                );

        if (rc == 0)
        {
            if (pdx->PciBar[i].Properties.bIoSpace == FALSE)
            {
                DebugPrintf((
                    "     Kernel VA: %p\n",
                    pdx->PciBar[i].pVa
                    ));
            }
        }
        else
        {
            if (pdx->PciBar[i].Properties.bIoSpace == FALSE)
            {
                ErrorPrintf((
                    "     Kernel VA: ERROR - Unable to map space to Kernel VA\n"
                    ));
            }
        }
    }

    // Determine which side of NT port we are located on
    if (pdx->PciBar[0].Properties.Physical != 0)
    {
        DebugPrintf(("Driver loaded for Link-side\n"));
        pdx->bFromLinkSide = TRUE;
    }
    else
    {
        DebugPrintf(("Driver loaded for Virtual-side\n"));
    }

    // Map upstream port PCI BAR 0 for register access
    if (PlxMapUpstreamBar(
            pdx
            ) != TRUE)
    {
        DebugPrintf(("ERROR - Unable to map upstream BAR for register access\n"));
        return (-ENOMEM);
    }

    // Implement work-around for NT BAR shadow errata
    PlxErrataWorkAround_NtBarShadow(
        pdx
        );

    // Fill in remaining device key information
    PlxUpdateDeviceKey(
        pdx
        );

    // Disable all interrupts
    PlxChipInterruptsDisable(
        pdx
        );

    // Install the ISR if available
    if (pdx->pPciDevice->irq != 0)
    {
        DebugPrintf(("   Resource %02d\n", ResourceCount));
        DebugPrintf(("     Type     : Interrupt\n"));
        DebugPrintf(("     IRQ      : %02d [0x%02x]\n",
                     pdx->pPciDevice->irq, pdx->pPciDevice->irq));

        // Increment resource count
        ResourceCount++;

        rc =
            request_irq(
                pdx->pPciDevice->irq,    // The device IRQ
                OnInterrupt,             // Interrupt handler
                SA_SHIRQ,                // Flags, support interrupt sharing
                PLX_DRIVER_NAME,         // The driver name
                pdx                      // Parameter to the ISR
                );

        if (rc != 0)
        {
            ErrorPrintf(("ERROR - Unable to install ISR\n"));
        }
        else
        {
            DebugPrintf(("Installed ISR for interrupt\n"));

            // Flag that the interrupt resource was claimed
            pdx->Flag_Interrupt = TRUE;

            // Re-enable interrupts
            PlxChipInterruptsEnable(
                pdx
                );
        }
    }
    else
    {
        DebugPrintf(("Device is not using a PCI interrupt resource\n"));
    }

    // Update device started flag
    pdx->Flag_started = TRUE;

    return 0;
}




/******************************************************************************
 *
 * Function   :  StopDevice
 *
 * Description:  Stop a device
 *
 ******************************************************************************/
VOID
StopDevice(
    DEVICE_OBJECT *fdo
    )
{
    DEVICE_EXTENSION *pdx;


    pdx = fdo->DeviceExtension;

    // Only stop devices which have been started
    if (pdx->Flag_started == FALSE)
    {
        return;
    }

    DebugPrintf(("Releasing device resources...\n"));

    if (pdx->Flag_Interrupt == TRUE)
    {
        // Disable all interrupts
        PlxChipInterruptsDisable(
            pdx
            );

        DebugPrintf((
            "Removing interrupt handler (IRQ = %02d [0x%02x])...\n",
            pdx->pPciDevice->irq, pdx->pPciDevice->irq
            ));

        // Release IRQ
        free_irq(
            pdx->pPciDevice->irq,
            pdx
            );

        // Flag that the interrupt resource was released
        pdx->Flag_Interrupt = FALSE;
    }

    // Unmap I/O regions from kernel space (No local register access after this)
    PlxPciBarResourcesUnmap(
        pdx
        );

    // Unmap the upstream BAR 0 space for NT virtual port
    if ((pdx->pRegVa != NULL) && (pdx->UpstreamBarSize != 0))
    {
        DebugPrintf((
            "Unmapping upstream port PCI BAR 0 (VA=%p)\n",
            pdx->pRegVa
            ));

        iounmap(
            pdx->pRegVa
            );

        pdx->pRegVa = NULL;
        pdx->UpstreamBarSize = 0;
    }

    // Mark that device is not started
    pdx->Flag_started = FALSE;
}




/******************************************************************************
 *
 * Function   :  PlxDeviceListBuild
 *
 * Description:  Scan OS-generated PCI device list to search for supported devices
 *
 ******************************************************************************/
int
PlxDeviceListBuild(
    DRIVER_OBJECT *pDriverObject
    )
{
    int             status;
    int             DeviceCount;
    struct pci_dev *pPciDev;


    DebugPrintf(("Scanning PCI bus for supported devices...\n"));

    // Clear device count
    DeviceCount = 0;

    // Get OS-generated list of PCI devices
    pPciDev =
        pci_find_device(
            PCI_ANY_ID,
            PCI_ANY_ID,
            NULL
            );

    while (pPciDev)
    {
        DebugPrintf((
            "Scanning - %04X %04X  [b:%02x s:%02x f:%02x]\n",
            pPciDev->device, pPciDev->vendor,
            pPciDev->bus->number, PCI_SLOT(pPciDev->devfn),
            PCI_FUNC(pPciDev->devfn)
            ));

        // Check if device is supported
        if (IsSupportedDevice(
                pDriverObject,
                pPciDev
                ))
        {
            DebugPrintf(("SUPPORTED - Adding device to list...\n")); 

            // Add the device to the device list
            status =
                AddDevice(
                    pDriverObject,
                    pPciDev
                    );

            if (status == 0)
            {
                // Increment number of devices found
                DeviceCount++;
            }
        }

        // Jump to next device
        pPciDev =
            pci_find_device(
                PCI_ANY_ID,
                PCI_ANY_ID,
                pPciDev
                );
    }

    DebugPrintf((
        "Device Scan: %d supported device(s) found\n",
        DeviceCount
        ));

    return DeviceCount;
}