/***************************************************************************

Copyright (c) 2000 Microsoft Corporation

Module Name:

        Dot4Usb.sys - Lower Filter Driver for Dot4.sys for USB connected
                        IEEE 1284.4 devices.

File Name:

        Ioctl.c

Abstract:

        Dispatch routines for IRP_MJ_DEVICE_CONTROL and IRP_MJ_INTERNAL_DEVICE_CONTROL

Environment:

        Kernel mode only

Notes:

        THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
        KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
        IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
        PURPOSE.

        Copyright (c) 2000 Microsoft Corporation.  All Rights Reserved.

Revision History:

        01/18/2000 : created

ToDo in this file:

        - code review

Author(s):

        Doug Fritz (DFritz)
        Joby Lafky (JobyL)

****************************************************************************/

#include "pch.h"


/************************************************************************/
/* DispatchDeviceControl                                                */
/************************************************************************/
//
// Routine Description:
//
//     Dispatch routine for IRP_MJ_DEVICE_CONTROL
//       - We don't currently handle any such requests but we may do
//           so in the future. Pass any unhandled requests down the
//           stack to the device below us.
//
// Arguments: 
//
//      DevObj - pointer to DeviceObject that is the target of the request
//      Irp    - pointer to device control IRP
//                                                        
// Return Value:                                          
//                                                        
//      NTSTATUS                                          
//                                                        
/************************************************************************/
NTSTATUS
DispatchDeviceControl(
    IN PDEVICE_OBJECT DevObj,
    IN PIRP           Irp
    )
{
    PDEVICE_EXTENSION  devExt = DevObj->DeviceExtension;
    NTSTATUS           status;
    ULONG              info = 0;

    TR_VERBOSE(("DispatchDeviceControl - enter"));

    status = IoAcquireRemoveLock( &devExt->RemoveLock, Irp );

    if( NT_SUCCESS(status) ) {

        PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation( Irp );

        switch( irpSp->Parameters.DeviceIoControl.IoControlCode ) {
            
        case IOCTL_PAR_QUERY_DEVICE_ID:
            // ISSUE - 000901 - DFritz - these new IOCTLs need to do parameter validation to avoid AVs
            {
                const LONG  minValidIdLength = sizeof("MFG:x;MDL:y;");
                const ULONG bufSize = 1024;
                PCHAR idBuffer = ExAllocatePool( NonPagedPool, bufSize );
                LONG idLength;
                
                if( idBuffer ) {
                    
                    RtlZeroMemory( idBuffer, bufSize );
                    
                    idLength = UsbGet1284Id( DevObj, idBuffer, bufSize-1 );
                    
                    if( idLength < minValidIdLength ) {
                        status = STATUS_UNSUCCESSFUL;
                    } else if( (ULONG)idLength >= irpSp->Parameters.DeviceIoControl.OutputBufferLength ) {
                        status = STATUS_BUFFER_TOO_SMALL;
                    } else {
                        RtlZeroMemory( Irp->AssociatedIrp.SystemBuffer, idLength+1 );
                        RtlCopyMemory( Irp->AssociatedIrp.SystemBuffer, idBuffer+2, idLength-2 );
                        info   = idLength - 1;
                        status = STATUS_SUCCESS;
                    }
                    
                    ExFreePool( idBuffer );
                    
                } else {
                    status = STATUS_NO_MEMORY;
                }
            }

            Irp->IoStatus.Status      = status;
            Irp->IoStatus.Information = info;
            IoCompleteRequest( Irp, IO_NO_INCREMENT );
            IoReleaseRemoveLock( &devExt->RemoveLock, Irp );

            break;
            
        case IOCTL_PAR_QUERY_RAW_DEVICE_ID:
            {
                const LONG  minValidIdLength = sizeof("MFG:x;MDL:y;");
                const ULONG bufSize = 1024;
                PCHAR idBuffer = ExAllocatePool( NonPagedPool, bufSize );
                LONG idLength;
                
                if( idBuffer ) {
                    
                    RtlZeroMemory( idBuffer, bufSize );
                    
                    idLength = UsbGet1284Id( DevObj, idBuffer, bufSize-1 );
                    
                    if( idLength < minValidIdLength ) {
                        status = STATUS_UNSUCCESSFUL;
                    } else if( (ULONG)idLength >= irpSp->Parameters.DeviceIoControl.OutputBufferLength ) {
                        status = STATUS_BUFFER_TOO_SMALL;
                    } else {
                        RtlZeroMemory( Irp->AssociatedIrp.SystemBuffer, idLength+1 );
                        RtlCopyMemory( Irp->AssociatedIrp.SystemBuffer, idBuffer, idLength);
                        info   = idLength + 1;
                        status = STATUS_SUCCESS;
                    }
                    
                    ExFreePool( idBuffer );
                    
                } else {
                    status = STATUS_NO_MEMORY;
                }
            }

            Irp->IoStatus.Status      = status;
            Irp->IoStatus.Information = info;
            IoCompleteRequest( Irp, IO_NO_INCREMENT );
            IoReleaseRemoveLock( &devExt->RemoveLock, Irp );

            break;
            
        case IOCTL_PAR_QUERY_DEVICE_ID_SIZE:

            {
                const LONG  minValidIdLength = sizeof("MFG:x;MDL:y;");
                const ULONG bufSize = 1024;
                PCHAR idBuffer = ExAllocatePool( NonPagedPool, bufSize );
                LONG idLength;
                
                if( idBuffer ) {
                    
                    RtlZeroMemory( idBuffer, bufSize );
                    
                    idLength = UsbGet1284Id( DevObj, idBuffer, bufSize-1 );
                    
                    if( idLength < minValidIdLength ) {
                        status = STATUS_UNSUCCESSFUL;
                    } else if( sizeof(ULONG) < irpSp->Parameters.DeviceIoControl.OutputBufferLength ) {
                        status = STATUS_BUFFER_TOO_SMALL;
                    } else {
                        ++idLength; // save room for terminating NULL
                        RtlCopyMemory( Irp->AssociatedIrp.SystemBuffer, &idLength, sizeof(ULONG));
                        info   = sizeof(ULONG);
                        status = STATUS_SUCCESS;
                    }
                    
                    ExFreePool( idBuffer );
                    
                } else {
                    status = STATUS_NO_MEMORY;
                }
            }

            Irp->IoStatus.Status      = status;
            Irp->IoStatus.Information = info;
            IoCompleteRequest( Irp, IO_NO_INCREMENT );
            IoReleaseRemoveLock( &devExt->RemoveLock, Irp );

            break;

        case IOCTL_PAR_QUERY_LOCATION:

            _snprintf( Irp->AssociatedIrp.SystemBuffer, 4, "USB" );
            info = 4;
            status = STATUS_SUCCESS;
            IoCompleteRequest( Irp, IO_NO_INCREMENT );
            IoReleaseRemoveLock( &devExt->RemoveLock, Irp );

        default:

            // pass request down
            IoSkipCurrentIrpStackLocation( Irp );
            status = IoCallDriver( devExt->LowerDevObj, Irp );
            IoReleaseRemoveLock( &devExt->RemoveLock, Irp );

        }
            
    } else {
        // unable to acquire RemoveLock - FAIL request
        Irp->IoStatus.Status = status;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );
    }

    return status;
}


/************************************************************************/
/* DispatchInternalDeviceControl                                        */
/************************************************************************/
//
// Routine Description:
//
//     Dispatch routine for IRP_MJ_INTERNAL_DEVICE_CONTROL
//       - We expect DataLink requests from dot4.sys driver above us. Any
//           request that we don't handle is simply passed down the stack
//           to the driver below us.       
//
// Arguments: 
//
//      DevObj - pointer to DeviceObject that is the target of the request
//      Irp    - pointer to device control IRP
//                                                        
// Return Value:                                          
//                                                        
//      NTSTATUS                                          
//                                                        
/************************************************************************/
NTSTATUS
DispatchInternalDeviceControl(
    IN PDEVICE_OBJECT DevObj,
    IN PIRP           Irp
    )
{
    NTSTATUS           status;
    PDEVICE_EXTENSION  devExt   = DevObj->DeviceExtension;

    TR_VERBOSE(("DispatchInternalDeviceControl - enter"));

    status = IoAcquireRemoveLock( &devExt->RemoveLock, Irp );

    if( NT_SUCCESS(status) ) {

        PIO_STACK_LOCATION irpSp        = IoGetCurrentIrpStackLocation( Irp );
        BOOLEAN            bCompleteIrp = FALSE;
        KIRQL              oldIrql;

        switch( irpSp->Parameters.DeviceIoControl.IoControlCode ) {
            
        case IOCTL_INTERNAL_PARDOT3_CONNECT:
            
            //
            // Enter a "DataLink Connected" state with dot4.sys
            //

            TR_VERBOSE(("DispatchInternalDeviceControl - IOCTL_INTERNAL_PARDOT3_CONNECT"));

            KeAcquireSpinLock( &devExt->SpinLock, &oldIrql );
            if( !devExt->IsDLConnected ) {
                devExt->IsDLConnected = TRUE;
                status = STATUS_SUCCESS;
            } else {
                // we believe that we are in a "datalink connected state" but obviously 
                //   dot4.sys doesn't agree - suggest investigating further if we hit
                //   this assert
                D4UAssert(FALSE);
                status = STATUS_INVALID_DEVICE_REQUEST;
            }
            KeReleaseSpinLock( &devExt->SpinLock, oldIrql );

            bCompleteIrp = TRUE;
            break;
            

        case IOCTL_INTERNAL_PARDOT3_RESET:
            
            //
            // This IOCTL is specific to parallel and is a NOOP for a USB connection.
            //

            TR_VERBOSE(("DispatchInternalDeviceControl - IOCTL_INTERNAL_PARDOT3_RESET"));

            status = STATUS_SUCCESS;
            bCompleteIrp = TRUE;
            break;
            
        case IOCTL_INTERNAL_PARDOT3_DISCONNECT:
            
            //
            // Terminate the "DataLink Connected" state with dot4.sys and
            //   invalidate any Dot4Event since the event may be freed anytime
            //   after we complete this IRP.
            //

            TR_VERBOSE(("DispatchInternalDeviceControl - IOCTL_INTERNAL_PARDOT3_DISCONNECT"));

            UsbStopReadInterruptPipeLoop( DevObj );

            KeAcquireSpinLock( &devExt->SpinLock, &oldIrql );
            devExt->Dot4Event = NULL; // invalidate dot4's event, if any, so we stop signalling dot4
            if( devExt->IsDLConnected ) {
                devExt->IsDLConnected = FALSE;
            } else {
                // we believe that we are NOT in a "datalink connected state" but obviously 
                //   dot4.sys doesn't agree - suggest investigating further if we hit
                //   this assert
                D4UAssert(FALSE);
            }
            KeReleaseSpinLock( &devExt->SpinLock, oldIrql );

            status = STATUS_SUCCESS; // we always succeed this request since it is a disconnect
            bCompleteIrp = TRUE;
            break;
            
        case IOCTL_INTERNAL_PARDOT3_SIGNAL:
            
            //
            // dot4.sys is giving us a pointer to an Event that it owns and dot4 
            //   expects us to Signal this event whenever we detect that the device has 
            //   data available to be read. We continue signalling this event on device 
            //   data avail until we receive a disconnect IOCTL.
            //

            TR_VERBOSE(("DispatchInternalDeviceControl - IOCTL_INTERNAL_PARDOT3_SIGNAL"));

            KeAcquireSpinLock( &devExt->SpinLock, &oldIrql );
            if( devExt->IsDLConnected ) {
                if( !devExt->Dot4Event ) {
                    // our state indicates that it is OK to receive this request
                    if( irpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(PKEVENT) ) {
                        status = STATUS_INVALID_PARAMETER;                
                    } else {
                        // save the pointer to the event in our device extension
                        PKEVENT Event;
                        RtlCopyMemory(&Event, Irp->AssociatedIrp.SystemBuffer, sizeof(PKEVENT));
                        devExt->Dot4Event = Event;
                        status = STATUS_SUCCESS;
                    }
                } else {
                    // we already have an event and dot4.sys sent us another one? - bad driver - AV crash likely real soon now
                    D4UAssert(FALSE);
                    status = STATUS_INVALID_DEVICE_REQUEST;
                }
            } else {
                // we're not in a datalink connected state - this is an invalid request
                D4UAssert(FALSE);
                status = STATUS_INVALID_DEVICE_REQUEST;
            }
            KeReleaseSpinLock( &devExt->SpinLock, oldIrql );

            if( NT_SUCCESS(status) && devExt->InterruptPipe ) {
                status = UsbStartReadInterruptPipeLoop( DevObj );
            }

            bCompleteIrp = TRUE;
            break;
            
        default :
            
            // unhandled request - pass it down the stack
            IoSkipCurrentIrpStackLocation( Irp );
            status = IoCallDriver( devExt->LowerDevObj, Irp );
            bCompleteIrp = FALSE;
            
        }
        
        if( bCompleteIrp ) {
            // we didn't pass this request down the stack, so complete it now
            Irp->IoStatus.Status      = status;
            Irp->IoStatus.Information = 0;
            IoCompleteRequest( Irp, IO_NO_INCREMENT );
        }

        IoReleaseRemoveLock( &devExt->RemoveLock, Irp );

    } else {
        // unable to acquire RemoveLock - we're in the process of being removed - FAIL request
        Irp->IoStatus.Status = status;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );
    }

    return status;
}