// ===================================================================
//
//      testusb.c
//
//      rev 1.0 beta
//
//      this module handles USB IOCTL/IRP calls     
//
//      USB device driver for USB Device Example 
//      kernel mode driver 
//
//
// ===================================================================

#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
#include "test98.h"

// ===================================================================
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;
    PFDO_DEVICE_DATA deviceData;
    PIRP irp;
    KEVENT event;
    IO_STATUS_BLOCK ioStatus;
    PIO_STACK_LOCATION nextStack;

    deviceData = (PFDO_DEVICE_DATA) DeviceObject->DeviceExtension;

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

    irp = IoBuildDeviceIoControlRequest(
                IOCTL_INTERNAL_USB_SUBMIT_URB,
                deviceData->UnderlyingPDO,
                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(deviceData->UnderlyingPDO,
                            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_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;
    PFDO_DEVICE_DATA deviceData;
    
    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
    deviceData = (PFDO_DEVICE_DATA) DeviceObject->DeviceExtension;

    // ***
    Test98_IncIoCount (deviceData);

    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;

        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
                       );

    Test98_DecIoCount (deviceData);

    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  
*/
{
    PFDO_DEVICE_DATA deviceData = NULL;
    
    NTSTATUS            ntStatus        = STATUS_SUCCESS;
    PURB                urb             = NULL;
    ULONG               length          = 0;
    
    deviceData = (PFDO_DEVICE_DATA) 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  
*/
{
    PFDO_DEVICE_DATA deviceData = NULL;
    NTSTATUS            ntStatus        = STATUS_SUCCESS;
    PURB                urb             = NULL;
    ULONG               length          = 0;
    
    deviceData = (PFDO_DEVICE_DATA) 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;
}    

// ===================================================================
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;
        UNREFERENCED_PARAMETER (DeviceObject);
        UNREFERENCED_PARAMETER (Irp);
        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;
        UNREFERENCED_PARAMETER (DeviceObject);
        UNREFERENCED_PARAMETER (Irp);
        return (ntStatus);

}

// ===================================================================
NTSTATUS
Test_Create(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    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);

    UNREFERENCED_PARAMETER (DeviceObject);

    return ntStatus;

}


