/*
 * PROJECT:     ReactOS Universal Serial Bus Bulk Storage Driver
 * LICENSE:     GPL - See COPYING in the top level directory
 * FILE:        drivers/usb/usbstor/pdo.c
 * PURPOSE:     USB block storage device driver.
 * PROGRAMMERS:
 *              James Tabor
 *              Michael Martin (michael.martin@reactos.org)
 *              Johannes Anderwald (johannes.anderwald@reactos.org)
 */

#include "usbstor.h"

#define NDEBUG
#include <debug.h>

LPCSTR
USBSTOR_GetDeviceType(
    IN PUFI_INQUIRY_RESPONSE InquiryData,
    IN UCHAR IsFloppy)
{
    //
    // check if device type is zero
    //
    if (InquiryData->DeviceType == 0)
    {
        if (IsFloppy)
        {
            //
            // floppy device
            //
            return "SFloppy";
        }

        //
        // direct access device
        //
        return "Disk";
    }

    //
    // FIXME: use constant - derived from http://en.wikipedia.org/wiki/SCSI_Peripheral_Device_Type
    //
    switch (InquiryData->DeviceType)
    {
        case 1:
        {
            //
            // sequential device, i.e magnetic tape
            //
            return "Sequential";
        }
        case 4:
        {
            //
            // write once device
            //
            return "Worm";
        }
        case 5:
        {
            //
            // CDROM device
            //
            return "CdRom";
        }
        case 7:
        {
            //
            // optical memory device
            //
            return "Optical";
        }
        case 8:
        {
            //
            // medium change device
            //
            return "Changer";
        }
        default:
        {
            //
            // other device
            //
            return "Other";
        }
    }
}

LPCSTR
USBSTOR_GetGenericType(
    IN PUFI_INQUIRY_RESPONSE InquiryData,
    IN UCHAR IsFloppy)
{
    //
    // check if device type is zero
    //
    if (InquiryData->DeviceType == 0)
    {
        if (IsFloppy)
        {
            //
            // floppy device
            //
            return "GenSFloppy";
        }

        //
        // direct access device
        //
        return "GenDisk";
    }

    //
    // FIXME: use constant - derived from http://en.wikipedia.org/wiki/SCSI_Peripheral_Device_Type
    //
    switch (InquiryData->DeviceType)
    {
        case 1:
        {
            //
            // sequential device, i.e magnetic tape
            //
            return "GenSequential";
        }
        case 4:
        {
            //
            // write once device
            //
            return "GenWorm";
        }
        case 5:
        {
            //
            // CDROM device
            //
            return "GenCdRom";
        }
        case 7:
        {
            //
            // optical memory device
            //
            return "GenOptical";
        }
        case 8:
        {
            //
            // medium change device
            //
            return "GenChanger";
        }
        default:
        {
            //
            // other device
            //
            return "UsbstorOther";
        }
    }
}


ULONG
CopyField(
    IN PUCHAR Name,
    IN PCHAR Buffer,
    IN ULONG MaxLength)
{
    ULONG Index;

    for(Index = 0; Index < MaxLength; Index++)
    {
        if (Name[Index] <= ' ' || Name[Index] >= 0x7F /* last printable ascii character */ ||  Name[Index] == ',')
        {
            //
            // convert to underscore
            //
            Buffer[Index] = '_';
        }
        else
        {
            //
            // just copy character
            //
            Buffer[Index] = Name[Index];
        }
    }

    return MaxLength;
}

NTSTATUS
USBSTOR_PdoHandleQueryDeviceText(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    //PPDO_DEVICE_EXTENSION DeviceExtension;
    PIO_STACK_LOCATION IoStack;
    LPWSTR Buffer;
    static WCHAR DeviceText[] = L"USB Mass Storage Device";

    //
    // get current stack location
    //
    IoStack = IoGetCurrentIrpStackLocation(Irp);

    if (IoStack->Parameters.QueryDeviceText.DeviceTextType == DeviceTextDescription)
    {
        DPRINT("USBSTOR_PdoHandleQueryDeviceText DeviceTextDescription\n");

        //
        // allocate item
        //
        Buffer = (LPWSTR)AllocateItem(PagedPool, sizeof(DeviceText));
        if (!Buffer)
        {
            //
            // no memory
            //
            Irp->IoStatus.Information = 0;
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        //
        // copy buffer
        //
        wcscpy(Buffer, DeviceText);

        //
        // save result
        //
        Irp->IoStatus.Information = (ULONG_PTR)Buffer;
        return STATUS_SUCCESS;
    }
    else
    {
        DPRINT("USBSTOR_PdoHandleQueryDeviceText DeviceTextLocationInformation\n");

        //
        // allocate item
        //
        Buffer = (LPWSTR)AllocateItem(PagedPool, sizeof(DeviceText));
        if (!Buffer)
        {
            //
            // no memory
            //
            Irp->IoStatus.Information = 0;
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        //
        // copy buffer
        //
        wcscpy(Buffer, DeviceText);

        //
        // save result
        //
        Irp->IoStatus.Information = (ULONG_PTR)Buffer;
        return STATUS_SUCCESS;
    }

}


NTSTATUS
USBSTOR_PdoHandleQueryDeviceId(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    PPDO_DEVICE_EXTENSION DeviceExtension;
    NTSTATUS Status;
    CHAR Buffer[100];
    LPCSTR DeviceType;
    ULONG Offset = 0;
    PUFI_INQUIRY_RESPONSE InquiryData;
    ANSI_STRING AnsiString;
    UNICODE_STRING DeviceId;

    //
    // get device extension
    //
    DeviceExtension = (PPDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    //
    // sanity check
    //
    ASSERT(DeviceExtension->InquiryData);

    //
    // get inquiry data
    //
    InquiryData = (PUFI_INQUIRY_RESPONSE)DeviceExtension->InquiryData;

    //
    // get device type
    //
    DeviceType = USBSTOR_GetDeviceType(InquiryData, DeviceExtension->IsFloppy);

    //
    // zero buffer
    //
    RtlZeroMemory(Buffer, sizeof(Buffer));

    //
    // lets create device string
    //
    Offset = sprintf(&Buffer[Offset], "USBSTOR\\");
    Offset += sprintf(&Buffer[Offset], DeviceType);
    Offset += sprintf(&Buffer[Offset], "&Ven_");
    Offset += CopyField(InquiryData->Vendor, &Buffer[Offset], 8);
    Offset += sprintf(&Buffer[Offset], "&Prod_");
    Offset += CopyField(InquiryData->Product, &Buffer[Offset], 16);
    Offset += sprintf(&Buffer[Offset], "&Rev_");
    Offset += CopyField(InquiryData->Revision, &Buffer[Offset], 4);

    //
    // now initialize ansi string
    //
    RtlInitAnsiString(&AnsiString, (PCSZ)Buffer);

    //
    // allocate DeviceId string
    //
    DeviceId.Length = 0;
    DeviceId.MaximumLength = (strlen((PCHAR)Buffer) + 1) * sizeof(WCHAR);
    DeviceId.Buffer = (LPWSTR)AllocateItem(PagedPool, DeviceId.MaximumLength);
    if (!DeviceId.Buffer)
    {
        //
        // no memory
        //
        Irp->IoStatus.Information = 0;
        return STATUS_INSUFFICIENT_RESOURCES;
    }


    //
    // convert to unicode
    //
    Status = RtlAnsiStringToUnicodeString(&DeviceId, &AnsiString, FALSE);

    if (NT_SUCCESS(Status))
    {
        //
        // store result
        //
        Irp->IoStatus.Information = (ULONG_PTR)DeviceId.Buffer;
    }

    DPRINT("DeviceId %wZ Status %x\n", &DeviceId, Status);

    //
    // done
    //
    return Status;
}

VOID
USBSTOR_ConvertToUnicodeString(
    IN CHAR * Buffer,
    IN ULONG ResultBufferLength,
    IN ULONG ResultBufferOffset,
    OUT LPWSTR ResultBuffer,
    OUT PULONG NewResultBufferOffset)
{
    UNICODE_STRING DeviceString;
    ANSI_STRING AnsiString;
    NTSTATUS Status;

    ASSERT(ResultBufferLength);
    ASSERT(ResultBufferLength > ResultBufferOffset);

    DPRINT("ResultBufferOffset %lu ResultBufferLength %lu Buffer %s Length %lu\n", ResultBufferOffset, ResultBufferLength, Buffer, strlen(Buffer));

    //
    // construct destination string
    //
    DeviceString.Buffer = &ResultBuffer[ResultBufferOffset];
    DeviceString.Length = 0;
    DeviceString.MaximumLength = (ResultBufferLength - ResultBufferOffset) * sizeof(WCHAR);

    //
    // initialize source string
    //
    RtlInitAnsiString(&AnsiString, Buffer);

    //
    // convert to unicode
    //
    Status = RtlAnsiStringToUnicodeString(&DeviceString, &AnsiString, FALSE);
    ASSERT(Status == STATUS_SUCCESS);

    //
    // subtract consumed bytes
    //
    ResultBufferLength -= (DeviceString.Length + sizeof(WCHAR)) / sizeof(WCHAR);
    ResultBufferOffset += (DeviceString.Length + sizeof(WCHAR)) / sizeof(WCHAR);

    //
    // store new offset
    //
    *NewResultBufferOffset = ResultBufferOffset;
}



NTSTATUS
USBSTOR_PdoHandleQueryHardwareId(
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp)
{
    PPDO_DEVICE_EXTENSION PDODeviceExtension;
    PFDO_DEVICE_EXTENSION FDODeviceExtension;
    LPCSTR GenericType, DeviceType;
    LPWSTR Buffer;
    CHAR Id1[50], Id2[50], Id3[50], Id4[50], Id5[50], Id6[50];
    ULONG Id1Length, Id2Length, Id3Length, Id4Length, Id5Length,Id6Length;
    ULONG Offset, TotalLength, Length;
    PUFI_INQUIRY_RESPONSE InquiryData;

    //
    // get PDO device extension
    //
    PDODeviceExtension = (PPDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    //
    // get FDO device extension
    //
    FDODeviceExtension = (PFDO_DEVICE_EXTENSION)PDODeviceExtension->LowerDeviceObject->DeviceExtension;

    //
    // sanity check
    //
    ASSERT(FDODeviceExtension->DeviceDescriptor);

    //
    // get inquiry data
    //
    InquiryData = (PUFI_INQUIRY_RESPONSE)PDODeviceExtension->InquiryData;


    //
    // get device type and generic type
    //
    DeviceType = USBSTOR_GetDeviceType(InquiryData, PDODeviceExtension->IsFloppy);
    GenericType = USBSTOR_GetGenericType(InquiryData, PDODeviceExtension->IsFloppy);

    ASSERT(GenericType);

    //
    // generate id 1
    // USBSTOR\SCSIType_Vendor(8)_Product(16)_Revision(4)
    //
    RtlZeroMemory(Id1, sizeof(Id1));
    Offset = 0;
    Offset = sprintf(&Id1[Offset], "USBSTOR\\");
    Offset += sprintf(&Id1[Offset], DeviceType);
    Offset += CopyField(InquiryData->Vendor, &Id1[Offset], 8);
    Offset += CopyField(InquiryData->Product, &Id1[Offset], 16);
    Offset += CopyField(InquiryData->Revision, &Id1[Offset], 4);
    Id1Length = strlen(Id1) + 1;
    DPRINT("USBSTOR_PdoHandleQueryHardwareId HardwareId1 %s\n", Id1);

    //
    // generate id 2
    // USBSTOR\SCSIType_VENDOR(8)_Product(16)
    //
    RtlZeroMemory(Id2, sizeof(Id2));
    Offset = 0;
    Offset = sprintf(&Id2[Offset], "USBSTOR\\");
    Offset += sprintf(&Id2[Offset], DeviceType);
    Offset += CopyField(InquiryData->Vendor, &Id2[Offset], 8);
    Offset += CopyField(InquiryData->Product, &Id2[Offset], 16);
    Id2Length = strlen(Id2) + 1;
    DPRINT("USBSTOR_PdoHandleQueryHardwareId HardwareId2 %s\n", Id2);

    //
    // generate id 3
    // USBSTOR\SCSIType_VENDOR(8)
    //
    RtlZeroMemory(Id3, sizeof(Id3));
    Offset = 0;
    Offset = sprintf(&Id3[Offset], "USBSTOR\\");
    Offset += sprintf(&Id3[Offset], DeviceType);
    Offset += CopyField(InquiryData->Vendor, &Id3[Offset], 8);
    Id3Length = strlen(Id3) + 1;
    DPRINT("USBSTOR_PdoHandleQueryHardwareId HardwareId3 %s\n", Id3);

    //
    // generate id 4
    // USBSTOR\SCSIType_VENDOR(8)_Product(16)_Revision(1)
    //
    RtlZeroMemory(Id4, sizeof(Id4));
    Offset = 0;
    Offset = sprintf(&Id4[Offset], "USBSTOR\\");
    Offset += sprintf(&Id4[Offset], DeviceType);
    Offset += CopyField(InquiryData->Vendor, &Id4[Offset], 8);
    Offset += CopyField(InquiryData->Product, &Id4[Offset], 16);
    Offset += CopyField(InquiryData->Revision, &Id4[Offset], 1);
    Id4Length = strlen(Id4) + 1;
    DPRINT("USBSTOR_PdoHandleQueryHardwareId HardwareId4 %s\n", Id4);

    //
    // generate id 5
    // USBSTOR\SCSIType
    //
    RtlZeroMemory(Id5, sizeof(Id5));
    Offset = 0;
    Offset = sprintf(&Id5[Offset], "USBSTOR\\");
    Offset += sprintf(&Id5[Offset], GenericType);
    Id5Length = strlen(Id5) + 1;
    DPRINT("USBSTOR_PdoHandleQueryHardwareId HardwareId5 %s\n", Id5);

    //
    // generate id 6
    // SCSIType
    //
    RtlZeroMemory(Id6, sizeof(Id6));
    Offset = 0;
    Offset = sprintf(&Id6[Offset], GenericType);
    Id6Length = strlen(Id6) + 1;
    DPRINT("USBSTOR_PdoHandleQueryHardwareId HardwareId6 %s\n", Id6);

    //
    // compute total length
    //
    TotalLength = Id1Length + Id2Length + Id3Length + Id4Length + Id5Length + Id6Length + 1;

    //
    // allocate buffer
    //
    Buffer = (LPWSTR)AllocateItem(PagedPool, TotalLength * sizeof(WCHAR));
    if (!Buffer)
    {
        //
        // no memory
        //
        Irp->IoStatus.Information = 0;
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // reset offset
    //
    Offset = 0;
    Length = TotalLength;

    USBSTOR_ConvertToUnicodeString(Id1, Length, Offset, Buffer, &Offset);
    USBSTOR_ConvertToUnicodeString(Id2, Length, Offset, Buffer, &Offset);
    USBSTOR_ConvertToUnicodeString(Id3, Length, Offset, Buffer, &Offset);
    USBSTOR_ConvertToUnicodeString(Id4, Length, Offset, Buffer, &Offset);
    USBSTOR_ConvertToUnicodeString(Id5, Length, Offset, Buffer, &Offset);
    USBSTOR_ConvertToUnicodeString(Id6, Length, Offset, Buffer, &Offset);

    //
    // sanity check
    //
    ASSERT(Offset + 1 == Length);

    //
    // store result
    //
    Irp->IoStatus.Information = (ULONG_PTR)Buffer;

    //
    // done
    //
    return STATUS_SUCCESS;
}

NTSTATUS
USBSTOR_PdoHandleQueryCompatibleId(
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp)
{
    PPDO_DEVICE_EXTENSION PDODeviceExtension;
    PFDO_DEVICE_EXTENSION FDODeviceExtension;
    CHAR Buffer[100];
    ULONG Length, Offset;
    LPWSTR InstanceId;
    LPCSTR DeviceType;

    //
    // get PDO device extension
    //
    PDODeviceExtension = (PPDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    //
    // get FDO device extension
    //
    FDODeviceExtension = (PFDO_DEVICE_EXTENSION)PDODeviceExtension->LowerDeviceObject->DeviceExtension;

    //
    // sanity check
    //
    ASSERT(FDODeviceExtension->DeviceDescriptor);

    //
    // get target device type
    //
    DeviceType = USBSTOR_GetDeviceType((PUFI_INQUIRY_RESPONSE)PDODeviceExtension->InquiryData, PDODeviceExtension->IsFloppy);

    //
    // zero memory
    //
    RtlZeroMemory(Buffer, sizeof(Buffer));

    //
    // format instance id
    //
    Length = sprintf(Buffer, "USBSTOR\\%s", DeviceType) + 1;
    Length += sprintf(&Buffer[Length], "USBSTOR\\%s", "RAW") + 2;

    //
    // allocate instance id
    //
    InstanceId = (LPWSTR)AllocateItem(PagedPool, Length * sizeof(WCHAR));
    if (!InstanceId)
    {
        //
        // no memory
        //
        Irp->IoStatus.Information = 0;
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    USBSTOR_ConvertToUnicodeString(Buffer, Length, 0, InstanceId, &Offset);
    USBSTOR_ConvertToUnicodeString(&Buffer[Offset], Length, Offset, InstanceId, &Offset);

    DPRINT("USBSTOR_PdoHandleQueryCompatibleId %S\n", InstanceId);

    //
    // store result
    //
    Irp->IoStatus.Information = (ULONG_PTR)InstanceId;

    //
    // completed successfully
    //
    return STATUS_SUCCESS;
}

NTSTATUS
USBSTOR_PdoHandleQueryInstanceId(
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp)
{
    PPDO_DEVICE_EXTENSION PDODeviceExtension;
    PFDO_DEVICE_EXTENSION FDODeviceExtension;
    WCHAR Buffer[100];
    ULONG Length;
    LPWSTR InstanceId;

    //
    // get PDO device extension
    //
    PDODeviceExtension = (PPDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    //
    // get FDO device extension
    //
    FDODeviceExtension = (PFDO_DEVICE_EXTENSION)PDODeviceExtension->LowerDeviceObject->DeviceExtension;

    //
    // format instance id
    //
    if (FDODeviceExtension->SerialNumber)
    {
        //
        // using serial number from device
        //
        swprintf(Buffer, L"%s&%c", FDODeviceExtension->SerialNumber->bString, PDODeviceExtension->LUN);
    }
    else
    {
        //
        // use instance count and LUN
        //
        swprintf(Buffer, L"%04lu&%c", FDODeviceExtension->InstanceCount, PDODeviceExtension->LUN);
    }

    //
    // calculate length
    //
    Length = wcslen(Buffer) + 1;

    //
    // allocate instance id
    //
    InstanceId = (LPWSTR)AllocateItem(PagedPool, Length * sizeof(WCHAR));
    if (!InstanceId)
    {
        //
        // no memory
        //
        Irp->IoStatus.Information = 0;
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // copy instance id
    //
    wcscpy(InstanceId, Buffer);

    DPRINT("USBSTOR_PdoHandleQueryInstanceId %S\n", InstanceId);

    //
    // store result
    //
    Irp->IoStatus.Information = (ULONG_PTR)InstanceId;

    //
    // completed successfully
    //
    return STATUS_SUCCESS;
}

NTSTATUS
USBSTOR_PdoHandleDeviceRelations(
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp)
{
    PDEVICE_RELATIONS DeviceRelations;
    PIO_STACK_LOCATION IoStack;

    DPRINT("USBSTOR_PdoHandleDeviceRelations\n");

    //
    // get current irp stack location
    //
    IoStack = IoGetCurrentIrpStackLocation(Irp);

    //
    // check if relation type is BusRelations
    //
    if (IoStack->Parameters.QueryDeviceRelations.Type != TargetDeviceRelation)
    {
        //
        // PDO handles only target device relation
        //
        return Irp->IoStatus.Status;
    }

    //
    // allocate device relations
    //
    DeviceRelations = (PDEVICE_RELATIONS)AllocateItem(PagedPool, sizeof(DEVICE_RELATIONS));
    if (!DeviceRelations)
    {
        //
        // no memory
        //
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // initialize device relations
    //
    DeviceRelations->Count = 1;
    DeviceRelations->Objects[0] = DeviceObject;
    ObReferenceObject(DeviceObject);

    //
    // store result
    //
    Irp->IoStatus.Information = (ULONG_PTR)DeviceRelations;

    //
    // completed successfully
    //
    return STATUS_SUCCESS;
}


NTSTATUS
USBSTOR_PdoHandlePnp(
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp)
{
    PIO_STACK_LOCATION IoStack;
    PPDO_DEVICE_EXTENSION DeviceExtension;
    NTSTATUS Status;
    PDEVICE_CAPABILITIES Caps;
    ULONG bDelete;

    //
    // get current stack location
    //
    IoStack = IoGetCurrentIrpStackLocation(Irp);

    //
    // get device extension
    //
    DeviceExtension = (PPDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    //
    // sanity check
    //
    ASSERT(DeviceExtension->Common.IsFDO == FALSE);

    switch(IoStack->MinorFunction)
    {
       case IRP_MN_QUERY_DEVICE_RELATIONS:
       {
           Status = USBSTOR_PdoHandleDeviceRelations(DeviceObject, Irp);
           break;
       }
       case IRP_MN_QUERY_DEVICE_TEXT:
       {
           Status = USBSTOR_PdoHandleQueryDeviceText(DeviceObject, Irp);
           break;
       }
       case IRP_MN_QUERY_ID:
       {
           if (IoStack->Parameters.QueryId.IdType == BusQueryDeviceID)
           {
               //
               // handle query device id
               //
               Status = USBSTOR_PdoHandleQueryDeviceId(DeviceObject, Irp);
               break;
           }
           else if (IoStack->Parameters.QueryId.IdType == BusQueryHardwareIDs)
           {
               //
               // handle instance id
               //
               Status = USBSTOR_PdoHandleQueryHardwareId(DeviceObject, Irp);
               break;
           }
           else if (IoStack->Parameters.QueryId.IdType == BusQueryInstanceID)
           {
               //
               // handle instance id
               //
               Status = USBSTOR_PdoHandleQueryInstanceId(DeviceObject, Irp);
               break;
           }
           else if (IoStack->Parameters.QueryId.IdType == BusQueryCompatibleIDs)
           {
               //
               // handle instance id
               //
               Status = USBSTOR_PdoHandleQueryCompatibleId(DeviceObject, Irp);
               break;
           }

           DPRINT1("USBSTOR_PdoHandlePnp: IRP_MN_QUERY_ID IdType %x unimplemented\n", IoStack->Parameters.QueryId.IdType);
           Status = STATUS_NOT_SUPPORTED;
           Irp->IoStatus.Information = 0;
           break;
       }
       case IRP_MN_REMOVE_DEVICE:
       {
           DPRINT("IRP_MN_REMOVE_DEVICE\n");

           if(*DeviceExtension->PDODeviceObject != NULL)
           {
               //
               // clear entry in FDO pdo list
               //
               *DeviceExtension->PDODeviceObject = NULL;
               bDelete = TRUE;
           }
           else
           {
               //
               // device object already marked for deletion
               //
               bDelete = FALSE;
           }

           /* Complete the IRP */
           Irp->IoStatus.Status = STATUS_SUCCESS;
           IoCompleteRequest(Irp, IO_NO_INCREMENT);

           if (bDelete)
           {
               /* Delete the device object */
               IoDeleteDevice(DeviceObject);
           }
           return STATUS_SUCCESS;
       }
       case IRP_MN_QUERY_CAPABILITIES:
       {
           //
           // just forward irp to lower device
           //
           Status = USBSTOR_SyncForwardIrp(DeviceExtension->LowerDeviceObject, Irp);
           ASSERT(Status == STATUS_SUCCESS);

           if (NT_SUCCESS(Status))
           {
               //
               // check if no unique id
               //
               Caps = (PDEVICE_CAPABILITIES)IoStack->Parameters.DeviceCapabilities.Capabilities;
               Caps->UniqueID = FALSE; // no unique id is supported
               Caps->Removable = TRUE; //FIXME
           }
           break;
       }
       case IRP_MN_QUERY_REMOVE_DEVICE:
       case IRP_MN_QUERY_STOP_DEVICE:
       {
#if 0
           //
           // if we're not claimed it's ok
           //
           if (DeviceExtension->Claimed)
#else
           if (TRUE)
#endif
           {
               Status = STATUS_UNSUCCESSFUL;
               DPRINT1("[USBSTOR] Request %x fails because device is still claimed\n", IoStack->MinorFunction);
           }
           else
               Status = STATUS_SUCCESS;
           break;
       }
       case IRP_MN_START_DEVICE:
       {
           //
           // no-op for PDO
           //
           Status = STATUS_SUCCESS;
           break;
       }
       case IRP_MN_SURPRISE_REMOVAL:
       {
           Status = STATUS_SUCCESS;
           break;
       }
       default:
        {
            //
            // do nothing
            //
            Status = Irp->IoStatus.Status;
        }
    }

    //
    // complete request
    //
    if (Status != STATUS_PENDING)
    {
        //
        // store result
        //
        Irp->IoStatus.Status = Status;

        //
        // complete request
        //
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
    }

    //
    // done processing
    //
    return Status;
}

NTSTATUS
NTAPI
USBSTOR_CompletionRoutine(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Ctx)
{
    PKEVENT Event = (PKEVENT)Ctx;

    //
    // signal event
    //
    KeSetEvent(Event, 0, FALSE);
    return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS
USBSTOR_AllocateIrp(
    IN PDEVICE_OBJECT DeviceObject,
    IN ULONG DataTransferLength,
    IN UCHAR OpCode,
    IN PKEVENT Event,
    OUT PSCSI_REQUEST_BLOCK *OutRequest,
    OUT PIRP *OutIrp)
{
    PIRP Irp;
    PIO_STACK_LOCATION IoStack;
    PSCSI_REQUEST_BLOCK Request;
    PCDB pCDB;

    //
    // allocate irp
    //
    Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
    if (!Irp)
    {
        //
        // no memory
        //
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // get next stack location
    //
    IoStack = IoGetNextIrpStackLocation(Irp);

    //
    // create scsi block
    //
    Request = ExAllocatePoolWithTag(NonPagedPool,
                                    sizeof(SCSI_REQUEST_BLOCK),
                                    USB_STOR_TAG);
    if (!Request)
    {
        //
        // no memory
        //
        IoFreeIrp(Irp);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // init request
    //
    RtlZeroMemory(Request, sizeof(SCSI_REQUEST_BLOCK));

    //
    // allocate data transfer block
    //
    Request->DataBuffer = ExAllocatePoolWithTag(NonPagedPool,
                                                DataTransferLength,
                                                USB_STOR_TAG);
    if (!Request->DataBuffer)
    {
        //
        // no memory
        //
        IoFreeIrp(Irp);
        ExFreePoolWithTag(Request, USB_STOR_TAG);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // allocate MDL
    //
    Irp->MdlAddress = IoAllocateMdl(Request->DataBuffer, DataTransferLength, FALSE, FALSE, NULL);
    if (!Irp->MdlAddress)
    {
        //
        // no memory
        //
        IoFreeIrp(Irp);
        ExFreePoolWithTag(Request->DataBuffer, USB_STOR_TAG);
        ExFreePoolWithTag(Request, USB_STOR_TAG);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // non paged pool
    //
    MmBuildMdlForNonPagedPool(Irp->MdlAddress);

    //
    // init scsi block
    //
    Request->DataTransferLength = DataTransferLength;
    Request->Function = SRB_FUNCTION_EXECUTE_SCSI;
    Request->SrbFlags = SRB_FLAGS_DATA_IN;

    RtlZeroMemory(Request->DataBuffer, DataTransferLength);


    //
    // get SCSI command data block
    //
    pCDB = (PCDB)Request->Cdb;

    //
    // set op code
    //
    pCDB->AsByte[0] = OpCode;

    //
    // store result
    //
    IoStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    IoStack->Parameters.Others.Argument1 = Request;
    IoStack->DeviceObject = DeviceObject;

    //
    // init event
    //
    KeInitializeEvent(Event, NotificationEvent, FALSE);

    //
    // lets setup a completion routine
    //
    IoSetCompletionRoutine(Irp, USBSTOR_CompletionRoutine, (PVOID)Event, TRUE, TRUE, TRUE);

    //
    // output result
    //
    *OutIrp = Irp;
    *OutRequest = Request;
    return STATUS_SUCCESS;
}

NTSTATUS
USBSTOR_SendIrp(
    IN PDEVICE_OBJECT PDODeviceObject,
    IN ULONG DataTransferLength,
    IN UCHAR OpCode,
    OUT PVOID *OutData)
{
    NTSTATUS Status;
    PIRP Irp;
    KEVENT Event;
    PPDO_DEVICE_EXTENSION PDODeviceExtension;
    PSCSI_REQUEST_BLOCK Request;

    //
    // let's allocate an irp
    //
    Status = USBSTOR_AllocateIrp(PDODeviceObject, DataTransferLength, OpCode, &Event, &Request, &Irp);
    if (!NT_SUCCESS(Status))
    {
        //
        // failed
        //
        DPRINT1("[USBSTOR] Failed to build irp\n");
        return Status;
    }

    //
    // get device extension
    //
    PDODeviceExtension = (PPDO_DEVICE_EXTENSION)PDODeviceObject->DeviceExtension;

    //
    // send irp
    //
    ASSERT(Irp);
    ASSERT(PDODeviceExtension->LowerDeviceObject);
    Status = IoCallDriver(PDODeviceExtension->Self, Irp);

    if (Status == STATUS_PENDING)
    {
        //
        // wait for completion
        //
        KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
        Status = Irp->IoStatus.Status;
    }

    if (NT_SUCCESS(Status))
    {
        //
        // store result
        //
        *OutData = Request->DataBuffer;
    }
    else
    {
        //
        // free the data
        //
        ExFreePoolWithTag(Request->DataBuffer, USB_STOR_TAG);
        *OutData = NULL;
    }

    //
    // free resources
    //
    ExFreePoolWithTag(Request, USB_STOR_TAG);
    IoFreeMdl(Irp->MdlAddress);
    IoFreeIrp(Irp);
    return Status;
}

NTSTATUS
USBSTOR_SendInquiryIrp(
    IN PDEVICE_OBJECT PDODeviceObject)
{
    NTSTATUS Status;
    PPDO_DEVICE_EXTENSION PDODeviceExtension;
    PUFI_INQUIRY_RESPONSE Response;

    //
    // get device extension
    //
    PDODeviceExtension = (PPDO_DEVICE_EXTENSION)PDODeviceObject->DeviceExtension;

    //
    // send request
    //
    Status = USBSTOR_SendIrp(PDODeviceObject, sizeof(UFI_INQUIRY_RESPONSE), SCSIOP_INQUIRY, (PVOID*)&Response);
    if (!NT_SUCCESS(Status))
    {
        //
        // command failed
        //
        DPRINT1("USBSTOR_SendInquiryIrp Failed with %x\n", Status);
        return Status;
    }

    DPRINT1("Response %p\n", Response);
    DPRINT1("DeviceType %x\n", Response->DeviceType);
    DPRINT1("RMB %x\n", Response->RMB);
    DPRINT1("Version %x\n", Response->Version);
    DPRINT1("Format %x\n", Response->Format);
    DPRINT1("Length %x\n", Response->Length);
    DPRINT1("Reserved %p\n", Response->Reserved);
    DPRINT1("Vendor %c%c%c%c%c%c%c%c\n", Response->Vendor[0], Response->Vendor[1], Response->Vendor[2], Response->Vendor[3], Response->Vendor[4], Response->Vendor[5], Response->Vendor[6], Response->Vendor[7]);
    DPRINT1("Product %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n", Response->Product[0], Response->Product[1], Response->Product[2], Response->Product[3],
                                                          Response->Product[4], Response->Product[5], Response->Product[6], Response->Product[7],
                                                          Response->Product[8], Response->Product[9], Response->Product[10], Response->Product[11],
                                                          Response->Product[12], Response->Product[13], Response->Product[14], Response->Product[15]);

    DPRINT1("Revision %c%c%c%c\n", Response->Revision[0], Response->Revision[1], Response->Revision[2], Response->Revision[3]);

    //
    // store result
    //
    PDODeviceExtension->InquiryData = (PVOID)Response;
    return Status;
}

NTSTATUS
USBSTOR_SendFormatCapacityIrp(
    IN PDEVICE_OBJECT PDODeviceObject)
{
    NTSTATUS Status;
    PPDO_DEVICE_EXTENSION PDODeviceExtension;
    PUCHAR Response;

    //
    // get device extension
    //
    PDODeviceExtension = (PPDO_DEVICE_EXTENSION)PDODeviceObject->DeviceExtension;

    //
    // send request
    //
    Status = USBSTOR_SendIrp(PDODeviceObject, 0xFC, SCSIOP_READ_FORMATTED_CAPACITY, (PVOID*)&Response);
    if (!NT_SUCCESS(Status))
    {
        //
        // command failed
        //
        return Status;
    }

    //
    // check if its a floppy
    //
    PDODeviceExtension->IsFloppy = USBSTOR_IsFloppy(Response, 0xFC /*FIXME*/, &PDODeviceExtension->MediumTypeCode);

    //
    // free response
    //
    ExFreePoolWithTag(Response, USB_STOR_TAG);
    return Status;
}



NTSTATUS
USBSTOR_CreatePDO(
    IN PDEVICE_OBJECT DeviceObject,
    IN UCHAR LUN)
{
    PDEVICE_OBJECT PDO;
    NTSTATUS Status;
    PPDO_DEVICE_EXTENSION PDODeviceExtension;
    PUFI_INQUIRY_RESPONSE Response;
    PFDO_DEVICE_EXTENSION FDODeviceExtension;

    //
    // get device extension
    //
    FDODeviceExtension = (PFDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;


    //
    // create child device object
    //
    Status = IoCreateDevice(DeviceObject->DriverObject, sizeof(PDO_DEVICE_EXTENSION), NULL, FILE_DEVICE_MASS_STORAGE, FILE_AUTOGENERATED_DEVICE_NAME | FILE_DEVICE_SECURE_OPEN, FALSE, &PDO);
    if (!NT_SUCCESS(Status))
    {
        //
        // failed to create device
        //
        return Status;
    }

    //
    // patch the stack size
    //
    PDO->StackSize = DeviceObject->StackSize;

    //
    // get device extension
    //
    PDODeviceExtension = (PPDO_DEVICE_EXTENSION)PDO->DeviceExtension;

    //
    // initialize device extension
    //
    RtlZeroMemory(PDODeviceExtension, sizeof(PDO_DEVICE_EXTENSION));
    PDODeviceExtension->Common.IsFDO = FALSE;
    PDODeviceExtension->LowerDeviceObject = DeviceObject;
    PDODeviceExtension->PDODeviceObject = &FDODeviceExtension->ChildPDO[LUN];
    PDODeviceExtension->Self = PDO;
    PDODeviceExtension->LUN = LUN;

    //
    // set device flags
    //
    PDO->Flags |= DO_DIRECT_IO | DO_MAP_IO_BUFFER;

    //
    // device is initialized
    //
    PDO->Flags &= ~DO_DEVICE_INITIALIZING;

    //
    // output device object
    //
    FDODeviceExtension->ChildPDO[LUN] = PDO;

    //
    // send inquiry command by irp
    //
    Status = USBSTOR_SendInquiryIrp(PDO);
    ASSERT(Status == STATUS_SUCCESS);

    //
    // check response data
    //
    Response = (PUFI_INQUIRY_RESPONSE)PDODeviceExtension->InquiryData;
    ASSERT(Response);

    if (Response->DeviceType == 0)
    {
        //
        // check if it is a floppy
        //
        Status = USBSTOR_SendFormatCapacityIrp(PDO);

        //
        // display result
        //
        DPRINT1("[USBSTOR] Status %x IsFloppy %x MediumTypeCode %x\n", Status, PDODeviceExtension->IsFloppy, PDODeviceExtension->MediumTypeCode);

        //
        // failing command is non critical
        //
        Status = STATUS_SUCCESS;
    }

    //
    // done
    //
    return Status;
}
