/*******************************************************************************
 * 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:
 *
 *      Driver.c
 *
 * Description:
 *
 *      Initializes the driver and claims system resources for the device
 *
 * Revision History:
 *
 *      09-01-10 : PLX SDK v6.40
 *
 *****************************************************************************/


#include <stdio.h>
#include "ApiFunc.h"
#include "Dispatch.h"
#include "Driver.h"
#include "GlobalVars.h"
#include "PciFunc.h"
#include "SuppFunc.h"


#if defined(ALLOC_PRAGMA) && !defined(PLX_DEBUG)
    #pragma alloc_text(INIT, DriverEntry)
#endif




/*******************************************************************************
 *
 * Function   :  DriverEntry
 *
 * Description:  Entry point for the driver
 *
 ******************************************************************************/
NTSTATUS
DriverEntry(
    DRIVER_OBJECT  *pDriverObject,
    UNICODE_STRING *pRegistryPath
    )
{
    U8                DeviceCount;
    NTSTATUS          status;
    PLX_PHYSICAL_MEM  PciMem;
    PLX_REGISTRY_INFO RegistryInfo;


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

    // Fill in the appropriate dispatch handlers
    pDriverObject->DriverUnload                         = DriverUnload;
    pDriverObject->MajorFunction[IRP_MJ_CREATE]         = Dispatch_Create;
    pDriverObject->MajorFunction[IRP_MJ_CLOSE]          = Dispatch_Close;
    pDriverObject->MajorFunction[IRP_MJ_CLEANUP]        = Dispatch_Cleanup;
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Dispatch_IoControl;

    DebugPrintf((
        "Registry path = \"%ws\"\n",
        pRegistryPath->Buffer
        ));

    // Get configuration information from registry
    PlxRegistryInformationGet(
        pRegistryPath,
        &RegistryInfo
        );

    // Add a single device object
    status =
        AddDevice(
            pDriverObject,
            NULL
            );

    if ( !NT_SUCCESS(status) )
    {
        DriverUnload( pDriverObject );
        return status;
    }

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

    // Scan the PCI bus to build list of devices
    DeviceCount =
        PlxDeviceListBuild(
            pDriverObject->DeviceObject->DeviceExtension
            );

    // Check if any devices were found
    if (DeviceCount == 0)
    {
        ErrorPrintf(("ERROR - No PCI devices found\n"));
        DriverUnload( pDriverObject );
        return STATUS_SUCCESS;
    }

    if (RegistryInfo.Size_CommonBuffer != 0)
    {
       DebugPrintf(("Allocate common buffer...\n"));

       // Set requested size
       PciMem.Size = RegistryInfo.Size_CommonBuffer;

       // Allocate common buffer
       PlxPciPhysicalMemoryAllocate(
           NULL,                    // No device associated with buffer
           &PciMem,
           TRUE                     // Smaller buffer is ok
           );
    }

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

    return STATUS_SUCCESS;
}




/*******************************************************************************
 *
 * Function   :  DriverUnload
 *
 * Description:  Unload the driver
 *
 ******************************************************************************/
VOID
DriverUnload(
    DRIVER_OBJECT *pDriverObject
    )
{
    DEVICE_OBJECT    *fdo;
    DEVICE_OBJECT    *pNext;
    PLX_PHYSICAL_MEM  PciMem;


    DebugPrintf_Cont(("\n"));
    DebugPrintf(("Unload driver...\n"));

    // Release the common buffer
    if (pGbl_CommonBuffer != NULL)
    {
        DebugPrintf(("De-allocate Common Buffer...\n"));

        // Prepare buffer properties for parameter
        PciMem.PhysicalAddr = pGbl_CommonBuffer->BusPhysical;
        PciMem.Size         = pGbl_CommonBuffer->Size;

        // Release the buffer
        PlxPciPhysicalMemoryFree(
            NULL,
            &PciMem
            );

        // Mark buffer as released
        pGbl_CommonBuffer = NULL;
    }

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

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

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

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

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




/*******************************************************************************
 *
 * Function   :  AddDevice
 *
 * Description:  Add a new device object to the driver
 *
 ******************************************************************************/
NTSTATUS
AddDevice(
    DRIVER_OBJECT *pDriverObject,
    DEVICE_OBJECT *fdo
    )
{
    WCHAR             DeviceName[PLX_MAX_NAME_LENGTH];
    WCHAR             LinkName[PLX_MAX_NAME_LENGTH];
    NTSTATUS          status;
    UNICODE_STRING    DeviceName_Unicode;
    UNICODE_STRING    DeviceLinkName_Unicode;
    DEVICE_EXTENSION *pdx;


    // Build a device name and attempt to create it
    swprintf(
        DeviceName,
        L"\\Device\\" PLX_DRIVER_NAME_UNICODE L"_v%d%02d",
        PLX_SDK_VERSION_MAJOR,
        PLX_SDK_VERSION_MINOR
        );

    RtlInitUnicodeString(
        &DeviceName_Unicode,
        DeviceName
        );

    DebugPrintf((
        "Create Device Object (%ws)\n",
        DeviceName
        ));

    // Create the device object
    status =
        IoCreateDevice(
            pDriverObject,              // Driver object
            sizeof(DEVICE_EXTENSION),   // Device extension size
            &DeviceName_Unicode,        // Device name
            FILE_DEVICE_UNKNOWN,        // Device type
            FILE_DEVICE_SECURE_OPEN,    // Device characteristics
            FALSE,                      // Exclusive? (must be FALSE)
            &fdo                        // Returned device object
            );

    if ( !NT_SUCCESS(status) )
    {
        DebugPrintf(("ERROR - Unable to create Device\n"));
        return status;
    }

    // Link a Win32 name for user applications
    swprintf(
        LinkName,
        L"\\DosDevices\\" PLX_DRIVER_NAME_UNICODE L"_v%d%02d",
        PLX_SDK_VERSION_MAJOR,
        PLX_SDK_VERSION_MINOR
        );

    RtlInitUnicodeString(
        &DeviceLinkName_Unicode,
        LinkName
        );

    DebugPrintf((
        "Create Win32 symbolic link (%ws)\n",
        LinkName
        ));

    status =
        IoCreateSymbolicLink(
            &DeviceLinkName_Unicode,
            &DeviceName_Unicode
            );

    if ( !NT_SUCCESS(status) )
    {
        DebugPrintf(("WARNING - Unable to create symbolic link for Win32 Apps\n"));
        swprintf(LinkName, L"");
    }

    //
    // Initialize the device extension
    //

    pdx = fdo->DeviceExtension;

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

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

    wcscpy( pdx->DriverName, DeviceName );
    wcscpy( pdx->LinkName, LinkName );

    InitializeListHead(
        &(pdx->List_Devices)
        );

    // Indicate the I/O Manager buffer management method
    fdo->Flags |= DO_BUFFERED_IO;

    // Manually clear the Device Initialzing flag
    fdo->Flags &= ~DO_DEVICE_INITIALIZING;

    return STATUS_SUCCESS;
}




/*******************************************************************************
 *
 * Function   :  RemoveDevice
 *
 * Description:  Remove a device object from device list and delete it
 *
 ******************************************************************************/
VOID
RemoveDevice(
    DEVICE_OBJECT *fdo
    )
{
    NTSTATUS          status;
    LIST_ENTRY       *pEntry;
    UNICODE_STRING    DeviceLinkName_Unicode;
    PLX_DEVICE_NODE  *pNode;
    DEVICE_EXTENSION *pdx;


    pdx = fdo->DeviceExtension;

    DebugPrintf(("Delete supported devices list\n"));

    // Free device list
    while (!IsListEmpty(
                &(pdx->List_Devices)
                ))
    {
        // Remove entry from head of list
        pEntry =
            RemoveHeadList(
                &(pdx->List_Devices)
                );

        // Get the device node
        pNode =
            CONTAINING_RECORD(
                pEntry,
                PLX_DEVICE_NODE,
                ListEntry
                );

        // Release the object
        ExFreePool(
            pNode
            );
    }

    // Remove Win32 link name
    if (wcslen(pdx->LinkName) != 0)
    {
        DebugPrintf((
            "Remove Win32 link (%ws)\n",
            pdx->LinkName
            ));

        RtlInitUnicodeString(
            &DeviceLinkName_Unicode,
            pdx->LinkName
            );

        status =
            IoDeleteSymbolicLink(
                &DeviceLinkName_Unicode
                );

        if ( !NT_SUCCESS(status) )
            DebugPrintf(("WARNING - Unable to remove Win32 link\n"));
    }

    DebugPrintf((
        "Delete device object (%ws)\n",
        pdx->DriverName
        ));

    // Delete the device object
    IoDeleteDevice(
        fdo
        );
}
