/*++

Copyright (c) 1998 Microsoft Corporation

Module Name:

    serscan.c

Abstract:

    This module contains the code for a serial imaging devices
    suport class driver.

Author:

    Vlad Sadovsky    vlads              10-April-1998

Environment:

    Kernel mode

Revision History :

    vlads           04/10/1998      Created first draft

--*/

#include "serscan.h"
#include "serlog.h"

#include <initguid.h>

#include <devguid.h>
#include <wiaintfc.h>

#if DBG
ULONG SerScanDebugLevel = -1;
#endif

const PHYSICAL_ADDRESS PhysicalZero = {0};

//
// Keep track of the number of Serial port devices created...
//
ULONG g_NumPorts = 0;

//
// Definition of OpenCloseMutex.
//
extern ULONG OpenCloseReferenceCount = 1;
extern PFAST_MUTEX OpenCloseMutex = NULL;

//
//
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, SerScanAddDevice)
#endif


NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )

/*++

Routine Description:

    This routine is called at system initialization time to initialize
    this driver.

Arguments:

    DriverObject    - Supplies the driver object.

    RegistryPath    - Supplies the registry path for this driver.

Return Value:

    STATUS_SUCCESS          - We could initialize at least one device.
    STATUS_NO_SUCH_DEVICE   - We could not in itialize even one device.

--*/

{

    int     i;

    PAGED_CODE();

    #if DBG
    DebugDump(SERINITDEV,("Entering DriverEntry\n"));
    #endif

    //
    // Initialize the Driver Object with driver's entry points
    //
    DriverObject->DriverExtension->AddDevice              = SerScanAddDevice;

    DriverObject->DriverUnload = SerScanUnload;

    #ifdef DEAD_CODE
    for (i=0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) {

        DriverObject->MajorFunction[i]= SerScanPassThrough;
    }
    #endif

    DriverObject->MajorFunction[IRP_MJ_CREATE]            = SerScanCreateOpen;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]             = SerScanClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]    = SerScanDeviceControl;
    DriverObject->MajorFunction[IRP_MJ_PNP]               = SerScanPnp;
    DriverObject->MajorFunction[IRP_MJ_POWER]             = SerScanPower;

    //
    // Following are possibly not needed, keep them here to allow
    // easier tracing in when debugging. All of them resort to pass-through
    // behaviour
    //
    #ifdef DEAD_CODE

    DriverObject->MajorFunction[IRP_MJ_CLEANUP]           = SerScanCleanup;
    DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = SerScanQueryInformationFile;
    DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION]   = SerScanSetInformationFile;

    #endif

    DriverObject->MajorFunction[IRP_MJ_READ]              = SerScanPassThrough;
    DriverObject->MajorFunction[IRP_MJ_WRITE]             = SerScanPassThrough;

    return STATUS_SUCCESS;

}


NTSTATUS
SerScanAddDevice(
    IN PDRIVER_OBJECT pDriverObject,
    IN PDEVICE_OBJECT pPhysicalDeviceObject
    )
/*++

Routine Description:

    This routine is called to create a new instance of the device.
    It creates FDO and attaches it to PDO

Arguments:

    pDriverObject           - pointer to the driver object for this instance of port.

    pPhysicalDeviceObject   - pointer to the device object that represents the port.

Return Value:

    STATUS_SUCCESS          - if successful.
    STATUS_UNSUCCESSFUL     - otherwise.

--*/
{
    UNICODE_STRING      ClassName;
    UNICODE_STRING      LinkName;
    NTSTATUS            Status;
    PDEVICE_EXTENSION   Extension;
    PDEVICE_OBJECT      pDeviceObject;

    PAGED_CODE();


    DebugDump(SERINITDEV,("Entering AddDevice\n"));

    //
    // Get the Class and Link names.
    //

    if (!SerScanMakeNames (g_NumPorts, &ClassName, &LinkName)) {

        SerScanLogError(pDriverObject,
                        NULL,
                        PhysicalZero,
                        PhysicalZero,
                        0,
                        0,
                        0,
                        1,
                        STATUS_SUCCESS,
                        SER_INSUFFICIENT_RESOURCES);

        DebugDump(SERERRORS,("SerScan: Could not form Unicode name strings.\n"));

        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Create the device object for this device.
    //

    Status = IoCreateDevice(pDriverObject,
                            sizeof(DEVICE_EXTENSION),
                            &ClassName,
                            FILE_DEVICE_SCANNER,
                            0,
                            TRUE,
                            &pDeviceObject);


    if (!NT_SUCCESS(Status)) {

        ExFreePool(ClassName.Buffer);
        ExFreePool(LinkName.Buffer);

        SerScanLogError(pDriverObject,
                        NULL,
                        PhysicalZero,
                        PhysicalZero,
                        0,
                        0,
                        0,
                        9,
                        STATUS_SUCCESS,
                        SER_INSUFFICIENT_RESOURCES);

        DebugDump(SERERRORS, ("SERPORT:  Could not create a device for %d\n", g_NumPorts));

        return Status;
    }

    //
    // The device object has a pointer to an area of non-paged
    // pool allocated for this device.  This will be the device
    // extension.
    //

    Extension = pDeviceObject->DeviceExtension;

    //
    // Zero all of the memory associated with the device
    // extension.
    //

    RtlZeroMemory(Extension, sizeof(DEVICE_EXTENSION));

    //
    // Get a "back pointer" to the device object.
    //

    Extension->DeviceObject = pDeviceObject;

    Extension->Pdo = pPhysicalDeviceObject;

    Extension->AttachedDeviceObject = NULL;
    Extension->AttachedFileObject = NULL;

    //
    // Setup buffered I/O
    //
    pDeviceObject->Flags |= DO_BUFFERED_IO;

    //
    // Indicate our power code is pageable
    //
    pDeviceObject->Flags |= DO_POWER_PAGABLE;

    //
    // Attach our new Device to our parents stack.
    //
    Extension->LowerDevice = IoAttachDeviceToDeviceStack(
                                  pDeviceObject,
                                  pPhysicalDeviceObject);

    if (NULL == Extension->LowerDevice) {

        ExFreePool(ClassName.Buffer);
        ExFreePool(LinkName.Buffer);

        IoDeleteDevice(pDeviceObject);

        return STATUS_UNSUCCESSFUL;
    }

    Extension->ClassName        = ClassName;
    Extension->SymbolicLinkName = LinkName;

    Status = SerScanHandleSymbolicLink(
        pPhysicalDeviceObject,
        &Extension->InterfaceNameString,
        TRUE
        );

    //
    // We have created the device, so increment the counter
    // that keeps track.
    //
    g_NumPorts++;

    //
    // Initiliaze the rest of device extension
    //
    Extension->ReferenceCount = 1;

    Extension->Removing = FALSE;

    Extension->OpenCount = 0;

    KeInitializeEvent(&Extension->RemoveEvent,
                      NotificationEvent,
                      FALSE
                      );

    // ExInitializeResourceLite(&Extension->Resource);
    ExInitializeFastMutex(&Extension->Mutex);

    //
    // Clear InInit flag to indicate device object can be used
    //
    pDeviceObject->Flags &= ~(DO_DEVICE_INITIALIZING);


    return STATUS_SUCCESS;

}

BOOLEAN
SerScanMakeNames(
    IN  ULONG           SerialPortNumber,
    OUT PUNICODE_STRING ClassName,
    OUT PUNICODE_STRING LinkName
    )

/*++

Routine Description:

    This routine generates the names \Device\SerScanN.

    This routine will allocate pool so that the buffers of
    these unicode strings need to be eventually freed.

Arguments:

    SerialPortNumber  - Supplies the serial port number.

    ClassName           - Returns the class name.

    LinkName            - Returns the link name.

Return Value:

    FALSE   - Failure.
    TRUE    - Success.

--*/
{
    UNICODE_STRING  Prefix;
    UNICODE_STRING  Digits;
    UNICODE_STRING  LinkPrefix;
    UNICODE_STRING  LinkDigits;
    WCHAR           DigitsBuffer[10];
    WCHAR           LinkDigitsBuffer[10];
    UNICODE_STRING  ClassSuffix;
    UNICODE_STRING  LinkSuffix;
    NTSTATUS        Status;

    //
    // Put together local variables for constructing names.
    //

    RtlInitUnicodeString(&Prefix, L"\\Device\\");
    RtlInitUnicodeString(&LinkPrefix, L"\\DosDevices\\");

    //
    // WORKWORK: Change the name to be device specific.
    //
    RtlInitUnicodeString(&ClassSuffix, SERSCAN_NT_SUFFIX);
    RtlInitUnicodeString(&LinkSuffix, SERSCAN_LINK_NAME);

    Digits.Length        = 0;
    Digits.MaximumLength = 20;
    Digits.Buffer        = DigitsBuffer;

    LinkDigits.Length        = 0;
    LinkDigits.MaximumLength = 20;
    LinkDigits.Buffer        = LinkDigitsBuffer;

    Status = RtlIntegerToUnicodeString(SerialPortNumber, 10, &Digits);
    if (!NT_SUCCESS(Status)) {
        return FALSE;
    }

    Status = RtlIntegerToUnicodeString(SerialPortNumber + 1, 10, &LinkDigits);
    if (!NT_SUCCESS(Status)) {
        return FALSE;
    }

    //
    // Make the class name.
    //

    ClassName->Length = 0;
    ClassName->MaximumLength = Prefix.Length + ClassSuffix.Length +
                               Digits.Length + sizeof(WCHAR);

    ClassName->Buffer = ExAllocatePool(PagedPool, ClassName->MaximumLength);
    if (!ClassName->Buffer) {
        return FALSE;
    }

    RtlZeroMemory(ClassName->Buffer, ClassName->MaximumLength);
    RtlAppendUnicodeStringToString(ClassName, &Prefix);
    RtlAppendUnicodeStringToString(ClassName, &ClassSuffix);
    RtlAppendUnicodeStringToString(ClassName, &Digits);

    //
    // Make the link name.
    //

    LinkName->Length = 0;
    LinkName->MaximumLength = LinkPrefix.Length + LinkSuffix.Length +
                              LinkDigits.Length + sizeof(WCHAR);

    LinkName->Buffer = ExAllocatePool(PagedPool, LinkName->MaximumLength);
    if (!LinkName->Buffer) {
        ExFreePool(ClassName->Buffer);
        return FALSE;
    }

    RtlZeroMemory(LinkName->Buffer, LinkName->MaximumLength);
    RtlAppendUnicodeStringToString(LinkName, &LinkPrefix);
    RtlAppendUnicodeStringToString(LinkName, &LinkSuffix);
    RtlAppendUnicodeStringToString(LinkName, &LinkDigits);

    return TRUE;
}


NTSTATUS
SerScanCleanup(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )

/*++

Routine Description:

    This routine is the dispatch for a cleanup requests.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the I/O request packet.

Return Value:

    STATUS_SUCCESS  - Success.

--*/

{
    NTSTATUS            Status;
    PDEVICE_EXTENSION   Extension;

    Extension = DeviceObject->DeviceExtension;

    //
    // Call down to the parent and wait on the Cleanup IRP to complete...
    //
    Status = SerScanCallParent(Extension,
                               Irp,
                               WAIT,
                               NULL);

    DebugDump(SERIRPPATH,
              ("SerScan: [Cleanup] After CallParent Status = %x\n",
              Status));

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return Status;
}

VOID
SerScanCancelRequest(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )

/*++

Routine Description:

    This routine is used to cancel any request in the Serial driver.

Arguments:

    DeviceObject - Pointer to the device object for this device

    Irp - Pointer to the IRP to be canceled.

Return Value:

    None.

--*/

{
    NTSTATUS            Status;
    PDEVICE_EXTENSION   Extension;

    Extension = DeviceObject->DeviceExtension;

    //
    // Call down to the parent and wait on the Cleanup IRP to complete...
    //
    Status = SerScanCallParent(Extension,
                               Irp,
                               WAIT,
                               NULL);

    DebugDump(SERIRPPATH,
              ("SerScan: [Cleanup] After CallParent Status = %x\n",
              Status));

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return;
}


NTSTATUS
SerScanQueryInformationFile(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )

/*++

Routine Description:

    This routine is used to query the end of file information on
    the opened Serial port.  Any other file information request
    is retured with an invalid parameter.

    This routine always returns an end of file of 0.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the I/O request packet.

Return Value:

    STATUS_SUCCESS              - Success.
    STATUS_INVALID_PARAMETER    - Invalid file information request.
    STATUS_BUFFER_TOO_SMALL     - Buffer too small.

--*/

{
    NTSTATUS            Status;
    PDEVICE_EXTENSION   Extension;

    Extension = DeviceObject->DeviceExtension;

    Status = SerScanCallParent(Extension,
                               Irp,
                               WAIT,
                               NULL);

    DebugDump(SERIRPPATH,
              ("SerScan: [Cleanup] After CallParent Status = %x\n",
              Status));

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return Status;
}


NTSTATUS
SerScanSetInformationFile(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )

/*++

Routine Description:

    This routine is used to set the end of file information on
    the opened Serial port.  Any other file information request
    is retured with an invalid parameter.

    This routine always ignores the actual end of file since
    the query information code always returns an end of file of 0.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the I/O request packet.

Return Value:

    STATUS_SUCCESS              - Success.
    STATUS_INVALID_PARAMETER    - Invalid file information request.

--*/

{
    NTSTATUS            Status;
    PDEVICE_EXTENSION   Extension;

    Extension = DeviceObject->DeviceExtension;

    Status = SerScanCallParent(Extension,
                               Irp,
                               WAIT,
                               NULL);

    DebugDump(SERIRPPATH,
              ("SerScan: [Cleanup] After CallParent Status = %x\n",
              Status));

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return Status;
}

VOID
SerScanUnload(
    IN  PDRIVER_OBJECT  DriverObject
    )

/*++

Routine Description:

    This routine loops through the device list and cleans up after
    each of the devices.

Arguments:

    DriverObject    - Supplies the driver object.

Return Value:

    None.

--*/

{
    PDEVICE_OBJECT      CurrentDevice;
    PDEVICE_OBJECT      NextDevice;
    PDEVICE_EXTENSION   Extension;

    DebugDump(SERUNLOAD,
              ("SerScan: In SerUnload\n"));

    CurrentDevice = DriverObject->DeviceObject;
    while (NULL != CurrentDevice){

        Extension = CurrentDevice->DeviceExtension;


        if(NULL != Extension->SymbolicLinkName.Buffer){
            if (Extension->CreatedSymbolicLink) {
                IoDeleteSymbolicLink(&Extension->SymbolicLinkName);

                RtlDeleteRegistryValue(RTL_REGISTRY_DEVICEMAP,
                                       L"Serial Scanners",
                                       Extension->SymbolicLinkName.Buffer);
            } // if (Extension->CreatedSymbolicLink) 

            ExFreePool(Extension->SymbolicLinkName.Buffer);
            Extension->SymbolicLinkName.Buffer = NULL;
        } // if(NULL != Extension->SymbolicLinkName.Buffer)

        if(NULL != Extension->ClassName.Buffer){
            ExFreePool(Extension->ClassName.Buffer);
            Extension->ClassName.Buffer = NULL;
        } // if(NULL != Extension->ClassName.Buffer)

        NextDevice = CurrentDevice->NextDevice;
        IoDeleteDevice(CurrentDevice);

        CurrentDevice = NextDevice;
    } // while (CurrentDevice = DriverObject->DeviceObject)

}

NTSTATUS
SerScanHandleSymbolicLink(
    PDEVICE_OBJECT      DeviceObject,
    PUNICODE_STRING     InterfaceName,
    BOOLEAN             Create
    )
/*++

Routine Description:

Arguments:

    DriverObject    - Supplies the driver object.

Return Value:

    None.

--*/
{

    NTSTATUS           Status;

    Status = STATUS_SUCCESS;

    if (Create) {

        Status=IoRegisterDeviceInterface(
            DeviceObject,
            &GUID_DEVINTERFACE_IMAGE,
            NULL,
            InterfaceName
            );

        DebugDump(SERINITDEV,("Called IoRegisterDeviceInterface . Returned=0x%X\n",Status));


        if (NT_SUCCESS(Status)) {

            IoSetDeviceInterfaceState(
                InterfaceName,
                TRUE
                );

            DebugDump(SERINITDEV,("Called IoSetDeviceInterfaceState(TRUE) . \n"));

        }

    } else {

        if (InterfaceName->Buffer != NULL) {

            IoSetDeviceInterfaceState(
                InterfaceName,
                FALSE
                );

            DebugDump(SERINITDEV,("Called IoSetDeviceInterfaceState(FALSE) . \n"));

            RtlFreeUnicodeString(
                InterfaceName
                );

            InterfaceName->Buffer = NULL;
        }

    }

    return Status;

}