
// ===================================================================
//
//      Testdrv.c
//
//      rev 1.0 
//
//      USB device driver for USB Device Example 
//      kernel mode driver 
//
//      to be compiled with 
//      - NT 4.0 DDK (MSDN DDK Jan 97 release or later)
//      - USB DDK (Aug 96 release or later)
//
// ===================================================================

#define DRIVER
// Include files needed for WDM driver support; from NT DDK
#include "wdm.h"
#include "stdarg.h"
#include "stdio.h"

// Include files needed for USB support; from USB DDK
#include "usbdi.h"
#include "usbdlib.h"
#include "usb.h"

#include "Testdrv.h"    // headers specified to this driver

// ===================================================================
NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
/*
    Entry point for loading of the driver
    This is where the driver is called when the driver is being loaded
    by the I/O system.  

    DriverObject - pointer to the driver object
    RegistryPath - pointer to a unicode string representing the path
                   to driver-specific key in the registry
    Return Value:
    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise
*/

{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PDEVICE_OBJECT deviceObject = NULL;

    DriverObject->MajorFunction[IRP_MJ_CREATE] = Test_Create;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = Test_Create;
    DriverObject->DriverUnload = Test_Unload;

    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Test_ProcessIOCTL;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = Test_Write;
    DriverObject->MajorFunction[IRP_MJ_READ] = Test_Read;

    DriverObject->MajorFunction[IRP_MJ_PNP_POWER] = Test_Dispatch;
    DriverObject->DriverExtension->AddDevice = Test_PnPAddDevice;

    return ntStatus;
}


// ===================================================================
NTSTATUS
Test_Dispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp
    )
/*
    Dispacthing the IRPs sent to the device.

    DeviceObject - pointer to a device object
    Irp          - pointer to an I/O Request Packet
    Return Value: NTSTATUS
*/
{
    PIO_STACK_LOCATION irpStack, nextStack;
    PDEVICE_EXTENSION deviceExtension;
    NTSTATUS ntStatus;

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

    // Get a pointer to the current location in the Irp. This is where
    // the function codes and parameters are located.
    irpStack = IoGetCurrentIrpStackLocation (Irp);

    // Get a pointer to the device extension
    deviceExtension = DeviceObject->DeviceExtension;

    switch (irpStack->MajorFunction) {

        case IRP_MJ_PNP_POWER:

            // This IRP is for Plug and Play and Power Management messages for your device.
            
            switch (irpStack->MinorFunction) {
                case IRP_MN_START_DEVICE:

                    // We pass the Irp down first 
                    nextStack = IoGetNextIrpStackLocation(Irp);
                    ASSERT(nextStack != NULL);
                    RtlCopyMemory(nextStack, irpStack, sizeof(IO_STACK_LOCATION));

                    // This will be deviceExtension->StackDeviceObject in future revisions of this driver
                    ntStatus = IoCallDriver(deviceExtension->PhysicalDeviceObject, Irp);

                    // begin our configuration actions on the device
                    ntStatus = Test_StartDevice(DeviceObject);

                    break; //IRP_MN_START_DEVICE

                case IRP_MN_STOP_DEVICE:

                    Test_Cleanup (DeviceObject);

                    ntStatus = Test_StopDevice(DeviceObject);

                    break; //IRP_MN_STOP_DEVICE

                case IRP_MN_REMOVE_DEVICE:

                    Test_Cleanup (DeviceObject);

                    ntStatus = Test_RemoveDevice(DeviceObject);

                    // Delete the link to the Stack Device Object, and delete the
                    // Functional Device Object we created
                    IoDetachDevice(deviceExtension->StackDeviceObject);

                    IoDeleteDevice (DeviceObject);

                    break; //IRP_MN_REMOVE_DEVICE

                case IRP_MN_SET_POWER:

                    switch (irpStack->Parameters.Power.Type) {
                        case SystemPowerState:
                        case DeviceSpecificPowerState:
                            // TODO: Your device may need to handle these Ioctls.
                            break; //SystemPowerState & DeviceSpecificPowerState

                        case DevicePowerState:
                            switch (irpStack->Parameters.Power.State.DeviceState) {
                                case PowerDeviceD3:
                                    break;
                                case PowerDeviceD2:
                                    break;
                                case PowerDeviceD1:
                                    break;
                                case PowerDeviceD0:
                                    break;
                            } // switch on Power.State.DeviceState

                            break; //DevicePowerState

                    }// switch on Power.Type

                    break;  //IRP_MN_SET_POWER

                 case IRP_MN_QUERY_POWER:

                    // Look at what type of power query this is
                    
                    switch (irpStack->Parameters.Power.Type) {
                        case SystemPowerState:
                        case DeviceSpecificPowerState:
                            // TODO: Your device may need to handle these Ioctls.
                            break; //SystemPowerState & DeviceSpecificPowerState


                        case DevicePowerState:
                            switch (irpStack->Parameters.Power.State.DeviceState) {
                                case PowerDeviceD2:
                                    break;
                                case PowerDeviceD1:
                                    break;
                                case PowerDeviceD3:
                                    break;
                            } //switch on Power.State.DeviceState

                            break; //DevicePowerState
                            
                    }//switch on Power.Type

                    break; //IRP_MN_QUERY_POWER

                case IRP_MN_QUERY_STOP_DEVICE:
                    break;
                case IRP_MN_QUERY_REMOVE_DEVICE:
                    break;
                case IRP_MN_CANCEL_STOP_DEVICE:
                    break;
                case IRP_MN_CANCEL_REMOVE_DEVICE:
                    break;

                default:
                    break;

            } 

            nextStack = IoGetNextIrpStackLocation(Irp);
            ASSERT(nextStack != NULL);
            RtlCopyMemory(nextStack, irpStack, sizeof(IO_STACK_LOCATION));

            // All PNP_POWER messages get passed to the PhysicalDeviceObject
            // (which in future revisions of this driver will be the StackDeviceObject)
            // we were given in PnPAddDevice.
            
            // This will be deviceExtension->StackDeviceObject in future revisions of this driver
            ntStatus =
                IoCallDriver(deviceExtension->PhysicalDeviceObject, Irp);

            // If lower layer driver marked the Irp as pending then reflect that by
            // calling IoMarkIrpPending.
            if (ntStatus == STATUS_PENDING) {
                IoMarkIrpPending(Irp);
            } else {} // if ntStatus

            goto Test_Dispatch_Done;
            
            break; //IRP_MJ_PNP_POWER

        default:
            
            Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;

        } 

    ntStatus = Irp->IoStatus.Status;

    IoCompleteRequest (Irp,
                       IO_NO_INCREMENT
                       );

Test_Dispatch_Done:
            return ntStatus;

}

// ===================================================================
VOID Test_Unload(IN PDRIVER_OBJECT DriverObject)
/*
    Free all the allocated resources, etc.
    TODO: This is a placeholder for driver writer to add code on unload

    DriverObject - pointer to a driver object

    Return Value: None
*/
{
}

// ===================================================================
NTSTATUS
Test_StartDevice(
    IN  PDEVICE_OBJECT DeviceObject)
/*
    Initializes a given instance of the Test Device on the USB.

    DeviceObject - pointer to the device object for this instance of a
                Test Device

    Return Value: NT status code
*/
{
    PDEVICE_EXTENSION deviceExtension;
    NTSTATUS ntStatus;
    PUSB_DEVICE_DESCRIPTOR deviceDescriptor = NULL;
    PURB urb;
    ULONG siz;

    deviceExtension = DeviceObject->DeviceExtension;
    deviceExtension->NeedCleanup = TRUE;

        // Get some memory from then non paged pool (fixed, locked system memory)
        // for use by the USB Request Block (urb) for the specific USB Request we
        // will be performing below (a USB device request).
        urb = ExAllocatePool(NonPagedPool,
                         sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST));

    if (urb) {

        siz = sizeof(USB_DEVICE_DESCRIPTOR);

        // Get some non paged memory for the device descriptor contents
                deviceDescriptor = ExAllocatePool(NonPagedPool,
                                          siz);

        if (deviceDescriptor) {

            // Use a macro in the standard USB header files to build the URB
                        UsbBuildGetDescriptorRequest(urb,
                                         (USHORT) sizeof (struct _URB_CONTROL_DESCRIPTOR_REQUEST),
                                         USB_DEVICE_DESCRIPTOR_TYPE,
                                         0,
                                         0,
                                         deviceDescriptor,
                                         NULL,
                                         siz,
                                         NULL);

            // Get the device descriptor
                        ntStatus = Test_CallUSBD(DeviceObject, urb);

        } else {
            ntStatus = STATUS_NO_MEMORY;
        }

        if (NT_SUCCESS(ntStatus)) {
                        // Put a ptr to the device descriptor in the device extension for easy
                        // access.  We will free this memory when the
                        // device is removed.  See the "Test_RemoveDevice" code.
                        deviceExtension->DeviceDescriptor = deviceDescriptor;
            deviceExtension->Stopped = FALSE;
        } else if (deviceDescriptor) {
                        // If the bus transaction failed, then free up the memory created to hold
                        // the device descriptor, since the device is probably non-functional
                        ExFreePool(deviceDescriptor);
        }

        ExFreePool(urb);

    } else {
        ntStatus = STATUS_NO_MEMORY;
    }

        // If the Get_Descriptor call was successful, then configure the device.
        if (NT_SUCCESS(ntStatus)) {
        ntStatus = Test_ConfigureDevice(DeviceObject);
    }

    return ntStatus;
}


// ===================================================================
NTSTATUS
Test_RemoveDevice(
    IN  PDEVICE_OBJECT DeviceObject
    )
/*
    Removes a given instance of a Test Device device on the USB.

    DeviceObject - pointer to the device object for this instance of a Test Device

    Return Value: NT status 
*/
{
    PDEVICE_EXTENSION deviceExtension;
    NTSTATUS ntStatus = STATUS_SUCCESS;

    deviceExtension = DeviceObject->DeviceExtension;

    if (deviceExtension->DeviceDescriptor) {
        ExFreePool(deviceExtension->DeviceDescriptor);
    }

    // Free up any interface structures in our device extension
    if (deviceExtension->Interface != NULL) {
        ExFreePool(deviceExtension->Interface);
    }

    return ntStatus;
}

// ===================================================================
NTSTATUS
Test_StopDevice(
    IN  PDEVICE_OBJECT DeviceObject
    )
/*
    Stops a given instance of a test Device device on the USB.

    DeviceObject - pointer to the device object for this instance of a Test Device

    Return Value: NT status 
*/
{
    PDEVICE_EXTENSION deviceExtension;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PURB urb;
    ULONG siz;

    deviceExtension = DeviceObject->DeviceExtension;

    // Send the select configuration urb with a NULL pointer for the configuration
    // handle, this closes the configuration and puts the device in the 'unconfigured'
    // state.
    siz = sizeof(struct _URB_SELECT_CONFIGURATION);

    urb = ExAllocatePool(NonPagedPool,
                         siz);

    if (urb) {
        NTSTATUS status;

        UsbBuildSelectConfigurationRequest(urb,
                                          (USHORT) siz,
                                          NULL);

        status = Test_CallUSBD(DeviceObject, urb);

        ExFreePool(urb);
    } else {
        ntStatus = STATUS_NO_MEMORY;
    }

    return ntStatus;
}

// ===================================================================
NTSTATUS
Test_PnPAddDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT PhysicalDeviceObject
    )
/*
    This routine is called to create a new instance of the device

    DriverObject - pointer to the driver object for this instance of Test
    PhysicalDeviceObject - pointer to a device object created by the bus

    Return Value:
    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise
*/
{
    NTSTATUS                ntStatus = STATUS_SUCCESS;
    PDEVICE_OBJECT          deviceObject = NULL;
    PDEVICE_EXTENSION       deviceExtension;

    
    // create our funtional device object (FDO)
    ntStatus =
        Test_CreateDeviceObject(DriverObject, &deviceObject, 0);

    if (NT_SUCCESS(ntStatus)) {
        deviceExtension = deviceObject->DeviceExtension;

        deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

        // Add more flags here if your driver supports other specific
        // behavior.  For example, if your IRP_MJ_READ and IRP_MJ_WRITE
        // handlers support DIRECT_IO, you would set that flag here.
        
        deviceExtension->PhysicalDeviceObject=PhysicalDeviceObject;

        // Attach to the StackDeviceObject.  This is the device object that what we 
        // use to send Irps and Urbs down the USB software stack
        
        deviceExtension->StackDeviceObject =
            IoAttachDeviceToDeviceStack(deviceObject, PhysicalDeviceObject);

        ASSERT (deviceExtension->StackDeviceObject != NULL);
        
    }
    return ntStatus;
}


// ===================================================================
NTSTATUS
Test_CreateDeviceObject(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT *DeviceObject,
    LONG Instance
    )
/*
    Creates a Functional DeviceObject

    DriverObject - pointer to the driver object for device
    DeviceObject - pointer to DeviceObject pointer to return
                   created device object.
    Instance - instnace of the device create.

    Return Value:
    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise
*/
{
    NTSTATUS ntStatus;
    WCHAR deviceLinkBuffer[]  = L"\\DosDevices\\Test-0";
    UNICODE_STRING deviceLinkUnicodeString;
    WCHAR deviceNameBuffer[]  = L"\\Device\\Test-0";
    UNICODE_STRING deviceNameUnicodeString;
    PDEVICE_EXTENSION deviceExtension;

    deviceLinkBuffer[19] = (USHORT) ('0' + Instance);
    deviceNameBuffer[15] = (USHORT) ('0' + Instance);

    RtlInitUnicodeString (&deviceNameUnicodeString,
                          deviceNameBuffer);

    ntStatus = IoCreateDevice (DriverObject,
                               sizeof (DEVICE_EXTENSION),
                               &deviceNameUnicodeString,
                               FILE_DEVICE_UNKNOWN,
                               0,
                               FALSE,
                               DeviceObject);


    if (NT_SUCCESS(ntStatus)) {
        RtlInitUnicodeString (&deviceLinkUnicodeString,
                              deviceLinkBuffer);

        ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
                                         &deviceNameUnicodeString);

        // Initialize our device extension
        deviceExtension = (PDEVICE_EXTENSION) ((*DeviceObject)->DeviceExtension);

        RtlCopyMemory(deviceExtension->DeviceLinkNameBuffer,
                      deviceLinkBuffer,
                      sizeof(deviceLinkBuffer));

        deviceExtension->ConfigurationHandle = NULL;
        deviceExtension->DeviceDescriptor = NULL;
        deviceExtension->NeedCleanup = FALSE;

        // Initialize our interface
        deviceExtension->Interface = NULL;

    }

    return ntStatus;
}


// ===================================================================
VOID Test_Cleanup(PDEVICE_OBJECT DeviceObject)
/*
        Cleans up certain elements of the device object.  This is called when the device
        is being removed from the system

        DeviceObject - pointer to DeviceObject

        Return Value: None.
*/
{
    PDEVICE_EXTENSION deviceExtension;
    UNICODE_STRING deviceLinkUnicodeString;

    deviceExtension = DeviceObject->DeviceExtension;

    if (deviceExtension->NeedCleanup) {

        deviceExtension->NeedCleanup = FALSE;

        RtlInitUnicodeString (&deviceLinkUnicodeString,
                              deviceExtension->DeviceLinkNameBuffer);

        IoDeleteSymbolicLink(&deviceLinkUnicodeString);
    }
}


// ===================================================================
NTSTATUS
Test_CallUSBD(
    IN PDEVICE_OBJECT DeviceObject,
    IN PURB Urb
    )
/*
    Passes a Usb Request Block (URB) to the USB class driver (USBD)

    Note that we create our own IRP here and use it to send the request to
        the USB software subsystem.  This means that this routine is essentially
        independent of the IRP that caused this driver to be called in the first
        place.  The IRP for this transfer is created, used, and then destroyed
        in this routine.

    DiviceObject - pointer to the device object for this instance of a Test Device
    Urb          - pointer to Urb request block

    Return Value:
    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise
*/
{
    NTSTATUS ntStatus, status = STATUS_SUCCESS;
    PDEVICE_EXTENSION deviceExtension;
    PIRP irp;
    KEVENT event;
    IO_STATUS_BLOCK ioStatus;
    PIO_STACK_LOCATION nextStack;

    deviceExtension = DeviceObject->DeviceExtension;

    // issue a synchronous request (see notes above)
    KeInitializeEvent(&event, NotificationEvent, FALSE);

    irp = IoBuildDeviceIoControlRequest(
                IOCTL_INTERNAL_USB_SUBMIT_URB,
                deviceExtension->PhysicalDeviceObject,
                NULL,
                0,
                NULL,
                0,
                TRUE, /* INTERNAL */
                &event,
                &ioStatus);

    // Prepare for calling the USB driver stack
    nextStack = IoGetNextIrpStackLocation(irp);
    ASSERT(nextStack != NULL);

    // Set up the URB ptr to pass to the USB driver stack
    nextStack->Parameters.Others.Argument1 = Urb;

    // Call the USB class driver to perform the operation.  If the returned status
    // is PENDING, wait for the request to complete.
    ntStatus = IoCallDriver(deviceExtension->PhysicalDeviceObject,
                            irp);

    if (ntStatus == STATUS_PENDING) {
        
        status = KeWaitForSingleObject(
                       &event,
                       Suspended,
                       KernelMode,
                       FALSE,
                       NULL);

    } else {
        ioStatus.Status = ntStatus;
    }

    // USBD maps the error code for us.  USBD uses error codes in its URB
    // structure that are more insightful into USB behavior. To allow more insight into
    // the specific USB error that occurred, your driver may wish to examine the
    // URB's status code (Urb->UrbHeader.Status) as well.
    ntStatus = ioStatus.Status;

    return ntStatus;
}

// ===================================================================
NTSTATUS
Test_ConfigureDevice(IN  PDEVICE_OBJECT DeviceObject)
/*
    Configures the USB device via USB-specific device requests and interaction
        with the USB software subsystem.

    DeviceObject - pointer to the device object for this instance of the Test Device

    Return Value:
    NT status code
*/
{
    PDEVICE_EXTENSION deviceExtension;
    NTSTATUS ntStatus;
    PURB urb = NULL;
    ULONG siz;
    PUSB_CONFIGURATION_DESCRIPTOR configurationDescriptor = NULL;

    deviceExtension = DeviceObject->DeviceExtension;

    // Get memory for the USB Request Block (urb).
    urb = ExAllocatePool(NonPagedPool,
                         sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST));

    if (urb != NULL) {

        // Set size of the data buffer.  Note we add padding to cover hardware faults
        // that may cause the device to go past the end of the data buffer
                siz = sizeof(USB_CONFIGURATION_DESCRIPTOR) + 16;

        // Get the nonpaged pool memory for the data buffer
                configurationDescriptor = ExAllocatePool(NonPagedPool,
                                                 siz);

        if (configurationDescriptor != NULL) {

            UsbBuildGetDescriptorRequest(urb,
                                         (USHORT) sizeof (struct _URB_CONTROL_DESCRIPTOR_REQUEST),
                                         USB_CONFIGURATION_DESCRIPTOR_TYPE,
                                         0,
                                         0,
                                         configurationDescriptor,
                                         NULL,
                                         sizeof (USB_CONFIGURATION_DESCRIPTOR),/* Get only the configuration descriptor */
                                         NULL);

            ntStatus = Test_CallUSBD(DeviceObject, urb);


        } else {
            ntStatus = STATUS_NO_MEMORY;
            goto Exit_TestConfigureDevice;
        }//if-else

        //  Free up the data buffer memory just used
        ExFreePool(configurationDescriptor);
        configurationDescriptor = NULL;

        // Determine how much data is in the entire configuration descriptor
        // and add extra room to protect against accidental overrun
        siz = configurationDescriptor->wTotalLength + 16;

        // Get nonpaged pool memory for the data buffer
                configurationDescriptor = ExAllocatePool(NonPagedPool,
                                                 siz);

        // Now get the entire Configuration Descriptor
        if (configurationDescriptor != NULL) {

            UsbBuildGetDescriptorRequest(urb,
                                         (USHORT) sizeof (struct _URB_CONTROL_DESCRIPTOR_REQUEST),
                                         USB_CONFIGURATION_DESCRIPTOR_TYPE,
                                         0,
                                         0,
                                         configurationDescriptor,
                                         NULL,
                                         siz,  // Get all the descriptor data
                                         NULL);

            ntStatus = Test_CallUSBD(DeviceObject, urb);

            if (NT_SUCCESS(ntStatus)) {
                
            } else {
                //Error in getting configuration descriptor
                goto Exit_TestConfigureDevice;
            }//else

        } else {
            // Failed getting data buffer (configurationDescriptor) memory
            ntStatus = STATUS_NO_MEMORY;
            goto Exit_TestConfigureDevice;
        }//if-else

    } else {
        // failed getting urb memory
        ntStatus = STATUS_NO_MEMORY;
        goto Exit_TestConfigureDevice;
    }//if-else

    // We have the configuration descriptor for the configuration
    // we want.
    // Now we issue the SelectConfiguration command to get
    // the  pipes associated with this configuration.
    if (configurationDescriptor) {
        // Get our pipes
        ntStatus = Test_SelectInterfaces(DeviceObject,
                                           configurationDescriptor,
                                           NULL // Device not yet configured
                                           );
    } //if

Exit_TestConfigureDevice:

    // Clean up and exit this routine
    if (urb != NULL) {
        ExFreePool(urb);                    // Free urb memory
    }//if

    if (configurationDescriptor != NULL) {
        ExFreePool(configurationDescriptor);// Free data buffer
    }//if

    return ntStatus;
}

// ===================================================================
NTSTATUS
Test_SelectInterfaces(
    IN PDEVICE_OBJECT DeviceObject,
    IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor,
    IN PUSBD_INTERFACE_INFORMATION Interface
    )
/*
    Initializes a Test Device with multiple interfaces

    DeviceObject            - pointer to the device object for this instance of the Test Device
    ConfigurationDescriptor - pointer to the USB configuration descriptor containing the interface and endpoint
                              descriptors.
    Interface               - pointer to a USBD Interface Information Object
                            - If this is NULL, then this driver must choose its interface based on driver-specific
                              criteria, and the driver must also CONFIGURE the device.
                            - If it is NOT NULL, then the driver has already been given an interface and
                              the device has already been configured by the parent of this device driver.

    Return Value: NT status 
*/
{
    PDEVICE_EXTENSION deviceExtension;
    NTSTATUS ntStatus;
    PURB urb;
    ULONG siz, numberOfInterfaces, j;
    UCHAR numberOfPipes, alternateSetting, MyInterfaceNumber;
    PUSB_INTERFACE_DESCRIPTOR interfaceDescriptor;
    PUSBD_INTERFACE_INFORMATION interfaceObject;

    deviceExtension = DeviceObject->DeviceExtension;
        MyInterfaceNumber = SAMPLE_INTERFACE_NBR;

    if (Interface == NULL) {

        // This example driver only supports one interface.  This can be extended
        // to be a dynamically allocated array by your driver.
        numberOfInterfaces = ConfigurationDescriptor->bNumInterfaces;
                
                numberOfInterfaces =1;      
        numberOfPipes = 0;          // Initialize to zero

        // We use alternate interface setting 0 for all interfaces
        // This is a simplification and is due to change in future releases of this driver.  If
        // your driver supports alternate settings, you will have to do more work to switch between
        // alternate settings.
        alternateSetting = 0;

        // Call a USBD helper function that returns a ptr to a USB Interface Descriptor given
        // a USB Configuration Descriptor, an Inteface Number, and an Alternate Setting for that Interface
        interfaceDescriptor =
            USBD_ParseConfigurationDescriptor(ConfigurationDescriptor,
                                              MyInterfaceNumber, //interface number (this is bInterfaceNumber from interface descr)
                                              alternateSetting);

        ASSERT(interfaceDescriptor != NULL);

        // Add to the tally of pipes in this configuration 
        numberOfPipes += interfaceDescriptor->bNumEndpoints;

        // Now that we have looked at the interface, we configure the device so that the remainder
        // of the USBD objects will come into existence (ie., pipes, etc.) as a result of the configuration,
        // thus completing the configuration process for the USB device.
        //
        // Allocate a URB big enough for this Select Configuration request
        
        siz = GET_SELECT_CONFIGURATION_REQUEST_SIZE(numberOfInterfaces, numberOfPipes);

        urb = ExAllocatePool(NonPagedPool,
                             siz);

        if (urb) {
            interfaceObject = (PUSBD_INTERFACE_INFORMATION) (&(urb->UrbSelectConfiguration.Interface));
                       
            // set up the input parameters in our interface request structure.
            interfaceObject->Length =
                 GET_USBD_INTERFACE_SIZE(interfaceDescriptor->bNumEndpoints);

            interfaceObject->InterfaceNumber = interfaceDescriptor->bInterfaceNumber;
            interfaceObject->AlternateSetting = interfaceDescriptor->bAlternateSetting;
            interfaceObject->NumberOfPipes = interfaceDescriptor->bNumEndpoints;

            // We set up a default max transfer size for the endpoints.  Your driver will
            // need to change this to reflect the capabilities of your device's endpoints.
            for (j=0; j<interfaceDescriptor->bNumEndpoints; j++) {
                interfaceObject->Pipes[j].MaximumTransferSize =
                    USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE;
            } 

            UsbBuildSelectConfigurationRequest(urb,
                                              (USHORT) siz,
                                              ConfigurationDescriptor);

            ntStatus = Test_CallUSBD(DeviceObject, urb);

            if (NT_SUCCESS(ntStatus) && USBD_SUCCESS(urb->UrbSelectConfiguration.Status)) {

                // Save the configuration handle for this device
                deviceExtension->ConfigurationHandle =
                    urb->UrbSelectConfiguration.ConfigurationHandle;

                deviceExtension->Interface = ExAllocatePool(NonPagedPool,
                                                               interfaceObject->Length);

                if (deviceExtension->Interface) {

                    RtlCopyMemory(deviceExtension->Interface, interfaceObject, interfaceObject->Length);

                    // Dump the pipe info
                    for (j=0; j<interfaceObject->NumberOfPipes; j++) {
                        PUSBD_PIPE_INFORMATION pipeInformation;

                        pipeInformation = &deviceExtension->Interface->Pipes[j];
                    }

                } 

            }// if selectconfiguration request was successful 

        } else {

            ntStatus = STATUS_NO_MEMORY;

        }// if urb alloc passed 

    }//if Interface was not NULL

    return ntStatus;

}

// ===================================================================
NTSTATUS
Test_Read(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*
    This function is called for a IRP_MJ_READ.
    TODO:  Add functionality here for your device driver if it handles that IRP code.

    DeviceObject - pointer to the device object for this instance of the Test device.
    Irp          - pointer to IRP

    Return Value: NT status 
*/
{
        NTSTATUS ntStatus = STATUS_SUCCESS;
        return (ntStatus);

}

// ===================================================================
NTSTATUS
Test_Write(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*
    This function is called for a IRP_MJ_WRITE.
    TODO:  Add functionality here for your device driver if it handles that IRP code.

    DeviceObject - pointer to the device object for this instance of the Test device.
    Irp                  - pointer to IRP

    Return Value: NT status 
*/
{

        NTSTATUS ntStatus = STATUS_SUCCESS;
        return (ntStatus);

}

// ===================================================================
NTSTATUS
Test_Create(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*
     This is the Entry point for CreateFile calls from user mode apps (apps may open "\\.\Test-x\yyzz"
     where yy is the interface number and zz is the endpoint address).

     Here is where you would add code to create symbolic links between endpoints
     (i.e., pipes in USB software terminology) and User Mode file names.  You are
     free to use any convention you wish to create these links, although the above
     convention offers a way to identify resources on a device by familiar file and
     directory structure nomenclature.

     DeviceObject - pointer to the device object for this instance of the Test device

     Return Value: NT status 
*/
{
    NTSTATUS ntStatus;

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

        // Create all the symbolic links here
    ntStatus = Irp->IoStatus.Status;

    IoCompleteRequest (Irp,
                       IO_NO_INCREMENT
                       );

    return ntStatus;

}

// ===================================================================
NTSTATUS Test_Close(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*
    Entry point for CloseHandle calls from user mode apps to close handles they have opened

    DeviceObject - pointer to the device object for this instance of the Test device
    Irp          - pointer to an irp

    Return Value: NT status 
*/
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

        return ntStatus;

}

// ===================================================================
NTSTATUS
Test_ProcessIOCTL(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*
    This where all the DeviceIoControl codes are handled.  You can expand and add more code
    here to handle IOCTL/IRP codes that are specific to your device driver.

    DeviceObject - pointer to the device object for this instance of the test device.

    Return Value: NT status 
*/
{
    PIO_STACK_LOCATION irpStack;
    PVOID ioBuffer;
    ULONG inputBufferLength;
    ULONG outputBufferLength;
    PDEVICE_EXTENSION deviceExtension;
    ULONG ioControlCode;
    NTSTATUS ntStatus;
    ULONG length;
    PUCHAR pch;

    // Get a pointer to the current location in the Irp. This is where
    //     the function codes and parameters are located.
    irpStack = IoGetCurrentIrpStackLocation (Irp);

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

    // Get a pointer to the device extension
    deviceExtension = DeviceObject->DeviceExtension;

    ioBuffer           = Irp->AssociatedIrp.SystemBuffer;
    inputBufferLength  = irpStack->Parameters.DeviceIoControl.InputBufferLength;
    outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;

    ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

    // Handle Ioctls from User mode
    switch (ioControlCode) {

    case IRP_Test_GET_PIPE_INFO:
        // inputs  - none
        // outputs - we copy the interface information structure that we have
        //           stored in our device extension area to the output buffer which
        //           will be reflected to the user mode application by the IOS.
        length = 0;
        pch = (PUCHAR) ioBuffer;

        if (deviceExtension->Interface) {
            RtlCopyMemory(pch+length,
                          (PUCHAR) deviceExtension->Interface,
                          deviceExtension->Interface->Length);

            length += deviceExtension->Interface->Length;
        } 


        Irp->IoStatus.Information = length;
        Irp->IoStatus.Status = STATUS_SUCCESS;

        break;

       case IRP_Test_GET_DEVICE_DESCRIPTOR:
        // inputs  - pointer to a buffer in which to place descriptor data
        // outputs - we put the device descriptor data, if any is returned by the device
        //           in the system buffer and then we set the length inthe Information field
        //           in the Irp, which will then cause the system to copy the buffer back
        //           to the user's buffer
        
        length = Test_GetDeviceDescriptor (DeviceObject, ioBuffer);

        Irp->IoStatus.Information = length;
        Irp->IoStatus.Status = STATUS_SUCCESS;
      
        break;

    case IRP_Test_GET_CONFIGURATION_DESCRIPTOR:
        
        // inputs  - pointer to a buffer in which to place descriptor data
        // outputs - we put the configuration descriptor data, if any is returned by the device
        //           in the system buffer and then we set the length in the Information field
        //           in the Irp, which will then cause the system to copy the buffer back
        //           to the user's buffer
        
        length = Test_GetConfigDescriptor (DeviceObject, ioBuffer, outputBufferLength);

        Irp->IoStatus.Information = length;
        Irp->IoStatus.Status = STATUS_SUCCESS;
      
        break;


    default:

        Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
    }// switch on ioControlCode 

    ntStatus = Irp->IoStatus.Status;

    IoCompleteRequest (Irp,
                       IO_NO_INCREMENT
                       );

    return ntStatus;

}

// ===================================================================
ULONG
Test_GetDeviceDescriptor(
    IN PDEVICE_OBJECT DeviceObject,
    PVOID             pvOutputBuffer
    )
/*
    Gets a device descriptor from the given device object

    DeviceObject - pointer to the test device object

    Return Value: Number of valid bytes in data buffer  
*/
{
    PDEVICE_EXTENSION   deviceExtension = NULL;
    NTSTATUS            ntStatus        = STATUS_SUCCESS;
    PURB                urb             = NULL;
    ULONG               length          = 0;
    
    deviceExtension = DeviceObject->DeviceExtension;
    
    urb = ExAllocatePool(NonPagedPool, 
                         sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST));
                         
    if (urb) {
        
        if (pvOutputBuffer) {    
        
            UsbBuildGetDescriptorRequest(urb,
                                         (USHORT) sizeof (struct _URB_CONTROL_DESCRIPTOR_REQUEST),
                                         USB_DEVICE_DESCRIPTOR_TYPE,    //descriptor type
                                         0,                             //index
                                         0,                             //language ID
                                         pvOutputBuffer,                //transfer buffer
                                         NULL,                          //MDL
                                         sizeof(USB_DEVICE_DESCRIPTOR), //buffer length
                                         NULL);                         //link
                                                                  
            ntStatus = Test_CallUSBD(DeviceObject, urb);

        } else {
            ntStatus = STATUS_NO_MEMORY;
        }    

        // Get the length from the Urb
        length = urb->UrbControlDescriptorRequest.TransferBufferLength;

        ExFreePool(urb);
        
    } else {
        ntStatus = STATUS_NO_MEMORY;        
    }        
   
    return length;
}    


// ===================================================================
/* not tested it yet
NTSTATUS    SetOne(IN PDEVICE_OBJECT pDeviceObject, PUSBTESTCTRLDESC pTestCtrlDesc)
 
//     perform a "set" setup packet 
//
//     Return Value: ntstatus

{
  NTSTATUS  ntStatus;
  PURB      pUrb;

  pTemp=ExAllocatePool(NonPagedPool, sizeof(USBDISPDESC));
  pUrb=ExAllocatePool(NonPagedPool, sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST));

  if (pUrb)  {

    RtlCopyMemory(pTemp, pTestCtrlDesc, sizeof(USBTESTDESC));

    UsbBuildVendorRequest(pUrb,
                          URB_FUNCTION_CLASS_DEVICE,   //Command
                          sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST),
                          !(USBD_TRANSFER_DIRECTION_IN),// Direction Bit: OUT
                          0,                            // Reserved Bits !
                          0x04,                         // Request  (0xF1)
                          pTestCtrlDesc->usControlCode, // Value    (0xF0)
                          0x0,                          // Index    (0x00)
                          pTemp,                        // Transfer Buffer
                          NULL,                         // Transfer Buffer MDL
                          0x08,                         // Transfer buffer length: Size of display
                                                        // descriptor.
                          NULL) ;

    ntStatus=MakeUSBDCall(pDeviceObject, pUrb);

    ExFreePool(pUrb);
  }

  ExFreePool(pTemp);
  return ntStatus;
}
*/

// ===================================================================
ULONG
Test_GetConfigDescriptor(
    IN PDEVICE_OBJECT DeviceObject,
    PVOID             pvOutputBuffer,
    ULONG             ulLength
    )
/*
    Gets configuration descriptors from the given device object

    DeviceObject    - pointer to the test device object
    pvOutputBuffer  - pointer to the buffer where the data is to be placed
    ulLength        - length of the buffer

    Return Value: Number of valid bytes in data buffer  
*/
{
    PDEVICE_EXTENSION   deviceExtension = NULL;
    NTSTATUS            ntStatus        = STATUS_SUCCESS;
    PURB                urb             = NULL;
    ULONG               length          = 0;
    
    deviceExtension = DeviceObject->DeviceExtension;
    
    urb = ExAllocatePool(NonPagedPool, 
                         sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST));
                         
    if (urb) {
        
        if (pvOutputBuffer) {    
        
            UsbBuildGetDescriptorRequest(urb,
                                         (USHORT) sizeof (struct _URB_CONTROL_DESCRIPTOR_REQUEST),
                                         USB_CONFIGURATION_DESCRIPTOR_TYPE, //descriptor type
                                         0,                             //index
                                         0,                             //language ID
                                         pvOutputBuffer,                //transfer buffer
                                         NULL,                          //MDL
                                         ulLength,                      //buffer length
                                         NULL);                         //link
                                                                  
            ntStatus = Test_CallUSBD(DeviceObject, urb);

        } else {
            ntStatus = STATUS_NO_MEMORY;
        }    

        // Get the length from the Urb
        length = urb->UrbControlDescriptorRequest.TransferBufferLength;

        ExFreePool(urb);
        
    } else {
        ntStatus = STATUS_NO_MEMORY;        
    }        
    
    return length;
}    

// ==========================================================

