VOID
MpHwFreeAdapterResources(__in PVOID DeviceExtension)
{
    PLIST_ENTRY         pNextEntry;
    pHW_HBA_EXT         pLclHBAExt;
    KLOCK_QUEUE_HANDLE  LockHandle;
    KIRQL               lowest_assumed_irql = PASSIVE_LEVEL;
    pHW_HBA_EXT         pHBAExt = (pHW_HBA_EXT)DeviceExtension;

    KdPrint2(("PhDskMnt::MpHwFreeAdapterResources:  pHBAExt = 0x%p\n", pHBAExt));

    // Free memory allocated for disk
    ImScsiStopAdapter(pHBAExt, &lowest_assumed_irql);

    ImScsiAcquireLock(&pMPDrvInfoGlobal->DrvInfoLock, &LockHandle, lowest_assumed_irql);

    for (                                             // Go through linked list of HBA extensions.
        pNextEntry = pMPDrvInfoGlobal->ListMPHBAObj.Flink;
        pNextEntry != &pMPDrvInfoGlobal->ListMPHBAObj;
        pNextEntry = pNextEntry->Flink
        )
    {
        pLclHBAExt = CONTAINING_RECORD(pNextEntry, HW_HBA_EXT, List);

        if (pLclHBAExt == pHBAExt)
        {                    // Is this entry the same as pHBAExt?
            RemoveEntryList(pNextEntry);
            pMPDrvInfoGlobal->DrvInfoNbrMPHBAObj--;
            break;
        }
    }

    ImScsiReleaseLock(&LockHandle, &lowest_assumed_irql);

}                                                     // End MpHwFreeAdapterResources().
BOOLEAN
ImScsiVirtualDrivesPresent(__inout __deref PKIRQL LowestAssumedIrql)
{
    PLIST_ENTRY           list_ptr;
    BOOLEAN		  result = FALSE;
    KLOCK_QUEUE_HANDLE    LockHandle;

    ImScsiAcquireLock(                   // Serialize the linked list of HBA.
        &pMPDrvInfoGlobal->DrvInfoLock, &LockHandle, *LowestAssumedIrql);

    for (list_ptr = pMPDrvInfoGlobal->ListMPHBAObj.Flink;
        list_ptr != &pMPDrvInfoGlobal->ListMPHBAObj;
        list_ptr = list_ptr->Flink
        )
    {
        pHW_HBA_EXT pHBAExt;

        pHBAExt = CONTAINING_RECORD(list_ptr, HW_HBA_EXT, List);

        if (!IsListEmpty(&pHBAExt->LUList))
        {
            result = TRUE;
            break;
        }
    }

    ImScsiReleaseLock(&LockHandle, LowestAssumedIrql);

    return result;
}
LONG
ImScsiCompletePendingSrbs(
__in pHW_HBA_EXT pHBAExt,  // Adapter device-object extension from port driver.
__inout __deref PKIRQL LowestAssumedIrql
)
{
    pMP_WorkRtnParms pWkRtnParms;
    LONG done = 0;

    KdPrint2(("PhDskMnt::ImScsiCompletePendingSrbs start. pHBAExt = 0x%p\n", pHBAExt));

    for (;;)
    {
        KLOCK_QUEUE_HANDLE lock_handle;
        PLIST_ENTRY request;

        ImScsiAcquireLock(&pMPDrvInfoGlobal->ResponseListLock,
            &lock_handle, *LowestAssumedIrql);

        request = RemoveHeadList(&pMPDrvInfoGlobal->ResponseList);
        if (request == &pMPDrvInfoGlobal->ResponseList)
        {
            request = NULL;
        }

        ImScsiReleaseLock(&lock_handle, LowestAssumedIrql);

        if (request == NULL)
        {
            LONG was_pending = _InterlockedExchangeAdd((volatile LONG*)&pHBAExt->WorkItems, -done);
            KdPrint2(("PhDskMnt::ImScsiCompletePendingSrbs finished.\n"));
            return was_pending - done;
        }

        ++done;

        pWkRtnParms = (pMP_WorkRtnParms)CONTAINING_RECORD(request, MP_WorkRtnParms, ResponseListEntry);

        KdPrint2(("PhDskMnt::ImScsiCompletePendingSrbs: Completing pWkRtnParms = 0x%p, pSrb = 0x%p\n", pWkRtnParms, pWkRtnParms->pSrb));

        ScsiPortNotification(RequestComplete, pWkRtnParms->pHBAExt, pWkRtnParms->pSrb);
        ScsiPortNotification(NextRequest, pWkRtnParms->pHBAExt);

        ExFreePoolWithTag(pWkRtnParms, MP_TAG_GENERAL);      // Free parm list.
    }
}
NTSTATUS
ImScsiQueryAdapter(
__in            pHW_HBA_EXT                 pHBAExt,
__inout __deref PSRB_IMSCSI_QUERY_ADAPTER   data,
__in            ULONG                       max_length,
__inout __deref PKIRQL                      LowestAssumedIrql
)
{
    KLOCK_QUEUE_HANDLE    LockHandle;
    ULONG                 count;
    PLIST_ENTRY           list_ptr;

    KdPrint(("PhDskMnt::ImScsiQueryAdapter:  pHBAExt = 0x%p\n", pHBAExt));

    ImScsiAcquireLock(                   // Serialize the linked list of LUN extensions.              
        &pHBAExt->LUListLock, &LockHandle, *LowestAssumedIrql);

    for (count = 0, list_ptr = pHBAExt->LUList.Flink;
        list_ptr != &pHBAExt->LUList;
        count++, list_ptr = list_ptr->Flink
        )
    {
        pHW_LU_EXTENSION object;
        object = CONTAINING_RECORD(list_ptr, HW_LU_EXTENSION, List);

        if (max_length >= FIELD_OFFSET(SRB_IMSCSI_QUERY_ADAPTER, DeviceList[count]) + sizeof(data->DeviceList[count]))
            data->DeviceList[count] = object->DeviceNumber;
    }

    ImScsiReleaseLock(&LockHandle, LowestAssumedIrql);

    if (max_length >= FIELD_OFFSET(SRB_IMSCSI_QUERY_ADAPTER, NumberOfDevices) + sizeof(data->NumberOfDevices))
        data->NumberOfDevices = count;

    return STATUS_SUCCESS;
}
ULONG
MpHwFindAdapter(
__in       PVOID                           DeviceExtension,
__in       PVOID                           pReservedArg1,
__in       PVOID                           pReservedArg2,
#ifdef USE_STORPORT
__in       PVOID                           pReservedArg3,
#endif
__in       PCHAR                           ArgumentString,
__inout __deref PPORT_CONFIGURATION_INFORMATION pConfigInfo,
__out      PBOOLEAN                        pBAgain
)
{
    ULONG              i,
        len,
        status = SP_RETURN_FOUND;
    PCHAR              pChar;
    pHW_HBA_EXT        pHBAExt = (pHW_HBA_EXT)DeviceExtension;
    NTSTATUS           ntstatus;
    KLOCK_QUEUE_HANDLE LockHandle;
    KIRQL              lowest_assumed_irql = PASSIVE_LEVEL;

    UNREFERENCED_PARAMETER(pReservedArg1);
    UNREFERENCED_PARAMETER(pReservedArg2);
#ifdef USE_STORPORT
    UNREFERENCED_PARAMETER(pReservedArg3);
#endif
    UNREFERENCED_PARAMETER(ArgumentString);

    KdPrint(("PhDskMnt::MpHwFindAdapter: Arg=%s%s%s, pHBAExt = 0x%p, pConfigInfo = 0x%p, IRQL=%i\n",
        ArgumentString != NULL ? "\"" : "(",
        ArgumentString != NULL ? ArgumentString : "null",
        ArgumentString != NULL ? "\"" : ")",
        pHBAExt,
        pConfigInfo,
        KeGetCurrentIrql()));

#if VERBOSE_DEBUG_TRACE > 0

    if (!KD_DEBUGGER_NOT_PRESENT)
        DbgBreakPoint();

#endif

    if (pMPDrvInfoGlobal->GlobalsInitialized)
    {
        LARGE_INTEGER wait_time;

        DbgPrint("PhDskMnt::MpHwFindAdapter: Already initialized.\n");

        wait_time.QuadPart = -1000000;
        KeDelayExecutionThread(KernelMode, FALSE, &wait_time);
    }

    KeInitializeSpinLock(&pHBAExt->LUListLock);
    InitializeListHead(&pHBAExt->LUList);

    pHBAExt->HostTargetId = (UCHAR)pMPDrvInfoGlobal->MPRegInfo.InitiatorID;

    pConfigInfo->WmiDataProvider = FALSE;                       // Indicate WMI provider.

    pConfigInfo->Master = TRUE;

    pConfigInfo->NumberOfPhysicalBreaks = 4096;

    pConfigInfo->MaximumTransferLength = 8 << 20;                     // 8 MB.

#ifdef USE_STORPORT

    pConfigInfo->VirtualDevice = TRUE;                        // Indicate no real hardware.
    pConfigInfo->SynchronizationModel = StorSynchronizeFullDuplex;

    if (pConfigInfo->Dma64BitAddresses == SCSI_DMA64_SYSTEM_SUPPORTED)
        pConfigInfo->Dma64BitAddresses = SCSI_DMA64_MINIPORT_FULL64BIT_SUPPORTED;

#endif
#ifdef USE_SCSIPORT

    //if (pConfigInfo->NumberOfPhysicalBreaks == SP_UNINITIALIZED_VALUE)
    //    pConfigInfo->NumberOfPhysicalBreaks     = 4096;

    //if (pConfigInfo->MaximumTransferLength > (64 << 10))
    //    pConfigInfo->MaximumTransferLength      = 64 << 10;                     // 64 KB.

    pConfigInfo->Dma64BitAddresses = SCSI_DMA64_MINIPORT_SUPPORTED;

#endif

    pConfigInfo->AlignmentMask = 0x3;                         // Indicate DWORD alignment.
    pConfigInfo->CachesData = FALSE;                       // Indicate miniport wants flush and shutdown notification.
    pConfigInfo->MaximumNumberOfTargets = SCSI_MAXIMUM_TARGETS;        // Indicate maximum targets.
    pConfigInfo->NumberOfBuses =
        (UCHAR)pMPDrvInfoGlobal->MPRegInfo.NumberOfBuses;                     // Indicate number of buses.
    pConfigInfo->ScatterGather = TRUE;                        // Indicate scatter-gather (explicit setting needed for Win2003 at least).
    pConfigInfo->AutoRequestSense = TRUE;
    pConfigInfo->TaggedQueuing = TRUE;
    pConfigInfo->MultipleRequestPerLu = TRUE;

    // Save Vendor Id, Product Id, Revision in device extension.

    pChar = (PCHAR)pMPDrvInfoGlobal->MPRegInfo.VendorId.Buffer;
    len = min(8, (pMPDrvInfoGlobal->MPRegInfo.VendorId.Length / 2));
    for (i = 0; i < len; i++, pChar += 2)
        pHBAExt->VendorId[i] = *pChar;

    pChar = (PCHAR)pMPDrvInfoGlobal->MPRegInfo.ProductId.Buffer;
    len = min(16, (pMPDrvInfoGlobal->MPRegInfo.ProductId.Length / 2));
    for (i = 0; i < len; i++, pChar += 2)
        pHBAExt->ProductId[i] = *pChar;

    pChar = (PCHAR)pMPDrvInfoGlobal->MPRegInfo.ProductRevision.Buffer;
    len = min(4, (pMPDrvInfoGlobal->MPRegInfo.ProductRevision.Length / 2));
    for (i = 0; i < len; i++, pChar += 2)
        pHBAExt->ProductRevision[i] = *pChar;

    // Add HBA extension to master driver object's linked list.

    ImScsiAcquireLock(&pMPDrvInfoGlobal->DrvInfoLock, &LockHandle, lowest_assumed_irql);

    InsertTailList(&pMPDrvInfoGlobal->ListMPHBAObj, &pHBAExt->List);

    pMPDrvInfoGlobal->DrvInfoNbrMPHBAObj++;

    ImScsiReleaseLock(&LockHandle, &lowest_assumed_irql);

    if (!pMPDrvInfoGlobal->GlobalsInitialized)
    {
        HANDLE thread_handle;
        OBJECT_ATTRIBUTES object_attributes;

        KeInitializeSpinLock(&pMPDrvInfoGlobal->RequestListLock);
        InitializeListHead(&pMPDrvInfoGlobal->RequestList);
        KeInitializeEvent(&pMPDrvInfoGlobal->RequestEvent, SynchronizationEvent, FALSE);

#ifdef USE_SCSIPORT
        KeInitializeSpinLock(&pMPDrvInfoGlobal->ResponseListLock);
        KeInitializeEvent(&pMPDrvInfoGlobal->ResponseEvent, SynchronizationEvent, FALSE);
        InitializeListHead(&pMPDrvInfoGlobal->ResponseList);
#endif

        KeInitializeEvent(&pMPDrvInfoGlobal->StopWorker, NotificationEvent, FALSE);

        pMPDrvInfoGlobal->GlobalsInitialized = TRUE;

        InitializeObjectAttributes(&object_attributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);

        ntstatus = PsCreateSystemThread(
            &thread_handle,
            (ACCESS_MASK)0L,
            &object_attributes,
            NULL,
            NULL,
            ImScsiWorkerThread,
            NULL);

        if (!NT_SUCCESS(ntstatus))
        {
            DbgPrint("PhDskMnt::ScsiGetLUExtension: Cannot create worker thread. (%#x)\n", ntstatus);

            status = SP_RETURN_ERROR;
        }
        else
        {
            ntstatus = ObReferenceObjectByHandle(
                thread_handle,
                FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                *PsThreadType,
                KernelMode,
                (PVOID*)&pMPDrvInfoGlobal->WorkerThread,
                NULL
                );

            if (!NT_SUCCESS(ntstatus))
            {
                DbgPrint("PhDskMnt::ScsiGetLUExtension: Cannot reference worker thread. (%#x)\n", ntstatus);
                KeSetEvent(&pMPDrvInfoGlobal->StopWorker, (KPRIORITY)0, FALSE);
                ZwWaitForSingleObject(thread_handle, FALSE, NULL);

                status = SP_RETURN_ERROR;
            }

            ZwClose(thread_handle);
        }
    }

    //Done:
    *pBAgain = FALSE;

    KdPrint(("PhDskMnt::MpHwFindAdapter: End, status = 0x%X\n", status));

    return status;
}                                                     // End MpHwFindAdapter().
NTSTATUS
ImScsiRemoveDevice(
__in            pHW_HBA_EXT     pHBAExt,
__in            PDEVICE_NUMBER  DeviceNumber,
__inout __deref PKIRQL          LowestAssumedIrql
)
{
    PLIST_ENTRY             list_ptr;
    NTSTATUS                status;
    ULONG                   count = 0;
    KLOCK_QUEUE_HANDLE      LockHandle;
    UCHAR                   pathId = DeviceNumber->PathId;

    KdPrint(("PhDskMnt::ImScsiRemoveDevice: PathId=%i, TargetId=%i, Lun=%i.\n",
        (int)DeviceNumber->PathId, (int)DeviceNumber->TargetId, (int)DeviceNumber->Lun));

    ImScsiAcquireLock(                   // Serialize the linked list of LUN extensions.              
        &pHBAExt->LUListLock, &LockHandle, *LowestAssumedIrql);

    for (list_ptr = pHBAExt->LUList.Flink;
        list_ptr != &pHBAExt->LUList;
        list_ptr = list_ptr->Flink
        )
    {
        pHW_LU_EXTENSION object;
        object = CONTAINING_RECORD(list_ptr, HW_LU_EXTENSION, List);

        if ((DeviceNumber->LongNumber ==
            IMSCSI_ALL_DEVICES) |
            (object->DeviceNumber.LongNumber ==
            DeviceNumber->LongNumber))
        {
            count++;
            KeSetEvent(&object->StopThread, (KPRIORITY)0, FALSE);
            KeSetEvent(&object->RequestEvent, (KPRIORITY)0, FALSE);
        }
    }

    ImScsiReleaseLock(&LockHandle, LowestAssumedIrql);

    if (count == 0)
    {
        KdPrint(("PhDskMnt::ImScsiRemoveDevice: Non-existing device.\n"));
        status = STATUS_OBJECT_NAME_NOT_FOUND;
        goto Done;
    }

    KdPrint(("PhDskMnt::ImScsiRemoveDevice: Found %i device(s).\n", count));

    status = STATUS_SUCCESS;

    if (pathId == 0xFF)
        pathId = 0x00;

    StoragePortNotification(BusChangeDetected, pHBAExt, pathId);

Done:
    KdPrint2(("PhDskMnt::ImScsiRemoveDevice: End: status=0x%X, *Result=%i\n", status));

    return status;
}
VOID
ImScsiExtendDevice(
    __in pHW_HBA_EXT          pHBAExt,
    __in PSCSI_REQUEST_BLOCK  pSrb,
    __inout __deref PUCHAR         pResult,
    __inout __deref PKIRQL         LowestAssumedIrql,
    __inout __deref PSRB_IMSCSI_EXTEND_DEVICE       extend_device_data
    )
{
    UCHAR scsi_status;
    pHW_LU_EXTENSION device_extension;

    KdPrint(("ImScsi: Request to grow device %i:%i:%i by %I64i bytes.\n",
        (int)extend_device_data->DeviceNumber.PathId,
        (int)extend_device_data->DeviceNumber.TargetId,
        (int)extend_device_data->DeviceNumber.Lun,
        extend_device_data->ExtendSize.QuadPart));

    scsi_status = ScsiGetLUExtension(
        pHBAExt,
        &device_extension,
        extend_device_data->DeviceNumber.PathId,
        extend_device_data->DeviceNumber.TargetId,
        extend_device_data->DeviceNumber.Lun,
        LowestAssumedIrql
        );

    if ((scsi_status != SRB_STATUS_SUCCESS) | (device_extension == NULL))
    {
        extend_device_data->SrbIoControl.ReturnCode = (ULONG)STATUS_OBJECT_NAME_NOT_FOUND;
        ScsiSetSuccess(pSrb, pSrb->DataTransferLength);
        return;
    }

    if (device_extension->ReadOnly)
    {
        extend_device_data->SrbIoControl.ReturnCode = (ULONG)STATUS_MEDIA_WRITE_PROTECTED;
        ScsiSetSuccess(pSrb, pSrb->DataTransferLength);
        return;
    }

    pMP_WorkRtnParms pWkRtnParms =                                     // Allocate parm area for work routine.
        (pMP_WorkRtnParms)ExAllocatePoolWithTag(NonPagedPool, sizeof(MP_WorkRtnParms), MP_TAG_GENERAL);

    if (pWkRtnParms == NULL)
    {
        DbgPrint("ImScsi::ImScsiExtendDevice Failed to allocate work parm structure\n");

        ScsiSetCheckCondition(pSrb, SRB_STATUS_ERROR, SCSI_SENSE_HARDWARE_ERROR, SCSI_ADSENSE_NO_SENSE, 0);
        return;
    }

    RtlZeroMemory(pWkRtnParms, sizeof(MP_WorkRtnParms));

    pWkRtnParms->pHBAExt = pHBAExt;
    pWkRtnParms->pLUExt = device_extension;
    pWkRtnParms->pSrb = pSrb;

    KEVENT wait_event;
    BOOLEAN wait_result = KeGetCurrentIrql() < DISPATCH_LEVEL;

    if (wait_result)
    {
        KeInitializeEvent(&wait_event, EVENT_TYPE::NotificationEvent, FALSE);
        pWkRtnParms->CallerWaitEvent = &wait_event;
    }

    // Queue work item, which will run in the System process.
    KLOCK_QUEUE_HANDLE           lock_handle;

    KdPrint2(("PhDskMnt::ImScsiExtendDevice: Queuing work=0x%p\n", pWkRtnParms));

    ImScsiAcquireLock(&device_extension->RequestListLock, &lock_handle, *LowestAssumedIrql);

    InsertTailList(&device_extension->RequestList, &pWkRtnParms->RequestListEntry);

    ImScsiReleaseLock(&lock_handle, LowestAssumedIrql);

    KeSetEvent(&device_extension->RequestEvent, (KPRIORITY)0, FALSE);

    *pResult = ResultQueued;                          // Indicate queuing.

    if (wait_result)
    {
        KeWaitForSingleObject(&wait_event, KWAIT_REASON::Executive, MODE::KernelMode, FALSE, NULL);
    }

    StoragePortNotification(BusChangeDetected, pHBAExt, extend_device_data->DeviceNumber.PathId);

    return;
}
VOID
ImScsiCreateDevice(
__in pHW_HBA_EXT          pHBAExt,
__in PSCSI_REQUEST_BLOCK  pSrb,
__inout __deref PUCHAR         pResult,
__inout __deref PKIRQL         LowestAssumedIrql
)
{
    pHW_LU_EXTENSION        pLUExt = NULL;
    PSRB_IMSCSI_CREATE_DATA new_device = (PSRB_IMSCSI_CREATE_DATA)pSrb->DataBuffer;
    pMP_WorkRtnParms        pWkRtnParms;
    KLOCK_QUEUE_HANDLE      lock_handle;

    // If auto-selecting device number
    if (new_device->Fields.DeviceNumber.LongNumber == IMSCSI_AUTO_DEVICE_NUMBER)
    {
        KdPrint(("PhDskMnt::ImScsiCreateDevice: Auto-select device number.\n"));

        for (new_device->Fields.DeviceNumber.PathId = 0;
            new_device->Fields.DeviceNumber.PathId < pMPDrvInfoGlobal->MPRegInfo.NumberOfBuses;
            new_device->Fields.DeviceNumber.PathId++)
        {
            for (new_device->Fields.DeviceNumber.Lun = 0;
                new_device->Fields.DeviceNumber.Lun < MAX_LUNS;
                new_device->Fields.DeviceNumber.Lun++)
            {
                for (new_device->Fields.DeviceNumber.TargetId = 0;
                    new_device->Fields.DeviceNumber.TargetId < MAX_TARGETS;
                    new_device->Fields.DeviceNumber.TargetId++)
                {
#ifdef USE_SCSIPORT
                    // With SCSIPORT, reserve device 0:0:0 as control device
                    if (new_device->Fields.DeviceNumber.LongNumber == 0)
                        continue;
#endif

                    ScsiGetLUExtension(
                        pHBAExt,
                        &pLUExt,
                        new_device->Fields.DeviceNumber.PathId,
                        new_device->Fields.DeviceNumber.TargetId,
                        new_device->Fields.DeviceNumber.Lun,
                        LowestAssumedIrql
                        );

                    if (pLUExt == NULL)
                        break;
                }

                if (pLUExt == NULL)
                    break;
            }

            if (pLUExt == NULL)
                break;
        }

        if (pLUExt != NULL)
        {
            KdPrint(("PhDskMnt::ImScsiCreateDevice: No free device number found.\n"));
            new_device->SrbIoControl.ReturnCode = (ULONG)STATUS_NO_MORE_ENTRIES;
            ScsiSetSuccess(pSrb, pSrb->DataTransferLength);
            return;
        }

        KdPrint(("PhDskMnt::ImScsiCreateDevice: PathId=%i, TargetId=%i, Lun=%i.\n",
            (int)new_device->Fields.DeviceNumber.PathId,
            (int)new_device->Fields.DeviceNumber.TargetId,
            (int)new_device->Fields.DeviceNumber.Lun));
    }
    else
    {
        KdPrint(("PhDskMnt::ImScsiCreateDevice: PathId=%i, TargetId=%i, Lun=%i.\n",
            (int)new_device->Fields.DeviceNumber.PathId,
            (int)new_device->Fields.DeviceNumber.TargetId,
            (int)new_device->Fields.DeviceNumber.Lun));

#ifdef USE_SCSIPORT
        if (new_device->Fields.DeviceNumber.LongNumber == 0)
        {
            DbgPrint("PhDskMnt::ImScsiCreateDevice: Device number 0:0:0 is reserved.\n");
            new_device->SrbIoControl.ReturnCode = (ULONG)STATUS_OBJECT_NAME_COLLISION;
            ScsiSetSuccess(pSrb, pSrb->DataTransferLength);
            return;
        }
#endif

        ScsiGetLUExtension(
            pHBAExt,
            &pLUExt,
            new_device->Fields.DeviceNumber.PathId,
            new_device->Fields.DeviceNumber.TargetId,
            new_device->Fields.DeviceNumber.Lun,
            LowestAssumedIrql
            );

        if (pLUExt != NULL)
        {
            KdPrint(("PhDskMnt::ImScsiCreateDevice: Device already exists.\n"));
            new_device->SrbIoControl.ReturnCode = (ULONG)STATUS_OBJECT_NAME_COLLISION;
            ScsiSetSuccess(pSrb, pSrb->DataTransferLength);
            return;
        }
    }

    pWkRtnParms =                                     // Allocate parm area for work routine.
        (pMP_WorkRtnParms)ExAllocatePoolWithTag(NonPagedPool, sizeof(MP_WorkRtnParms), MP_TAG_GENERAL);

    if (pWkRtnParms == NULL)
    {
        DbgPrint("PhDskMnt::ImScsiCreateDevice Failed to allocate work parm structure\n");

        new_device->SrbIoControl.ReturnCode = (ULONG)STATUS_INSUFFICIENT_RESOURCES;
        ScsiSetSuccess(pSrb, pSrb->DataTransferLength);
        return;
    }

    RtlZeroMemory(pWkRtnParms, sizeof(MP_WorkRtnParms));

    pWkRtnParms->pHBAExt = pHBAExt;
    pWkRtnParms->pSrb = pSrb;
    pWkRtnParms->pReqThread = PsGetCurrentThread();

    ObReferenceObject(pWkRtnParms->pReqThread);

    // Queue work item, which will run in the System process.

    KdPrint2(("PhDskMnt::ImScsiCreateDevice: Queuing work=0x%p\n", pWkRtnParms));

    new_device->SrbIoControl.ReturnCode = (ULONG)STATUS_PENDING;

    ImScsiAcquireLock(&pMPDrvInfoGlobal->RequestListLock, &lock_handle, *LowestAssumedIrql);

    InsertTailList(&pMPDrvInfoGlobal->RequestList, &pWkRtnParms->RequestListEntry);

    ImScsiReleaseLock(&lock_handle, LowestAssumedIrql);

    KeSetEvent(&pMPDrvInfoGlobal->RequestEvent, (KPRIORITY)0, FALSE);

    *pResult = ResultQueued;                          // Indicate queuing.

    StoragePortNotification(BusChangeDetected, pHBAExt, new_device->Fields.DeviceNumber.PathId);

    KdPrint(("PhDskMnt::ImScsiCreateDevice: End: *Result=%i\n", *pResult));

    return;
}
VOID
ImScsiWorkerThread(__in PVOID Context)
{
    pHW_LU_EXTENSION            pLUExt = (pHW_LU_EXTENSION)Context;
    pMP_WorkRtnParms            pWkRtnParms = NULL;
    PLIST_ENTRY                 request_list = NULL;
    PKSPIN_LOCK                 request_list_lock = NULL;
    PKEVENT                     wait_objects[2] = { NULL };

    KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY);

    if (pLUExt != NULL)
    {
        KdPrint(("PhDskMnt::ImScsiWorkerThread: Device worker thread start. pLUExt = 0x%p\n",
            pLUExt));

        request_list = &pLUExt->RequestList;
        request_list_lock = &pLUExt->RequestListLock;
        wait_objects[0] = &pLUExt->RequestEvent;

        // If this is a VM backed disk that should be pre-loaded with an image file
        // we have to load the contents of that file now before entering the service
        // loop.
        if (pLUExt->VMDisk && (pLUExt->ImageFile != NULL))
            if (!ImScsiFillMemoryDisk(pLUExt))
                KeSetEvent(&pLUExt->StopThread, (KPRIORITY)0, FALSE);
    }
    else
    {
        KdPrint(("PhDskMnt::ImScsiWorkerThread: Global worker thread start. pLUExt=%p\n",
            pLUExt));

        request_list = &pMPDrvInfoGlobal->RequestList;
        request_list_lock = &pMPDrvInfoGlobal->RequestListLock;
        wait_objects[0] = &pMPDrvInfoGlobal->RequestEvent;
    }
    wait_objects[1] = &pMPDrvInfoGlobal->StopWorker;

    for (;;)
    {
        PLIST_ENTRY                 request;
        KLOCK_QUEUE_HANDLE          lock_handle;
        KIRQL                       lowest_assumed_irql = PASSIVE_LEVEL;

#ifdef USE_SCSIPORT

        NTSTATUS                    status = STATUS_SUCCESS;
        PIRP                        irp = NULL;

        ImScsiGetControllerObject();

        if (pMPDrvInfoGlobal->ControllerObject != NULL)
        {
            KdPrint2(("PhDskMnt::ImScsiWorkerThread: Pre-building IRP for next SMB_IMSCSI_CHECK.\n"));

            irp = ImScsiBuildCompletionIrp();
        }
#endif

        for (;;)
        {
            ImScsiAcquireLock(request_list_lock, &lock_handle, lowest_assumed_irql);

            request = RemoveHeadList(request_list);

            ImScsiReleaseLock(&lock_handle, &lowest_assumed_irql);

            if (request != request_list)
            {
                break;
            }

            if (KeReadStateEvent(&pMPDrvInfoGlobal->StopWorker) ||
                ((pLUExt != NULL) && (KeReadStateEvent(&pLUExt->StopThread))))
            {
                KdPrint(("PhDskMnt::ImScsiWorkerThread shutting down.\n"));

                if (pLUExt != NULL)
                {
                    KIRQL lowest_assumed_irql = PASSIVE_LEVEL;
                    ImScsiCleanupLU(pLUExt, &lowest_assumed_irql);
                }

#ifdef USE_SCSIPORT
                // One last SMB_IMSCSI_CHECK call to flush response queue and free allocated IRP
                if (irp != NULL)
                {
                    IoCallDriver(pMPDrvInfoGlobal->ControllerObject, irp);
                }
#endif

                PsTerminateSystemThread(STATUS_SUCCESS);
                return;
            }

            KdPrint2(("PhDskMnt::ImScsiWorkerThread idle, waiting for request.\n"));

            KeWaitForMultipleObjects(2, (PVOID*)wait_objects, WaitAny, Executive, KernelMode, FALSE, NULL, NULL);
        }

        pWkRtnParms = CONTAINING_RECORD(request, MP_WorkRtnParms, RequestListEntry);

        KdPrint2(("PhDskMnt::ImScsiWorkerThread got request. pWkRtnParms = 0x%p\n",
            pWkRtnParms));

        // Request to wait for LU worker thread to terminate
        if (pWkRtnParms->pSrb == NULL)
        {
            KdPrint(("PhDskMnt::ImScsiWorkerThread: Request to wait for LU worker thread to exit. pLUExt=%p\n",
                pWkRtnParms->pLUExt));

            if (pWkRtnParms->pLUExt->WorkerThread != NULL)
            {
                KeWaitForSingleObject(
                    pWkRtnParms->pLUExt->WorkerThread,
                    Executive,
                    KernelMode,
                    FALSE,
                    NULL);

                ObDereferenceObject(pWkRtnParms->pLUExt->WorkerThread);
                pWkRtnParms->pLUExt->WorkerThread = NULL;

                KdPrint(("PhDskMnt::ImScsiWorkerThread: Worker thread exit. Ready to free LUExt.\n"));
            }
            else
            {
                KdPrint(("PhDskMnt::ImScsiWorkerThread: Worker not started. Ready to free LUExt.\n"));
            }
            
            ExFreePoolWithTag(pWkRtnParms->pLUExt, MP_TAG_GENERAL);

            ExFreePoolWithTag(pWkRtnParms, MP_TAG_GENERAL);

            continue;
        }

        ImScsiDispatchWork(pWkRtnParms);

#ifdef USE_SCSIPORT

        KdPrint2(("PhDskMnt::ImScsiWorkerThread: Calling SMB_IMSCSI_CHECK for work: 0x%p.\n", pWkRtnParms));

        status = ImScsiCallForCompletion(irp, pWkRtnParms, &lowest_assumed_irql);

        if (!NT_SUCCESS(status))
            DbgPrint("PhDskMnt::ImScsiWorkerThread: IoCallDriver failed: 0x%X for work 0x%p\n", status, pWkRtnParms);
        else
            KdPrint2(("PhDskMnt::ImScsiWorkerThread: Finished SMB_IMSCSI_CHECK for work: 0x%p.\n", pWkRtnParms));

#endif

#ifdef USE_STORPORT

        KdPrint2(("PhDskMnt::ImScsiWorkerThread: Sending 'RequestComplete' to StorPort for work: 0x%p.\n", pWkRtnParms));

        StorPortNotification(RequestComplete, pWkRtnParms->pHBAExt, pWkRtnParms->pSrb);

        ExFreePoolWithTag(pWkRtnParms, MP_TAG_GENERAL);

        KdPrint2(("PhDskMnt::ImScsiWorkerThread: Finished work: 0x%p.\n", pWkRtnParms));

#endif

    }
}
VOID
ImScsiDispatchWork(
__in pMP_WorkRtnParms        pWkRtnParms
)
{
    pHW_HBA_EXT               pHBAExt = pWkRtnParms->pHBAExt;
    pHW_LU_EXTENSION          pLUExt = pWkRtnParms->pLUExt;
    PSCSI_REQUEST_BLOCK       pSrb = pWkRtnParms->pSrb;
    PETHREAD                  pReqThread = pWkRtnParms->pReqThread;
    PCDB                      pCdb = (PCDB)pSrb->Cdb;

    KdPrint2(("PhDskMnt::ImScsiDispatchWork Action: 0x%X, pSrb: 0x%p, pSrb->DataBuffer: 0x%p pSrb->DataTransferLength: 0x%X\n",
        (int)pSrb->Cdb[0], pSrb, pSrb->DataBuffer, pSrb->DataTransferLength));

    switch (pSrb->Function)
    {
    case SRB_FUNCTION_IO_CONTROL:
    {
        PSRB_IO_CONTROL srb_io_control = (PSRB_IO_CONTROL)pSrb->DataBuffer;

        switch (srb_io_control->ControlCode)
        {
        case SMP_IMSCSI_CREATE_DEVICE:
        {                       // Create new?
            KIRQL lowest_assumed_irql = PASSIVE_LEVEL;

            KdPrint(("PhDskMnt::ImScsiDispatchWork: Request to create new device.\n"));

            ImScsiCreateLU(pHBAExt, pSrb, pReqThread, &lowest_assumed_irql);

            ObDereferenceObject(pReqThread);
        }
        break;

        default:
            break;
        }
    }
    break;

    case SRB_FUNCTION_EXECUTE_SCSI:
        switch (pSrb->Cdb[0])
        {
        case SCSIOP_READ:
        case SCSIOP_WRITE:
        case SCSIOP_READ16:
        case SCSIOP_WRITE16:
        {
            // Read/write?
            PVOID sysaddress;
            PVOID buffer;
            ULONG status;
            LARGE_INTEGER startingSector;
            LARGE_INTEGER startingOffset;
            KLOCK_QUEUE_HANDLE LockHandle;
            KIRQL lowest_assumed_irql = PASSIVE_LEVEL;

            if ((pCdb->AsByte[0] == SCSIOP_READ16) |
                (pCdb->AsByte[0] == SCSIOP_WRITE16))
            {
                REVERSE_BYTES_QUAD(&startingSector, pCdb->CDB16.LogicalBlock);
            }
            else
            {
                startingSector.QuadPart = 0;
                REVERSE_BYTES(&startingSector, &pCdb->CDB10.LogicalBlockByte0);
            }

            startingOffset.QuadPart = startingSector.QuadPart << pLUExt->BlockPower;

            KdPrint2(("PhDskMnt::ImScsiDispatchWork starting sector: 0x%I64X\n", startingSector));

            status = StoragePortGetSystemAddress(pHBAExt, pSrb, &sysaddress);

            if ((status != STORAGE_STATUS_SUCCESS) | (sysaddress == NULL))
            {
                DbgPrint("PhDskMnt::ImScsiDispatchWork: StorPortGetSystemAddress failed: status=0x%X address=0x%p translated=0x%p\n",
                    status,
                    pSrb->DataBuffer,
                    sysaddress);

                pSrb->SrbStatus = SRB_STATUS_ERROR;
                pSrb->ScsiStatus = SCSISTAT_GOOD;

                break;
            }

            buffer = ExAllocatePoolWithTag(NonPagedPool, pSrb->DataTransferLength, MP_TAG_GENERAL);

            if (buffer == NULL)
            {
                DbgPrint("PhDskMnt::ImScsiDispatchWork: Memory allocation failed.\n");

                pSrb->SrbStatus = SRB_STATUS_ERROR;
                pSrb->ScsiStatus = SCSISTAT_GOOD;

                break;
            }
            else
            {
                NTSTATUS status = STATUS_NOT_IMPLEMENTED;

                /// For write operations, prepare temporary buffer
                if ((pSrb->Cdb[0] == SCSIOP_WRITE) | (pSrb->Cdb[0] == SCSIOP_WRITE16))
                {
                    RtlMoveMemory(buffer, sysaddress, pSrb->DataTransferLength);
                }

                if ((pSrb->Cdb[0] == SCSIOP_READ) | (pSrb->Cdb[0] == SCSIOP_READ16))
                {
                    status = ImScsiReadDevice(pLUExt, buffer, &startingOffset, &pSrb->DataTransferLength);
                }
                else if ((pSrb->Cdb[0] == SCSIOP_WRITE) | (pSrb->Cdb[0] == SCSIOP_WRITE16))
                {
                    status = ImScsiWriteDevice(pLUExt, buffer, &startingOffset, &pSrb->DataTransferLength);
                }

                if (!NT_SUCCESS(status))
                {
                    ExFreePoolWithTag(buffer, MP_TAG_GENERAL);

                    DbgPrint("PhDskMnt::ImScsiDispatchWork: I/O error status=0x%X\n", status);
                    switch (status)
                    {
                    case STATUS_INVALID_BUFFER_SIZE:
                    {
                        DbgPrint("PhDskMnt::ImScsiDispatchWork: STATUS_INVALID_BUFFER_SIZE from image I/O. Reporting SCSI_SENSE_ILLEGAL_REQUEST/SCSI_ADSENSE_INVALID_CDB/0x00.\n");
                        ScsiSetCheckCondition(
                            pSrb,
                            SRB_STATUS_ERROR,
                            SCSI_SENSE_ILLEGAL_REQUEST,
                            SCSI_ADSENSE_INVALID_CDB,
                            0);
                        break;
                    }
                    case STATUS_DEVICE_BUSY:
                    {
                        DbgPrint("PhDskMnt::ImScsiDispatchWork: STATUS_DEVICE_BUSY from image I/O. Reporting SRB_STATUS_BUSY/SCSI_SENSE_NOT_READY/SCSI_ADSENSE_LUN_NOT_READY/SCSI_SENSEQ_BECOMING_READY.\n");
                        ScsiSetCheckCondition(
                            pSrb,
                            SRB_STATUS_BUSY,
                            SCSI_SENSE_NOT_READY,
                            SCSI_ADSENSE_LUN_NOT_READY,
                            SCSI_SENSEQ_BECOMING_READY
                            );
                        break;
                    }
                    default:
                    {
                        ScsiSetError(pSrb, SRB_STATUS_PARITY_ERROR);
                        break;
                    }
                    }

                    break;
                }

                /// Fake random disk signature in case mounted read-only, 0xAA55 at end of mbr and 0x00000000 in disk id field.
                /// Compatibility fix for mounting Windows Backup vhd files in read-only.
                if ((pLUExt->FakeDiskSignature != 0) &&
                    ((pSrb->Cdb[0] == SCSIOP_READ) |
                    (pSrb->Cdb[0] == SCSIOP_READ16)) &&
                    (startingSector.QuadPart == 0) &&
                    (pSrb->DataTransferLength >= 512) &&
                    (pLUExt->ReadOnly))
                {
                    PUCHAR mbr = (PUCHAR)buffer;

                    if ((*(PUSHORT)(mbr + 0x01FE) == 0xAA55) &
                        (*(PUSHORT)(mbr + 0x01BC) == 0x0000) &
                        ((*(mbr + 0x01BE) & 0x7F) == 0x00) &
                        ((*(mbr + 0x01CE) & 0x7F) == 0x00) &
                        ((*(mbr + 0x01DE) & 0x7F) == 0x00) &
                        ((*(mbr + 0x01EE) & 0x7F) == 0x00) &
                        ((*(PULONG)(mbr + 0x01B8) == 0x00000000UL)))
                    {
                        DbgPrint("PhDskMnt::ImScsiDispatchWork: Faking disk signature as %#X.\n", pLUExt->FakeDiskSignature);

                        *(PULONG)(mbr + 0x01B8) = pLUExt->FakeDiskSignature;
                    }
                }

                /// For write operations, temporary buffer holds read data.
                /// Copy that to system buffer.
                if ((pSrb->Cdb[0] == SCSIOP_READ) | (pSrb->Cdb[0] == SCSIOP_READ16))
                {
                    RtlMoveMemory(sysaddress, buffer, pSrb->DataTransferLength);
                }
            }

            ImScsiAcquireLock(&pLUExt->LastIoLock, &LockHandle, lowest_assumed_irql);

            if (pLUExt->LastIoBuffer != NULL)
                ExFreePoolWithTag(pLUExt->LastIoBuffer, MP_TAG_GENERAL);

            pLUExt->LastIoStartSector = startingSector.QuadPart;
            pLUExt->LastIoLength = pSrb->DataTransferLength;
            pLUExt->LastIoBuffer = buffer;

            ImScsiReleaseLock(&LockHandle, &lowest_assumed_irql);

            ScsiSetSuccess(pSrb, pSrb->DataTransferLength);
        }
        break;

        default:
        {
            DbgPrint("PhDskMnt::ImScsiDispatchWork unknown function: 0x%X\n", (int)pSrb->Cdb[0]);

            ScsiSetError(pSrb, SRB_STATUS_INTERNAL_ERROR);
        }
        }

    default:
        break;
    }

    KdPrint2(("PhDskMnt::ImScsiDispatchWork: End pSrb: 0x%p.\n", pSrb));

}                                                     // End ImScsiDispatchWork().