BOOLEAN
MpHwStartIo(
            __in       pHW_HBA_EXT          pHBAExt,  // Adapter device-object extension from port driver.
            __in __out PSCSI_REQUEST_BLOCK  pSrb
           )
{
    UCHAR                     Result = ResultDone;
#ifdef USE_SCSIPORT
    UCHAR                     PathId = pSrb->PathId;
    UCHAR                     TargetId = pSrb->TargetId;
    UCHAR                     Lun = pSrb->Lun;
#endif

    KdPrint2(("PhDskMnt::MpHwStartIo:  pHBAExt = 0x%p, pSrb = 0x%p, Path=%i, Target=%i, Lun=%i, IRQL=%i\n",
        pHBAExt,
        pSrb,
        (int) pSrb->PathId,
        (int) pSrb->TargetId,
        (int) pSrb->Lun,
        KeGetCurrentIrql()));

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

    ImScsiCompletePendingSrbs(pHBAExt);

    _InterlockedExchangeAdd((volatile LONG *)&pHBAExt->SRBsSeen, 1);   // Bump count of SRBs encountered.

    // Next, if true, will cause port driver to remove the associated LUNs if, for example, devmgmt.msc is asked "scan for hardware changes."
    //if (pHBAExt->bDontReport)
    //{                       // Act as though the HBA/path is gone?
    //    pSrb->SrbStatus = SRB_STATUS_INVALID_LUN;
    //    goto done;
    //}

    switch (pSrb->Function)
    {
    case SRB_FUNCTION_IO_CONTROL:
        ScsiIoControl(pHBAExt, pSrb, &Result);
        break;

    case SRB_FUNCTION_EXECUTE_SCSI:
        ScsiExecute(pHBAExt, pSrb, &Result);
        break;

    case SRB_FUNCTION_RESET_LOGICAL_UNIT:
        DbgPrint("PhDskMnt::MpHwStartIo: SRB_FUNCTION_RESET_LOGICAL_UNIT.\n");
        pSrb->SrbStatus = ScsiResetLun(pHBAExt, pSrb);
        break;

    case SRB_FUNCTION_RESET_DEVICE:
        DbgPrint("PhDskMnt::MpHwStartIo: SRB_FUNCTION_RESET_DEVICE.\n");
        pSrb->SrbStatus = ScsiResetDevice(pHBAExt, pSrb);
        break;
            
    case SRB_FUNCTION_RESET_BUS:
        DbgPrint("PhDskMnt::MpHwStartIo: SRB_FUNCTION_RESET_BUS.\n");
        pSrb->SrbStatus = MpHwResetBus(pHBAExt, pSrb->PathId);
        break;
            
    case SRB_FUNCTION_PNP:                        
        MpHwHandlePnP(pHBAExt, (PSCSI_PNP_REQUEST_BLOCK)pSrb);
        break;

    case SRB_FUNCTION_POWER:                      
        KdPrint(("PhDskMnt::MpHwStartIo: SRB_FUNCTION_POWER.\n"));
        // Do nothing.
        pSrb->SrbStatus = SRB_STATUS_SUCCESS;

        break;

    case SRB_FUNCTION_SHUTDOWN:                   
        KdPrint(("PhDskMnt::MpHwStartIo: SRB_FUNCTION_SHUTDOWN.\n"));
        // Do nothing.
        pSrb->SrbStatus = SRB_STATUS_SUCCESS;

        break;

    default:
        KdPrint(("PhDskMnt::MpHwStartIo: Unknown pSrb Function = 0x%x\n", pSrb->Function));
        
        ScsiSetCheckCondition(pSrb, SRB_STATUS_ERROR, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ADSENSE_ILLEGAL_COMMAND, 0);

        break;

    } // switch (pSrb->Function)

    if (Result == ResultDone)
    {                         // Complete now?
        // Note:  A miniport with real hardware would not always be calling RequestComplete from HwScsiStartIo.  Rather,
        //        the miniport would typically be doing real I/O and would call RequestComplete only at the end of that
        //        real I/O, in its HwScsiInterrupt or in a DPC routine.

#ifdef USE_SCSIPORT
        KdPrint2(("PhDskMnt::MpHwStartIo sending 'RequestComplete', 'NextRequest' and 'NextLuRequest' to ScsiPort.\n"));
        ScsiPortNotification(RequestComplete, pHBAExt, pSrb);
        ScsiPortNotification(NextRequest, pHBAExt);
        ScsiPortNotification(NextLuRequest, pHBAExt, 0, 0, 0);
#endif
#ifdef USE_STORPORT
        KdPrint2(("PhDskMnt::MpHwStartIo sending 'RequestComplete' to port StorPort.\n"));
        StorPortNotification(RequestComplete, pHBAExt, pSrb);
#endif
    }
    else
    {
#ifdef USE_SCSIPORT
        _InterlockedExchangeAdd((volatile LONG*)&pHBAExt->WorkItems, 1);

        KdPrint2(("PhDskMnt::MpHwStartIo sending 'RequestTimerCall' and 'NextLuRequest' to ScsiPort.\n"));
        ScsiPortNotification(RequestTimerCall, pHBAExt, MpHwTimer, (ULONG) 1);
        ScsiPortNotification(NextLuRequest, pHBAExt, PathId, TargetId, Lun);
        ScsiPortNotification(NextLuRequest, pHBAExt, 0, 0, 0);
#endif
    }

    KdPrint2(("PhDskMnt::MpHwStartIo End.\n"));

    return TRUE;
}                                                     // End MpHwStartIo().
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;
}
BOOLEAN
MpHwStartIo(
__in                PVOID                DeviceExtension,  // Adapter device-object extension from port driver.
__inout __deref     PSCSI_REQUEST_BLOCK  pSrb
)
{
    KIRQL                   lowest_assumed_irql = PASSIVE_LEVEL;
    ResultType              Result = ResultDone;
    pHW_HBA_EXT             pHBAExt = (pHW_HBA_EXT)DeviceExtension;
#ifdef USE_SCSIPORT
    UCHAR                   PathId = pSrb->PathId;
    UCHAR                   TargetId = pSrb->TargetId;
    UCHAR                   Lun = pSrb->Lun;
#endif

    KdPrint2(("PhDskMnt::MpHwStartIo:  pHBAExt = 0x%p, pSrb = 0x%p, Path=%i, Target=%i, Lun=%i, IRQL=%i\n",
        pHBAExt,
        pSrb,
        (int)pSrb->PathId,
        (int)pSrb->TargetId,
        (int)pSrb->Lun,
        KeGetCurrentIrql()));

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

    ImScsiCompletePendingSrbs(pHBAExt, &lowest_assumed_irql);

    _InterlockedExchangeAdd((volatile LONG *)&pHBAExt->SRBsSeen, 1);   // Bump count of SRBs encountered.

    // Next, if true, will cause port driver to remove the associated LUNs if, for example, devmgmt.msc is asked "scan for hardware changes."
    //if (pHBAExt->bDontReport)
    //{                       // Act as though the HBA/path is gone?
    //    pSrb->SrbStatus = SRB_STATUS_NO_DEVICE;
    //    goto done;
    //}

    switch (pSrb->Function)
    {
    case SRB_FUNCTION_IO_CONTROL:
        ScsiIoControl(pHBAExt, pSrb, &Result, &lowest_assumed_irql);
        break;

    case SRB_FUNCTION_EXECUTE_SCSI:
        if (pSrb->Cdb[0] == SCSIOP_REPORT_LUNS)
        {
            ScsiOpReportLuns(pHBAExt, pSrb, &lowest_assumed_irql);
        }
        else
        {
            ScsiExecute(pHBAExt, pSrb, &Result, &lowest_assumed_irql);
        }
        break;

    case SRB_FUNCTION_RESET_LOGICAL_UNIT:
        DbgPrint("PhDskMnt::MpHwStartIo: SRB_FUNCTION_RESET_LOGICAL_UNIT.\n");
        pSrb->SrbStatus = ScsiResetLun(pHBAExt, pSrb);
        break;

    case SRB_FUNCTION_RESET_DEVICE:
        DbgPrint("PhDskMnt::MpHwStartIo: SRB_FUNCTION_RESET_DEVICE.\n");
        pSrb->SrbStatus = ScsiResetDevice(pHBAExt, pSrb);
        break;

    case SRB_FUNCTION_RESET_BUS:
        DbgPrint("PhDskMnt::MpHwStartIo: SRB_FUNCTION_RESET_BUS.\n");
        pSrb->SrbStatus = MpHwResetBus(pHBAExt, pSrb->PathId);
        break;

    case SRB_FUNCTION_PNP:
        ScsiPnP(pHBAExt, (PSCSI_PNP_REQUEST_BLOCK)pSrb, &lowest_assumed_irql);
        break;

    case SRB_FUNCTION_POWER:
        KdPrint(("PhDskMnt::MpHwStartIo: SRB_FUNCTION_POWER.\n"));
        // Do nothing.
        pSrb->SrbStatus = SRB_STATUS_SUCCESS;

        break;

    case SRB_FUNCTION_SHUTDOWN:
        KdPrint(("PhDskMnt::MpHwStartIo: SRB_FUNCTION_SHUTDOWN.\n"));
        // Do nothing.
        pSrb->SrbStatus = SRB_STATUS_SUCCESS;

        break;

    default:
        KdPrint(("PhDskMnt::MpHwStartIo: Unknown pSrb Function = 0x%X\n", pSrb->Function));

        //StorPortLogError(pHBAExt, pSrb, pSrb->PathId, pSrb->TargetId, pSrb->Lun, SP_PROTOCOL_ERROR, 0x0200 | pSrb->Cdb[0]);

        ScsiSetCheckCondition(pSrb, SRB_STATUS_ERROR, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ADSENSE_ILLEGAL_COMMAND, 0);

        break;

    } // switch (pSrb->Function)

    if (Result == ResultDone)
    {                         // Complete now?
#ifdef USE_SCSIPORT
        KdPrint2(("PhDskMnt::MpHwStartIo sending 'RequestComplete', 'NextRequest' and 'NextLuRequest' to ScsiPort.\n"));
        ScsiPortNotification(RequestComplete, pHBAExt, pSrb);
        ScsiPortNotification(NextRequest, pHBAExt);
        ScsiPortNotification(NextLuRequest, pHBAExt, 0, 0, 0);
#endif
#ifdef USE_STORPORT
        KdPrint2(("PhDskMnt::MpHwStartIo sending 'RequestComplete' to port StorPort.\n"));
        StorPortNotification(RequestComplete, pHBAExt, pSrb);
#endif
    }
    else
    {
#ifdef USE_SCSIPORT
        _InterlockedExchangeAdd((volatile LONG*)&pHBAExt->WorkItems, 1);

        KdPrint2(("PhDskMnt::MpHwStartIo sending 'RequestTimerCall' and 'NextLuRequest' to ScsiPort.\n"));
        ScsiPortNotification(RequestTimerCall, pHBAExt, MpHwTimer, (ULONG)1);
        ScsiPortNotification(NextLuRequest, pHBAExt, PathId, TargetId, Lun);
        ScsiPortNotification(NextLuRequest, pHBAExt, 0, 0, 0);
#endif
    }

    KdPrint2(("PhDskMnt::MpHwStartIo End.\n"));

    return TRUE;
}                                                     // End MpHwStartIo().
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().