/*--         
Copyright (c) 1998, 1999  Microsoft Corporation

Module Name:

    pnp.c

Abstract:

Environment:

    Kernel mode only.

Notes:


--*/

#include "usbverfy.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, UsbVerify_AddDevice)
#pragma alloc_text (PAGE, UsbVerify_InitializeFromRegistry)
#pragma alloc_text (PAGE, UsbVerify_OpenServiceParameters)
#pragma alloc_text (PAGE, UsbVerify_PnP)
#pragma alloc_text (PAGE, UsbVerify_QueryKey)
#pragma alloc_text (PAGE, UsbVerify_StartDevice)
#endif

// {F16328AF-4480-4b18-B028-51301BEB166D}
const GUID GUID_USB_VERIFY  = 
{ 0xf16328afL, 0x4480, 0x4b18, { 0xb0, 0x28, 0x51, 0x30, 0x1b, 0xeb, 0x16, 0x6d } };

NTSTATUS
UsbVerify_QueryKey (
    IN  HANDLE  Handle,
    IN  PWCHAR  ValueNameString,
    OUT PVOID   Data,
    IN  ULONG   DataLength
    )
{
    PKEY_VALUE_FULL_INFORMATION fullInfo;
    NTSTATUS                    status;
    UNICODE_STRING              valueName;
    ULONG                       length;

    PAGED_CODE();

    RtlInitUnicodeString(&valueName, ValueNameString);

    length = sizeof (KEY_VALUE_FULL_INFORMATION)
           + valueName.MaximumLength + DataLength;

    fullInfo = ExAllocatePool (PagedPool, length);

    if (fullInfo) {
        status = ZwQueryValueKey(Handle,
                                 &valueName,
                                 KeyValueFullInformation,
                                 fullInfo,
                                 length,
                                 &length);

        if (NT_SUCCESS(status)) {
//            ASSERT(DataLength == fullInfo->DataLength);
            RtlCopyMemory(Data,
                          ((PUCHAR) fullInfo) + fullInfo->DataOffset,
                          fullInfo->DataLength);
        }

        ExFreePool(fullInfo);
    }
    else {
        status = STATUS_NO_MEMORY;
    }

    return status;
}

HANDLE
UsbVerify_OpenServiceParameters(
    PUSB_VERIFY_DEVICE_EXTENSION DeviceExtension
    )
{
    OBJECT_ATTRIBUTES oa;
    UNICODE_STRING    parameters;
    HANDLE            hService, hParameters = NULL;
    NTSTATUS          status;

    PAGED_CODE();

    InitializeObjectAttributes(
        &oa,
        UsbVerify_GetRegistryPath(DeviceExtension->Self->DriverObject),
        OBJ_CASE_INSENSITIVE,
        NULL,
        (PSECURITY_DESCRIPTOR) NULL);

    //
    // Try to create/open the service key.  The key should always exist
    //  for Win2k but may or may not need to be created if this is Win9x.
    //  However, for simplicity's sake, ZwCreateKey() will be used on
    //  both OSs as it acts like open if the key is already created.
    //
    // Previous code was:
    //
    //  status = ZwOpenKey(&hService, KEY_ALL_ACCESS, &oa);
    //

    status = ZwCreateKey (&hService, 
                          KEY_ALL_ACCESS, 
                          &oa,
                          0,
                          NULL,
                          REG_OPTION_NON_VOLATILE,
                          NULL);

    if (NT_SUCCESS (status)) 
    {
        RtlInitUnicodeString(&parameters, L"Parameters");
        InitializeObjectAttributes (&oa,
                                    &parameters,
                                    OBJ_CASE_INSENSITIVE,
                                    hService,
                                    (PSECURITY_DESCRIPTOR) NULL);

        ZwOpenKey (&hParameters, KEY_ALL_ACCESS, &oa);
        ZwClose(hService);
    }
    else 
    {
        DbgPrint("ZwCreateKey failed with status 0x%08x\n", status);
    }

    return hParameters;
}

VOID
UsbVerify_InitializeFromRegistry(
    PUSB_VERIFY_DEVICE_EXTENSION DeviceExtension,
    HANDLE Handle
    )
{
    PAGED_CODE();

    UsbVerify_QueryKey(Handle,
                       USB_VERIFY_FLAGS_SZ,
                       &DeviceExtension->VerifyFlags,
                       sizeof(DeviceExtension->VerifyFlags));

    UsbVerify_QueryKey(Handle,
                       USB_LOG_FLAGS_SZ,
                       &DeviceExtension->LogFlags,
                       sizeof(DeviceExtension->LogFlags));

    UsbVerify_QueryKey(Handle,
                       USB_VERIFY_LOGSIZE_SZ,
                       &DeviceExtension->LogSize,
                       sizeof(DeviceExtension->LogSize));

    UsbVerify_QueryKey(Handle,
                       USB_PRINT_FLAGS_SZ,
                       &DeviceExtension->PrintFlags,
                       sizeof(DeviceExtension->PrintFlags));

    UsbVerify_QueryKey(Handle,
                       USB_WARNINGS_AS_ERRORS_SZ,
                       &DeviceExtension->TreatWarningsAsErrors,
                       sizeof(DeviceExtension->TreatWarningsAsErrors));

}

NTSTATUS
UsbVerify_AddDevice(
    IN PDRIVER_OBJECT   Driver,
    IN PDEVICE_OBJECT   PDO
    )
{
    PUSB_VERIFY_DEVICE_EXTENSION    devExt;
    PDEVICE_OBJECT                  device;
    HANDLE                          hKey, hParameters;
    NTSTATUS                        status = STATUS_SUCCESS;

    PAGED_CODE();

    status = IoCreateDevice(Driver,                   
                            sizeof(USB_VERIFY_DEVICE_EXTENSION), 
                            NULL,                    
                            FILE_DEVICE_NULL,    
                            0,                   
                            FALSE,              
                            &device            
                            );

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

    RtlZeroMemory(device->DeviceExtension, sizeof(USB_VERIFY_DEVICE_EXTENSION));

    devExt = GetExtension(device);
    devExt->TopOfStack = IoAttachDeviceToDeviceStack(device, PDO);

    ASSERT(devExt->TopOfStack);

    devExt->Self = device;
    devExt->PDO = PDO;
    devExt->PowerState = PowerDeviceD0;

    devExt->VerifyState = Uninitialized;

    UsbVerify_InitializeInterfaceListLock(devExt);
    devExt->InterfaceList = NULL;
    devExt->InterfaceListCount = -1;
    devExt->InterfaceListSize = -1;

    UsbVerify_InitializeUrbListLock(devExt);
    UsbVerify_InitializeUrbList(devExt);

    UsbVerify_InitLog(devExt);

    //
    // Preinitialize in case the values are missing from the reg
    // 
    devExt->VerifyFlags = VERIFY_FLAGS_DEFAULT;
    devExt->LogFlags    = LOG_FLAGS_DEFAULT;
    devExt->LogSize     = LOG_SIZE_DEFAULT;
    devExt->PrintFlags  = PRINT_FLAGS_DEFAULT;

    devExt->TreatWarningsAsErrors = FALSE;

    //
    // Read values from the sevices\parameters key first. 
    // Then read from the devnode.
    //
    // The devnode values will override the global values
    //

    hParameters = UsbVerify_OpenServiceParameters(devExt);

    if (hParameters != NULL) {
        UsbVerify_InitializeFromRegistry(devExt, hParameters);
        ZwClose(hParameters);
    }

    status = IoOpenDeviceRegistryKey(devExt->PDO,
                                     PLUGPLAY_REGKEY_DEVICE, 
                                     STANDARD_RIGHTS_READ,
                                     &hKey);

    if (NT_SUCCESS(status)) {

        UsbVerify_InitializeFromRegistry(devExt, hKey);
        ZwClose(hKey);

    }

    device->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE);
    device->Flags &= ~DO_DEVICE_INITIALIZING;

#ifdef DO_INTERFACE
    status = IoRegisterDeviceInterface(PDO,
                                       (LPGUID)&GUID_USB_VERIFY,
                                       NULL,
                                       &devExt->SymbolicLinkName);

    if (!NT_SUCCESS(status)) {
        IoDeleteDevice(device);
    }
#endif
    
    if (NT_SUCCESS(status))
    {
        NTSTATUS       temp;
        WCHAR          dd[512];
        ULONG          len;
        UNICODE_STRING uniDD;
        ANSI_STRING    ansiDD;

        temp = IoGetDeviceProperty(devExt -> PDO,
                                   DevicePropertyDeviceDescription,
                                   sizeof(dd),
                                   dd,
                                   &len);

        if (NT_SUCCESS(temp))
        {    
            RtlInitUnicodeString(&uniDD, dd);

            temp = RtlUnicodeStringToAnsiString(&ansiDD, &uniDD, TRUE);

            if (NT_SUCCESS(temp))
            {
                DbgPrint("Usbverifier loaded on %s\n", ansiDD.Buffer);
                RtlFreeAnsiString(&ansiDD);
            }
        }
    }

    return status;
}

NTSTATUS
UsbVerify_SendIrpSynchronously(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )
{
    KEVENT      event;
    NTSTATUS    status;

    IoCopyCurrentIrpStackLocationToNext(Irp);
    KeInitializeEvent(&event,
                      SynchronizationEvent,
                      FALSE
                      );

    IoSetCompletionRoutine(Irp,
                           (PIO_COMPLETION_ROUTINE) UsbVerify_Complete, 
                           &event,
                           TRUE,
                           TRUE,
                           TRUE); // No need for Cancel

    status = IoCallDriver(DeviceObject, Irp);

    if (STATUS_PENDING == status) {
        KeWaitForSingleObject(
           &event,
           Executive, // Waiting for reason of a driver
           KernelMode, // Waiting in kernel mode
           FALSE, // No allert
           NULL); // No timeout

        status = Irp->IoStatus.Status;
    }

    return status;
}

#define UsbVerify_LogPnpEvent(devExt, irp)          \
if ((devExt)->LogFlags & LOG_PNP) {                 \
    USB_VERIFY_LOG_ENTRY logEntry;                  \
    RtlZeroMemory(&logEntry, sizeof(logEntry));     \
    logEntry.Type = LOG_PNP;                        \
    logEntry.u.PnpEvent.MinorFunction = IoGetCurrentIrpStackLocation((irp))->MinorFunction; \
    UsbVerify_Log(devExt, &logEntry);               \
}

NTSTATUS
UsbVerify_PnP(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This routine is the dispatch routine for plug and play irps 

Arguments:

    DeviceObject - Pointer to the device object.

    Irp - Pointer to the request packet.

Return Value:

    Status is returned.

--*/
{
    PUSB_VERIFY_DEVICE_EXTENSION devExt; 
    PIO_STACK_LOCATION          stack;
    NTSTATUS                    status = STATUS_SUCCESS;

    PAGED_CODE();

    devExt = GetExtension(DeviceObject);
    stack = IoGetCurrentIrpStackLocation(Irp);

    switch (stack->MinorFunction) {
    case IRP_MN_START_DEVICE: 

        //
        // The device is starting.
        //
        // We cannot touch the device (send it any non pnp irps) until a
        // start device has been passed down to the lower drivers.
        //
        
        status = UsbVerify_SendIrpSynchronously(devExt->TopOfStack, Irp);

        if (NT_SUCCESS(status))
        {

            //
            // As we are successfully now back from our start device
            // we can do work.
            //

            UsbVerify_LogPnpEvent(devExt, Irp);
            UsbVerify_StartDevice(devExt);
        }

        //
        // We must now complete the IRP, since we stopped it in the
        // completion routine with MORE_PROCESSING_REQUIRED.  
        //

        Irp->IoStatus.Status      = status;
        Irp->IoStatus.Information = 0;

        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        break;

    case IRP_MN_SURPRISE_REMOVAL:
        //
        // Same as a remove device, but don't call IoDetach or IoDeleteDevice
        //
        devExt->VerifyState = SurpriseRemoved;
        UsbVerify_LogPnpEvent(devExt, Irp);

        // Remove code here

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

    case IRP_MN_REMOVE_DEVICE:
        
        devExt->VerifyState = Removed;
        UsbVerify_LogPnpEvent(devExt, Irp);

        UsbVerify_ASSERT(devExt->HasFrameLengthControl == FALSE,
                         devExt->Self,
                         Irp,
                         NULL);

        //
        // NOTE: it is unclear where we should check for any URBs that are still
        //          pending.  I am concerned that many USB clients will leave
        //          URBs pended and will let USBD clean up the URBs upon removal.
        //          I am not sure if this is by design or not.  
        //
        //        If we don't check before the remove is sent, we definitely/
        //          need to check afterwards
        //
        
        // UsbVerify_CheckPendingUrbs(devExt); 

        status = UsbVerify_SendIrpSynchronously(devExt->TopOfStack, Irp);

        UsbVerify_RemoveDevice(devExt);        

        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        break;

    // 
    // We just want to log these events
    // 
    case IRP_MN_QUERY_REMOVE_DEVICE:
    case IRP_MN_QUERY_STOP_DEVICE:
    case IRP_MN_CANCEL_REMOVE_DEVICE:
    case IRP_MN_CANCEL_STOP_DEVICE:
    case IRP_MN_STOP_DEVICE:
        UsbVerify_LogPnpEvent(devExt, Irp);

    case IRP_MN_FILTER_RESOURCE_REQUIREMENTS: 
    case IRP_MN_QUERY_DEVICE_RELATIONS:
    case IRP_MN_QUERY_INTERFACE:
    case IRP_MN_QUERY_CAPABILITIES:
    case IRP_MN_QUERY_DEVICE_TEXT:
    case IRP_MN_QUERY_RESOURCES:
    case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
    case IRP_MN_READ_CONFIG:
    case IRP_MN_WRITE_CONFIG:
    case IRP_MN_EJECT:
    case IRP_MN_SET_LOCK:
    case IRP_MN_QUERY_ID:
    case IRP_MN_QUERY_PNP_DEVICE_STATE:
    default:
        //
        // Here the filter driver might modify the behavior of these IRPS
        // Please see PlugPlay documentation for use of these IRPs.
        //
        IoSkipCurrentIrpStackLocation(Irp);
        status = IoCallDriver(devExt->TopOfStack, Irp);
        break;
    }

    return status;
}

VOID
UsbVerify_StartDevice(
    PUSB_VERIFY_DEVICE_EXTENSION DeviceExtension
    )
{
    PAGED_CODE();

    DeviceExtension->VerifyState = Started;

    if (!DeviceExtension->Initialized) {
#ifdef DO_INTERFACE
        IoSetDeviceInterfaceState(&DeviceExtension->SymbolicLinkName, TRUE);
#endif
        DeviceExtension->Initialized = TRUE;
    }
}

VOID
UsbVerify_RemoveDevice(
    PUSB_VERIFY_DEVICE_EXTENSION DeviceExtension
    )
{
    KIRQL irql;

#ifdef DO_INTERFACE
    IoSetDeviceInterfaceState(&DeviceExtension->SymbolicLinkName, FALSE);
    RtlFreeUnicodeString(&DeviceExtension->SymbolicLinkName);
#endif

    UsbVerify_FreePendingUrbsList(DeviceExtension); 

    UsbVerify_CheckReplacedUrbs(DeviceExtension);

    UsbVerify_LockInterfaceList(DeviceExtension, irql);    
    UsbVerify_ClearInterfaceList(DeviceExtension, RemoveDeviceRemoved);
    UsbVerify_UnlockInterfaceList(DeviceExtension, irql);    

    UsbVerify_DestroyLog(DeviceExtension);

    //
    // Clean up all allocated memory
    //

    IoDetachDevice(DeviceExtension->TopOfStack); 
    IoDeleteDevice(DeviceExtension->Self);
}