///////////////////////////////////////////////////////////////////////////////
//
//    (C) Copyright 1995 - 2005 OSR Open Systems Resources, Inc.
//    All Rights Reserved
//
//    This sofware is supplied for instructional purposes only.
//
//    OSR Open Systems Resources, Inc. (OSR) expressly disclaims any warranty
//    for this software.  THIS SOFTWARE IS PROVIDED  "AS IS" WITHOUT WARRANTY
//    OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
//    THE IMPLIED WARRANTIES OF MECHANTABILITY OR FITNESS FOR A PARTICULAR
//    PURPOSE.  THE ENTIRE RISK ARISING FROM THE USE OF THIS SOFTWARE REMAINS
//    WITH YOU.  OSR's entire liability and your exclusive remedy shall not
//    exceed the price paid for this material.  In no event shall OSR or its
//    suppliers be liable for any damages whatsoever (including, without
//    limitation, damages for loss of business profit, business interruption,
//    loss of business information, or any other pecuniary loss) arising out
//    of the use or inability to use this software, even if OSR has been
//    advised of the possibility of such damages.  Because some states/
//    jurisdictions do not allow the exclusion or limitation of liability for
//    consequential or incidental damages, the above limitation may not apply
//    to you.
//
//    OSR Open Systems Resources, Inc.
//    105 Route 101A Suite 19
//    Amherst, NH 03031  (603) 595-6500 FAX: (603) 595-6503
//    email bugs to: bugs@osr.com
//
//
//    MODULE:
//
//        RedirectPnP.C -- Redirect driver V2 pnp functions
//
//    ABSTRACT:
//
//      This file contains the skeleton status for a WDM/Win2K device driver
//      that does absolutely Redirect.
//
//    AUTHOR(S):
//
//        OSR Open Systems Resources, Inc.
// 
//    REVISION:   
//
//        This is a WDM compatible version of the original NT V4
//        REDIRECT driver.
//
///////////////////////////////////////////////////////////////////////////////

#include "redirect.h"
#include <initguid.h>


DEFINE_GUID(REDIRECTID_PRIVATE,0x0c833dae, 0x8679, 0x11d3, 0xb1, 0x9b, 0x00, 0x60, 0xb0, 0xef, 0xd4, 0xaa);

NTSTATUS OsrPnpComplete(IN PDEVICE_OBJECT   DeviceObject, 
                        IN PIRP             Irp, 
                        IN PVOID            Context);

LONG   InstanceNumber = 0;

///////////////////////////////////////////////////////////////////////////////
//
//	RedirectAddDevice
//
//      We're called here by the PnP Manager when a device instance
//      needs to be created
//
//	INPUTS:
//
//		DriverObj - Address of our DRIVER_OBJECT.
//      PhysicalDeviceObject -- Pointer to underlying PDO
//
//	OUTPUTS:
//
//		None.
//
//	RETURNS:
//
//      NTSTATUS -- result of our attempt to create this instance
//
//  IRQL:
//
//    This routine is called at IRQL_PASSIVE_LEVEL.
//
//	NOTES:
//
//    No hardware access in this routine!!!
//
///////////////////////////////////////////////////////////////////////////////
NTSTATUS RedirectAddDevice(IN PDRIVER_OBJECT  DriverObject,
                           IN PDEVICE_OBJECT  PhysicalDeviceObject)
{
    PENCRYPT_DEVICE_EXT devExt;
    PDEVICE_OBJECT	deviceObject;
    PWCHAR          pDevNameBuffer;
    UNICODE_STRING	devName;
    UNICODE_STRING  linkName;
	NTSTATUS		status = STATUS_SUCCESS;		

#if DBG
    DbgPrint(DRIVER_NAME "AddDevice: entered\n");
#endif

    pDevNameBuffer = (PWCHAR) ExAllocatePoolWithTag(PagedPool,256*sizeof(WCHAR),'eman');

    if(!pDevNameBuffer) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Initialize the UNICODE device name.  This will be the "native NT" name
    // for our device.
    //
    status = RtlStringCbPrintfW(pDevNameBuffer,256*sizeof(WCHAR),
                                L"\\Device\\Encrypt%d",InterlockedIncrement(&InstanceNumber));

    //
    // Make sure that the creation worked.  If not, get out of here.
    //
    if(!NT_SUCCESS(status)) {
        ExFreePool(pDevNameBuffer);
        return status;
    }
    
    //
    // Initialize the Unicode Name structure to be used for the Device Name.
    //
    devName.Length = wcslen(pDevNameBuffer) * sizeof(WCHAR);
    devName.MaximumLength = 256*sizeof(WCHAR);
    devName.Buffer = pDevNameBuffer;

    //
    // Ask the I/O Manager to create the device object and
    // device extension
    //
    status = IoCreateDevice(DriverObject,
                    sizeof(ENCRYPT_DEVICE_EXT),
                    &devName,
                    FILE_DEVICE_REDIRECT,
                    FILE_DEVICE_SECURE_OPEN,       
                    FALSE,
                    &deviceObject);


    if(!NT_SUCCESS(status))  {

#if DBG
        DbgPrint(DRIVER_NAME "IoCreateDevice failed.  Status = 0x%0x\n", status);
#endif
        return(STATUS_UNSUCCESSFUL);
    }    

    //
    // Tell the I/O Manger to buffer our reads/writes
    //
    deviceObject->Flags |= DO_BUFFERED_IO;


    //
    // Get a pointer to our device extension
    //
    devExt = (PENCRYPT_DEVICE_EXT)deviceObject->DeviceExtension;

    //
    // Save the device object pointer away for future reference
    //
    devExt->DeviceObject = deviceObject;

	//
	// Save the address of the physical device object away for future reference
	//
	devExt->Pdo = PhysicalDeviceObject;

    //
    // Save assigned Device Name for registring with controller.
    //
    RtlCopyMemory(&devExt->DeviceName,&devName,sizeof(UNICODE_STRING));
    
	//
	// Attach the device object to the device stack
	//
	devExt->DeviceToSendIrpsTo = IoAttachDeviceToDeviceStack(
									devExt->DeviceObject, 
									devExt->Pdo
									);
    
    if(devExt->DeviceToSendIrpsTo == NULL)  {

#if DBG
        DbgPrint(DRIVER_NAME 
                 "IoAttachDeviceToDeviceStack failed to attach to Target Device");
#endif
        //
        // The attempt to attach our device FAILED!
        // (this would be a VERY strange error to get)
        //
        ExFreePool(pDevNameBuffer);

        //
        // Return our device object
        //
        IoDeleteDevice(deviceObject);

        //
        // Indicate load failure to the I/O manager; driver image is deleted...
        //
        return(STATUS_UNSUCCESSFUL);
    }

    //
    // Try to allocate a Driver Extension.
    //
    status = IoAllocateDriverObjectExtension(DriverObject,(PVOID) REDIRECT_EXTENSION_IDENTIFICATION_ADDRESS,
                             sizeof(REDIRECT_DRIVER_OBJECT_EXT),(PVOID*) &devExt->Common.DriverObjectExt);

    //
    // Check the return status.  If it is STATUS_OBJECT_NAME_COLLISION, then we know it has already been
    // allocated by us.
    //
    if(status == STATUS_OBJECT_NAME_COLLISION) {
        //
        // The extension already exists, attempt to retrieve it.
        //
        devExt->Common.DriverObjectExt = (PREDIRECT_DRIVER_OBJECT_EXT) IoGetDriverObjectExtension(DriverObject,
                                      (PVOID) REDIRECT_EXTENSION_IDENTIFICATION_ADDRESS);

        //
        // Make sure we have it.
        //
        if(!devExt->Common.DriverObjectExt) {
            //
            // An error occurred, we were not able to retrieve the address.
            // Clean up the mess
            //

            //
            // Delete our device object
            //
            IoDeleteDevice(deviceObject);

            return STATUS_UNSUCCESSFUL;
        }

        status = STATUS_SUCCESS;

    } else if(NT_SUCCESS(status)) {

        PREDIRECT_CTL_EXT   ctlExt = NULL;
        PDEVICE_OBJECT      ctlDeviceObject = NULL;
        UNICODE_STRING      ctlName;

        RtlInitUnicodeString(&ctlName,L"\\Device\\RedirectControl");

        //
        // If we are here then we successfully created the extension and there can initialize it.
        //
        RtlZeroMemory(devExt->Common.DriverObjectExt,sizeof(REDIRECT_DRIVER_OBJECT_EXT));

        devExt->Common.DriverObjectExt->MagicNumber = REDIRECT_DRIVER_OBJECT_EXT_MAGIC_NUMBER;

        status = IoCreateDevice(DriverObject,
                        sizeof(REDIRECT_CTL_EXT),
                        &ctlName,
                        FILE_DEVICE_REDIRECT,
                        FILE_DEVICE_SECURE_OPEN,       
                        FALSE,
                        &ctlDeviceObject);

        if(!NT_SUCCESS(status)) {
            //
            // An error occurred, we were not able to retrieve the address.
            // Clean up the mess
            //
            ExFreePool(pDevNameBuffer);

            //
            // Delete our device object
            //
            IoDeleteDevice(deviceObject);

            return status;
        }

        devExt->Common.DriverObjectExt->RedirectorDeviceObject = ctlDeviceObject;

        ctlExt = (PREDIRECT_CTL_EXT) ctlDeviceObject->DeviceExtension;

        RtlZeroMemory(ctlExt,sizeof(REDIRECT_CTL_EXT));

        ctlExt->Common.DriverObjectExt = devExt->Common.DriverObjectExt;
        ctlExt->Common.Redirector = TRUE;
        ExInitializeFastMutex(&ctlExt->DeviceNameListLock);
        InitializeListHead(&ctlExt->DeviceNameList);

        //
        // Next, make the device accessible from user-mode applications.
        // Note that this name can be either the same or different from
        // the native "kernel mode" name of the device object, given above.
        //
        // Note that we either additionally, or alternatively, export a
        // device interface (by calling IoRegisterDeviceInterface()).
        // But, accessing this specific device by name is what we expect
        // to do, so we'll bypass that.
        //
        RtlInitUnicodeString(&linkName, L"\\DosDevices\\Redirect");

        status = IoCreateSymbolicLink(&linkName, &ctlName);

        if (!NT_SUCCESS(status))
        {
#if DBG
            DbgPrint("%wZ IoCreateSymbolicLink failed.  Status = 0x%x\n",&linkName, status);
#endif
            IoDeleteDevice(ctlDeviceObject);

            ExFreePool(pDevNameBuffer);
            IoDeleteDevice(deviceObject);

            //
            // Indicate load failure to the I/O manager; driver image is deleted...
            //

            return(status);
        }

	    ctlDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    } else {
        //
        // An error occurred, we were not able to retrieve the address.
        // Clean up the mess
        //
        ExFreePool(pDevNameBuffer);

        //
        // Return our device object
        //
        IoDeleteDevice(deviceObject);
        return status;
    }
#if DBG
    DbgPrint("%wZ AddDevice: done\n",&devExt->DeviceName);
#endif



	//
	//  Clear the Device Initializing bit since the Device Object was created
	//  outside of DriverEntry.
	//
	deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

	return(STATUS_SUCCESS);
}

static PSTR pnpMinorCodes[] = 
{
    "IRP_MN_START_DEVICE",
    "IRP_MN_QUERY_REMOVE_DEVICE",
    "IRP_MN_REMOVE_DEVICE",
    "IRP_MN_CANCEL_REMOVE_DEVICE",
    "IRP_MN_STOP_DEVICE",
    "IRP_MN_QUERY_STOP_DEVICE",
    "IRP_MN_CANCEL_STOP_DEVICE",
    "IRP_MN_QUERY_DEVICE_RELATIONS",
    "IRP_MN_QUERY_INTERFACE",
    "IRP_MN_QUERY_CAPABILITIES",
    "IRP_MN_QUERY_RESOURCES",
    "IRP_MN_QUERY_RESOURCE_REQUIREMENTS",
    "IRP_MN_QUERY_DEVICE_TEXT",
    "IRP_MN_FILTER_RESOURCE_REQUIREMENTS",
    "***** FUNCTION 0x0e",
    "IRP_MN_READ_CONFIG",
    "IRP_MN_WRITE_CONFIG",
    "IRP_MN_EJECT",
    "IRP_MN_SET_LOCK",
    "IRP_MN_QUERY_ID",
    "IRP_MN_QUERY_PNP_DEVICE_STATE",
    "IRP_MN_QUERY_BUS_INFORMATION",
    "IRP_MN_DEVICE_USAGE_NOTIFICATION",
    "IRP_MN_SURPRISE_REMOVAL",
    "IRP_MN_QUERY_LEGACY_BUS_INFORMATION"
};

///////////////////////////////////////////////////////////////////////////////
//
//  Notify Bus Driver
//
///////////////////////////////////////////////////////////////////////////////
NTSTATUS NotifyBusDriver(PDEVICE_OBJECT TargetDevice,PIRP Irp) 
{
    NTSTATUS    status;
    KEVENT      eventWaitLowerDrivers;

    KeInitializeEvent(&eventWaitLowerDrivers, NotificationEvent, FALSE);

    //
    // The BUS DRIVER handles this IRP before we do
    //
    IoCopyCurrentIrpStackLocationToNext(Irp);

    //
    // Call OsrPnpComplete() when this IRP is done...
    //
    IoSetCompletionRoutine(Irp,
                          OsrPnpComplete,
                          &eventWaitLowerDrivers,
                          TRUE,
                          TRUE,
                          TRUE);

    //
    // Send the IRP to the bus driver.  Let's see what HE
    // thinks.
    //
    status = IoCallDriver(TargetDevice, Irp);

    if (STATUS_PENDING == status) {

        KeWaitForSingleObject(&eventWaitLowerDrivers,
                           Executive,
                           KernelMode,
                           FALSE,
                           NULL);

        status = Irp->IoStatus.Status;
    }

    return status;

}


///////////////////////////////////////////////////////////////////////////////
//
//	RedirectPnP
//
//      This routine handles Plug and Play requests for the Redirect
//      driver.
//
//	INPUTS:
//      The usual found at a dispatch entry point
//
//      DeviceObject        Pointer to a device object 
//      IRP                 Pointer to an IRP
//
//
//	OUTPUTS:
//
//		None.
//
//	RETURNS:
//
//      NTSTATUS -- status of request.
//
//  IRQL:
//
//    This routine is called at IRQL_PASSIVE_LEVEL.
//
//	NOTES:
//
//
///////////////////////////////////////////////////////////////////////////////
NTSTATUS RedirectPnp(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    PIO_STACK_LOCATION ioStackLocation = IoGetCurrentIrpStackLocation(Irp);
    PENCRYPT_DEVICE_EXT devExt = (PENCRYPT_DEVICE_EXT)DeviceObject->DeviceExtension;
    NTSTATUS status;
    UNICODE_STRING linkName;

#if DBG
    DbgPrint(DRIVER_NAME "PnP: PROCESSING IRP_MJ_PNP\n");

    if(ioStackLocation->MinorFunction <= IRP_MN_QUERY_LEGACY_BUS_INFORMATION ) {

        DbgPrint("%wZ Pnp: *** PNP Minor Function is %s\n",&devExt->DeviceName, 
                        pnpMinorCodes[ioStackLocation->MinorFunction]);

    } else  {

        DbgPrint("%wZ Pnp: *** PNP Minor Function is 0x%0x\n",&devExt->DeviceName,
                ioStackLocation->MinorFunction);
    }
#endif // DBG

    //
    // Handle the Minor Function
    //
    switch(ioStackLocation->MinorFunction)  {
        
        case IRP_MN_START_DEVICE:

#if DBG
            DbgPrint("%wZ Pnp: PROCESSING START_DEVICE\n",&devExt->DeviceName);
#endif

            //
            // We are considered to be at full power here, as it is 
            // required that the bus driver power up devices before
            // allowing them to receive an IRP_MN_START_DEVICE.  We
            // will record the power states here.
            //
            devExt->DevicePowerState = PowerDeviceD0;
            devExt->SystemPowerState = PowerSystemWorking;

            //
            // The BUS DRIVER handles this IRP before we do
            //

			status = NotifyBusDriver(devExt->DeviceToSendIrpsTo, Irp);

            //
            // Can the bus driver do the start?
            //
            if (!NT_SUCCESS(status)) {

                //
                // The bus driver has declined to start the device.
                // Oh well...
                //
#if DBG
                DbgPrint(DRIVER_NAME 
                         "Pnp: IoCallDriver() for START fails! 0x%0x \n", status);
#endif
            } else  {                               

#if DBG
                DbgPrint(DRIVER_NAME 
                         "Pnp: IoCallDriver() for START succeeds...\n");
#endif
                //
                // Check to see if we are registered with the controller.
                // if we are not, then we must.
                //
                if(!devExt->Registered) {
                    RegisterWithRedirect(devExt);
                }
                status = STATUS_SUCCESS;

            }

            //
            // We must now complete the IRP, since we stopped it in the
            // completetion routine with MORE_PROCESSING_REQUIRED.
            //
            Irp->IoStatus.Status = status;

            Irp->IoStatus.Information = 0;

            IoCompleteRequest(Irp, IO_NO_INCREMENT);

            return(status);            

        case IRP_MN_CANCEL_REMOVE_DEVICE:
        case IRP_MN_CANCEL_STOP_DEVICE:
            //
            // Set the status that indicates WE'RE good with the request
            //
            Irp->IoStatus.Status = STATUS_SUCCESS;
            Irp->IoStatus.Information = 0;

            IoSkipCurrentIrpStackLocation (Irp);

            status = IoCallDriver (devExt->DeviceToSendIrpsTo, Irp);

            break;

        case IRP_MN_QUERY_REMOVE_DEVICE:
        case IRP_MN_QUERY_STOP_DEVICE:
            //
            // Set the status that indicates WE'RE good with the request
            //
            Irp->IoStatus.Status = STATUS_SUCCESS;
            Irp->IoStatus.Information = 0;

            IoSkipCurrentIrpStackLocation (Irp);

            status = IoCallDriver (devExt->DeviceToSendIrpsTo, Irp);

            break;

        case IRP_MN_STOP_DEVICE:

            //
            // Set the status that indicates WE'RE good with the request
            //
            Irp->IoStatus.Status = STATUS_SUCCESS;
            Irp->IoStatus.Information = 0;

            IoSkipCurrentIrpStackLocation (Irp);

            status = IoCallDriver (devExt->DeviceToSendIrpsTo, Irp);

            break;

        case IRP_MN_SURPRISE_REMOVAL:

            //
            // We're not able to take any more redirected requests, so
            // Unregister with the controller.
            //
            UnregisterWithRedirect(devExt);
#if DBG
            DbgPrint(DRIVER_NAME "Pnp: Default action\n");
#endif
            IoSkipCurrentIrpStackLocation (Irp);

            status = IoCallDriver (devExt->DeviceToSendIrpsTo, Irp);
            break;


        //
        // The only thing we have to take any ACTION on is
        // the request to REMOVE the device
        //
        case IRP_MN_REMOVE_DEVICE:
#if DBG
            DbgPrint(DRIVER_NAME "Pnp: Doing remove\n");
#endif
            //
            // We're not able to take any more redirected requests, so
            // Unregister with the controller.
            //
            UnregisterWithRedirect(devExt);

            //
            // Free the memory that held the device name.
            //
            ExFreePool(devExt->DeviceName.Buffer);

			//
			// Don't wait for the Irp to finish, don't set a completion 
			// routine and don't complete the Irp! Just foist it off to the
			// PDO and propogate the returned status
			//
			IoSkipCurrentIrpStackLocation(Irp);

			status = IoCallDriver(devExt->DeviceToSendIrpsTo, Irp);

            //
            // Detach from the PDO 
            //
            IoDetachDevice(devExt->DeviceToSendIrpsTo);

            //
            // Return our device object
            //
            IoDeleteDevice(devExt->DeviceObject);

#if DBG
            devExt->DeviceToSendIrpsTo = NULL;

            devExt = NULL;
#endif
            return status;

        default:
            //
            // DEFAULT CASE
            // Just pass the request to the lower driver
            //
#if DBG
            DbgPrint(DRIVER_NAME "Pnp: Default action\n");
#endif
            IoSkipCurrentIrpStackLocation (Irp);

            status = IoCallDriver (devExt->DeviceToSendIrpsTo, Irp);

            break;

    }

    return(status);
}


//
// OsrPnpComplete
// 
// This is the completion routine for IRP_MJ_PNP requests
//
NTSTATUS
OsrPnpComplete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
    PKEVENT pEvent = (PKEVENT) Context;

    UNREFERENCED_PARAMETER (DeviceObject);

    KeSetEvent(pEvent, 0, FALSE);

    //
    // Take the IRP back so that we can continue using it during
    // the IRP_MN_START_DEVICE dispatch routine.
    // NB: we will have to call IoCompleteRequest
    //
    return STATUS_MORE_PROCESSING_REQUIRED;
}

