Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

SCSI Over Net

4.98/5 (15 votes)
28 Mar 2018CPOL6 min read 15.8K  
This article shows you how to share builtin CD-ROM / HDD over network

Introduction

Traditionally, device stack for CD-ROM drive looks like this:

Image 1

CD-ROM class driver manages FDOs for CD-ROM devices, and SCSI bus driver manages FDO for bus device and its children - PDOs for CD-ROM devices. CD-ROM class driver sends requests to SCSI bus driver, which talks to hardware and returns response.

In order to share CD-ROM device over network we need to consider client and server parts. On server side, we will replace CD-ROM class driver with our own SCSI server driver, so device stack will look like this:

Image 2

On client side, we will replace SCSI bus driver with SCSI client driver, so device stack will look like this:

Image 3

On the client side, we will get requests from CD-ROM class driver and send them over network to the server side. On the server side, we will pass requests to SCSI bus driver and send response back to the client.

Image 4

Background

To manage driver, we need driver package - inf and sys files located in the same directory. Inf file contains device setup class GUID and device hardware ids that our driver can be installed for.

The functions to manage driver are located in Difxapi.h. I will not comment on different flags that can be passed to these functions, I just used DRIVER_PACKAGE_FORCE.

C++
DWORD DriverPackageInstall(
  _In_     PCTSTR            DriverPackageInfPath,
  _In_     DWORD             Flags,
  _In_opt_ PCINSTALLERINFO_W pInstallerInfo,
  _Out_    BOOL              *pNeedReboot
);

DriverPackageInstall:

  1. If driver is not preinstalled - preinstall driver
  2. If inf file matches some hardware ids of the devices (driver is compatible) - install driver for these devices (if another driver is installed for the device - it will be uninstalled automatically)
C++
DWORD DriverPackagePreinstall(
  _In_ PCTSTR DriverPackageInfPath,
  _In_ DWORD  Flags
);

DriverPackagePreinstall:

  1. Preinstall driver
C++
DWORD DriverPackageUninstall(
  _In_     PCTSTR            DriverPackageInfPath,
  _In_     DWORD             Flags,
  _In_opt_ PCINSTALLERINFO_W pInstallerInfo,
  _Out_    BOOL              *pNeedReboot
);

DriverPackageUninstall:

  1. If driver is installed for some devices - uninstall driver for these devices (if another compatible driver is preinstalled - it will be installed automatically for the device)
  2. deinstall driver

When we preinstall driver, the system displays warning dialog and then copies driver files to driver store. When we deinstall driver, the system deletes driver files from driver store.
When we install driver, the system copies driver files to system directories if they are not already copied.

Warning dialog looks like this:

Image 5

Other functions to manage driver are located in Newdev.h. These functions can be used to manage driver on a particular device basis. We will use DiInstallDevice function:

C++
BOOL DiInstallDevice(
  _In_opt_  HWND             hwndParent,
  _In_      HDEVINFO         DeviceInfoSet,
  _In_      PSP_DEVINFO_DATA DeviceInfoData,
  _In_opt_  PSP_DRVINFO_DATA DriverInfoData,
  _In_      DWORD            Flags,
  _Out_opt_ PBOOL            NeedReboot
);

DiInstallDevice:

  1. install driver for specified device (driver is already preinstalled). Previous driver (if any) gets uninstalled.

Using the Code

We will use the same virtual machine as server and client for test purposes.

  1. Brief description of inf files:

    Server side:
    Inf file name = scsiserver.inf
    Device setup class = CDROM
    Device hardware id = GenCdRom
    Device description = SCSI Server

    Client side:
    Inf file name = scsiclient.inf
    Device setup class = HDC
    Device hardware id = VirtualSCSIBus
    Device description = SCSI Client

  2. Process of program installation:

    On the server side, we need to preinstall server driver:

    C++
    #include <Windows.h>
    #include <Difxapi.h>
    #include <stdio.h>
    
    #pragma comment(lib, "Difxapi.lib")
    
    int main(int argc, char* argv[])
    {
        DWORD dw;
    
        // preinstall driver
        dw = DriverPackagePreinstallW(L"C:\\scsiserver.inf", DRIVER_PACKAGE_FORCE);
    
        if (dw == ERROR_SUCCESS) wprintf(L"Success\n");
        else wprintf(L"Failure\n");
    
        getchar();
        return 0;
    }

    On the client side, we need to add root-enumerated device, preinstall client driver and install client driver for created device:

    C++
    #include <Windows.h>
    #include <Devguid.h>
    #include <Setupapi.h>
    #include <Difxapi.h>
    #include <stdio.h>
    
    #pragma comment(lib, "Setupapi.lib")
    #pragma comment(lib, "Difxapi.lib")
    
    BOOL AddDevice(WCHAR *pHardwareId)
    {
        BOOL b, bSuccess;
        HDEVINFO hDevInfo;
        SP_DEVINFO_DATA DevInfoData;
    
        bSuccess = FALSE;
    
        hDevInfo = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_UNKNOWN, NULL);
    
        if (hDevInfo != INVALID_HANDLE_VALUE)
        {
            DevInfoData.cbSize = sizeof(DevInfoData);
    
            b = SetupDiCreateDeviceInfoW(hDevInfo, pHardwareId,
                &GUID_DEVCLASS_UNKNOWN, NULL, NULL, DICD_GENERATE_ID, &DevInfoData);
    
            if (b)
            {
                b = SetupDiSetDeviceRegistryPropertyW(hDevInfo, &DevInfoData, SPDRP_HARDWAREID,
                    (BYTE*)pHardwareId, (wcslen(pHardwareId) + 1) * sizeof(WCHAR));
    
                if (b)
                {
                    b = SetupDiCallClassInstaller(DIF_REGISTERDEVICE, hDevInfo, &DevInfoData);
    
                    if (b) bSuccess = TRUE;
                }
            }
    
            SetupDiDestroyDeviceInfoList(hDevInfo);
        }
    
        return bSuccess;
    }
    
    int main(int argc, char* argv[])
    {
        DWORD dw;
        BOOL b, bRebootRequired;
    
        // add root-enumerated device with Unknown setup class
        b = AddDevice(L"VirtualSCSIBus");
    
        if (b)
        {
            // preinstall and install driver (root-enumerated device setup class
            // will be ajusted according to driver)
            dw = DriverPackageInstallW(L"C:\\scsiclient.inf", DRIVER_PACKAGE_FORCE,
                 NULL, &bRebootRequired);
    
            if (dw == ERROR_SUCCESS)
            {
                wprintf(L"Success\n");
    
                if (bRebootRequired) wprintf(L"Reboot required\n");
            }
            else wprintf(L"Failure\n");
        }
        else wprintf(L"Failure\n");
    
        getchar();
        return 0;
    }
  3. Process of program uninstallation:

    On the server side, we need to deinstall server driver:

    C++
    #include <Windows.h>
    #include <Difxapi.h>
    #include <stdio.h>
    
    #pragma comment(lib, "Difxapi.lib")
    
    int main(int argc, char* argv[])
    {
        DWORD dw;
        BOOL bRebootRequired;
    
        // deinstall driver
        dw = DriverPackageUninstallW(L"C:\\scsiserver.inf", DRIVER_PACKAGE_FORCE,
             NULL, &bRebootRequired);
    
        if (dw == ERROR_SUCCESS)
        {
            wprintf(L"Success\n");
    
            if (bRebootRequired) wprintf(L"Reboot required\n");
        }
        else wprintf(L"Failure\n");
    
        getchar();
        return 0;
    }

    On the client side, we need to uninstall client driver for root-enumerated device, deinstall client driver and remove root-enumerated device.

    C++
    #include <Windows.h>
    #include <Setupapi.h>
    #include <Difxapi.h>
    #define INITGUID
    #include <Devpkey.h>
    #include <stdio.h>
    
    #pragma comment(lib, "Setupapi.lib")
    #pragma comment(lib, "Difxapi.lib")
    
    BOOL RemoveDevice(WCHAR *pHardwareId)
    {
        BOOL b, bSuccess;
        HDEVINFO hDevInfo;
        SP_DEVINFO_DATA DevInfoData;
        WCHAR PropertyBuffer[1000];
        SP_REMOVEDEVICE_PARAMS params;
        DWORD PropertyDataType, Index;
        GUID ClassGuid;
    
        bSuccess = FALSE;
    
        hDevInfo = SetupDiGetClassDevsW(NULL, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT);
    
        if (hDevInfo != INVALID_HANDLE_VALUE)
        {
            Index = 0;
            DevInfoData.cbSize = sizeof(DevInfoData);
    
            while (SetupDiEnumDeviceInfo(hDevInfo, Index, &DevInfoData))
            {
                if ((!SetupDiGetDevicePropertyW(hDevInfo, &DevInfoData, 
                     &DEVPKEY_Device_Class, &PropertyDataType, (PBYTE)&ClassGuid,
                    sizeof(GUID), NULL, 0)) || (PropertyDataType != DEVPROP_TYPE_GUID))
                {
                    if (GetLastError() == ERROR_NOT_FOUND)
                    {
                        /* device has Unknown setup class */
    
                        b = SetupDiGetDeviceRegistryPropertyW(hDevInfo, &DevInfoData, 
                            SPDRP_HARDWAREID, &PropertyDataType, (BYTE*)PropertyBuffer, 
                            sizeof(PropertyBuffer), NULL);
    
                        if (b)
                        {
                            if (!wcscmp(PropertyBuffer, pHardwareId))
                            {
                                params.ClassInstallHeader.cbSize = sizeof
                                                     (SP_CLASSINSTALL_HEADER);
                                params.ClassInstallHeader.InstallFunction = DIF_REMOVE;
                                params.Scope = DICS_FLAG_GLOBAL;
    
                                b = SetupDiSetClassInstallParamsW(hDevInfo, &DevInfoData, 
                                          &params.ClassInstallHeader, sizeof(params));
    
                                if (b)
                                {
                                    b = SetupDiCallClassInstaller
                                        (DIF_REMOVE, hDevInfo, &DevInfoData);
    
                                    if (b) bSuccess = TRUE;
                                }
    
                                break;
                            }
                        }
                    }
                }
    
                ++Index;
            }
    
            SetupDiDestroyDeviceInfoList(hDevInfo);
        }
    
        return bSuccess;
    }
    
    int main(int argc, char* argv[])
    {
        DWORD dw;
        BOOL b, bRebootRequired;
    
        // uninstall (root-enumerated device setup class will be ajusted back to Unknown) 
        // and deinstall driver
        dw = DriverPackageUninstallW(L"C:\\scsiclient.inf", 
                          DRIVER_PACKAGE_FORCE, NULL, &bRebootRequired);
    
        if (dw == ERROR_SUCCESS)
        {
            // remove root-enumerated device with Unknown setup class
            b = RemoveDevice(L"VirtualSCSIBus");
    
            if (b) wprintf(L"Success\n");
            else wprintf(L"Failure\n");
    
            if (bRebootRequired) wprintf(L"Reboot required\n");
        }
        else wprintf(L"Failure\n");
    
        getchar();
        return 0;
    }
  4. Process of program operation:

    On each side, we have { driver and user mode program } pair. On either side, we need to launch user mode program and enter "Connect" command. Once we get connected, on the server side, we will enter "List" command to see available CD-ROM devices (their hardware ids). Next, we will enter "Share" command followed by hardware id to start sharing process. On the server side, CD-ROM device will disappear from explorer and will show up on the client side.

    Client side:

    Image 6

    Server side:

    Image 7

    To stop device sharing, we need to enter "Unshare" command on the server side or disconnect on the client side. On the client side, CD-ROM device will dissapear and we will get it back on the server side.

    Client side:

    Image 8

    Server side:

    Image 9

    Windows sockets are used to transfer data over network. User mode code is designed as follows:

    1. "User thread" - This thread is dedicated to user commands (waits for them)
    2. "Receive thread" - This thread is responsible to recv REQUEST_HEADER structure which contains length of entire request and device tag (so we can tell what shared device this requested is directed to and how many bytes we need to recv next time)
    3. "Device thread" - Each shared device has its own thread which will send request, recv request (save for REQUEST_HEADER structure, it will be taken from "receive thread"), and interact with driver in the loop.

    The need for "receive thread" arises from the nature of TCP/IP. When we send data over network, byte sequencing is guaranteed to be the same on the other side. However, if we send data simultaneously from different threads (A and B), it is not guaranteed that buffer A will be sent before buffer B and vise versa. Also, when we recv data, we don't know how many bytes to expect - the only thing we can do is to supply our buffer size. It means that any number of sends on one side can result in any number of recvs on the other side. In order to walk around this, we recv REQUEST_HEADER first, then pass information to appropriate "device thread" and allow it to recv remaining data.

Server side code (truncated):

C++
#define IOCTL_GET_PNP_INFO               CTL_CODE(FILE_DEVICE_CONTROLLER, 
                                         0, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_EXECUTE_REQUEST            CTL_CODE(FILE_DEVICE_CONTROLLER, 
                                         1, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)

#define BUFFER_SIZE                      40 * 4096

#define OP_PLUGIN                        1
#define OP_PLUGOUT                       2
#define OP_REQUEST                       3

struct REQUEST_HEADER
{
    ULONG Id;
    ULONG Op;
    ULONG Irp[2];
    ULONG Length;
    ULONG MajorFunction;
    ULONG MinorFunction;
    ULONG IoControlCode;
    ULONG InputBufferLength;
    ULONG OutputBufferLength;
    NTSTATUS Status;
    ULONG Information;
};

struct DEVICE_CONTEXT
{
    LIST_ENTRY ListEntry;
    ULONG Id;
    HANDLE hThread;
    HANDLE hReceiveBeginEvent;
    HANDLE hReceiveEndEvent;
    REQUEST_HEADER* pHeader;
    REQUEST_HEADER* pBuffer;
    SOCKET Socket;
    BOOL End;
    WCHAR* pHardwareId;
};

struct RECEIVE_CONTEXT
{
    HANDLE hThread;
    SOCKET Socket;
    LIST_ENTRY DeviceList;
    CRITICAL_SECTION Section;
    REQUEST_HEADER Header;
    ULONG Id;
};

BOOL Send(DEVICE_CONTEXT *pDevContext)
{
    int iResult;

    iResult = send(pDevContext->Socket, 
    (CHAR*)pDevContext->pBuffer, pDevContext->pBuffer->Length, 0);

    return (iResult > 0);
}

BOOL Receive(RECEIVE_CONTEXT *pRecvContext)
{
    int iResult;

    iResult = recv(pRecvContext->Socket, 
    (CHAR*)&pRecvContext->Header, sizeof(REQUEST_HEADER), 0);

    return (iResult > 0);
}

BOOL Receive(DEVICE_CONTEXT *pDevContext)
{
    int iResult;
    ULONG Length;

    WaitForSingleObject(pDevContext->hReceiveBeginEvent, INFINITE);

    memcpy(pDevContext->pBuffer, pDevContext->pHeader, sizeof(REQUEST_HEADER));

    Length = pDevContext->pHeader->Length - sizeof(REQUEST_HEADER);

    if (Length) iResult = recv(pDevContext->Socket, 
    (CHAR*)((UCHAR*)pDevContext->pBuffer + sizeof(REQUEST_HEADER)), Length, 0);
    else iResult = 1;

    SetEvent(pDevContext->hReceiveEndEvent);

    return (iResult > 0);
}

void UnlockDeviceContext(RECEIVE_CONTEXT *pRecvContext, DEVICE_CONTEXT *pDevContext)
{
    pDevContext->pHeader = &pRecvContext->Header;

    SetEvent(pDevContext->hReceiveBeginEvent);

    WaitForSingleObject(pDevContext->hReceiveEndEvent, INFINITE);
}

void DeleteDeviceContext(DEVICE_CONTEXT *pDevContext)
{
    CloseHandle(pDevContext->hReceiveBeginEvent);

    CloseHandle(pDevContext->hReceiveEndEvent);

    CloseHandle(pDevContext->hThread);

    delete[] pDevContext->pHardwareId;

    delete[] (UCHAR*)pDevContext->pBuffer;

    delete pDevContext;
}

DEVICE_CONTEXT* CreateDeviceContext
(PTHREAD_START_ROUTINE StartRoutine, ULONG Id, SOCKET Socket, WCHAR *pHardwareId)
{
    DEVICE_CONTEXT *pDevContext;

    pDevContext = new DEVICE_CONTEXT;

    pDevContext->pBuffer = (REQUEST_HEADER*)new UCHAR[BUFFER_SIZE];

    pDevContext->hThread = CreateThread(NULL, 0, StartRoutine, 
                           pDevContext, CREATE_SUSPENDED, NULL);

    pDevContext->hReceiveBeginEvent = CreateEventW(NULL, FALSE, FALSE, NULL);

    pDevContext->hReceiveEndEvent = CreateEventW(NULL, FALSE, FALSE, NULL);

    pDevContext->Id = Id;
    pDevContext->Socket = Socket;
    pDevContext->End = FALSE;

    pDevContext->pHardwareId = new WCHAR[wcslen(pHardwareId) + 1];
    wcscpy(pDevContext->pHardwareId, pHardwareId);

    return pDevContext;
}

DWORD WINAPI DeviceThread(PVOID Parameter)
{
    HANDLE Handle;
    WCHAR *pDevicePath;
    OVERLAPPED Overlapped;
    DEVICE_CONTEXT *pDevContext;
    WCHAR *pDriverDescription;
    BOOL bNeedReboot;
    DWORD Bytes;

    pDevContext = (DEVICE_CONTEXT*)Parameter;

    pDriverDescription = GetInstalledDriver((GUID*)&GUID_DEVCLASS_CDROM, 
                         pDevContext->pHardwareId);

    InstallDriver((GUID*)&GUID_DEVCLASS_CDROM, 
    pDevContext->pHardwareId, L"SCSI Server", &bNeedReboot);

    Sleep(5000);        // we need to wait, otherwise it will not work properly

    pDevicePath = GetDevicePath(&g_ClassGuid, pDevContext->pHardwareId);

    Handle = CreateFileW(pDevicePath, GENERIC_READ | GENERIC_WRITE, 
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 
    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);

    DeviceIoControl(Handle, IOCTL_GET_PNP_INFO, NULL, 0, 
                    pDevContext->pBuffer, BUFFER_SIZE, NULL, NULL);

    pDevContext->pBuffer->Op = OP_PLUGIN;
    pDevContext->pBuffer->Id = pDevContext->Id;

    if (Send(pDevContext))
    {
        while (Receive(pDevContext))
        {
            if (pDevContext->pBuffer->Op == OP_PLUGOUT)
            {
                break;
            }
            else if (pDevContext->pBuffer->Op == OP_REQUEST)
            {
                if (pDevContext->End)
                {
                    pDevContext->pBuffer->Op = OP_PLUGOUT;
                    pDevContext->pBuffer->Length = sizeof(REQUEST_HEADER);

                    Send(pDevContext);
                    break;
                }
                else
                {
                    memset(&Overlapped, 0, sizeof(OVERLAPPED));

                    if (!DeviceIoControl(Handle, IOCTL_EXECUTE_REQUEST, NULL, 0, 
                    pDevContext->pBuffer, BUFFER_SIZE, NULL, &Overlapped))
                    {
                        /* ERROR_IO_PENDING */
                        GetOverlappedResultEx(Handle, &Overlapped, &Bytes, 3000, FALSE);
                    }

                    if (!Send(pDevContext)) break;
                }
            }
            else DbgRaiseAssertionFailure();
        }
    }

    CloseHandle(Handle);

    delete[] pDevicePath;

    Sleep(5000);        // we need to wait, otherwise it will not work properly

    InstallDriver((GUID*)&GUID_DEVCLASS_CDROM, 
    pDevContext->pHardwareId, pDriverDescription, &bNeedReboot);

    delete[] pDriverDescription;

    return 0;
}

DWORD WINAPI ReceiveThread(PVOID Parameter)
{
    DEVICE_CONTEXT *pDevContext;
    RECEIVE_CONTEXT *pRecvContext;

    pRecvContext = (RECEIVE_CONTEXT*)Parameter;

    while (Receive(pRecvContext))
    {
        pDevContext = FindDeviceContextById(pRecvContext, pRecvContext->Header.Id);

        if (pDevContext) UnlockDeviceContext(pRecvContext, pDevContext);
        else DbgRaiseAssertionFailure();
    }

    return 0;
}

DEVICE_CONTEXT* AddDeviceContext(RECEIVE_CONTEXT *pRecvContext, WCHAR *pHardwareId)
{
    DEVICE_CONTEXT *pDevContext;

    pDevContext = CreateDeviceContext(DeviceThread, 
    pRecvContext->Id++, pRecvContext->Socket, pHardwareId);

    EnterCriticalSection(&pRecvContext->Section);

    InsertToList(&pRecvContext->DeviceList, (LIST_ENTRY*)pDevContext);

    LeaveCriticalSection(&pRecvContext->Section);

    ResumeThread(pDevContext->hThread);

    return pDevContext;
}

void RemoveDeviceContext(RECEIVE_CONTEXT *pRecvContext, DEVICE_CONTEXT *pDevContext)
{
    EnterCriticalSection(&pRecvContext->Section);

    RemoveFromList((LIST_ENTRY*)pDevContext);

    LeaveCriticalSection(&pRecvContext->Section);

    DeleteDeviceContext(pDevContext);
}

RECEIVE_CONTEXT* CreateReceiveContext(SOCKET ServerSocket)
{
    RECEIVE_CONTEXT *pRecvContext;

    pRecvContext = new RECEIVE_CONTEXT;

    InitializeCriticalSection(&pRecvContext->Section);

    InitializeList(&pRecvContext->DeviceList);

    pRecvContext->Id = 0;
    pRecvContext->Socket = Accept(ServerSocket);

    pRecvContext->hThread = 
    CreateThread(NULL, 0, ReceiveThread, (PVOID)pRecvContext, CREATE_SUSPENDED, NULL);

    return pRecvContext;
}

void DeleteReceiveContext(RECEIVE_CONTEXT* pRecvContext)
{
    DEVICE_CONTEXT *pDevContext;

    while (pDevContext = GetFirstDeviceContext(pRecvContext))
    {
        pDevContext->End = TRUE;
        WaitForSingleObject(pDevContext->hThread, INFINITE);

        RemoveDeviceContext(pRecvContext, pDevContext);
    }

    Close(pRecvContext->Socket);

    WaitForSingleObject(pRecvContext->hThread, INFINITE);

    CloseHandle(pRecvContext->hThread);

    DeleteCriticalSection(&pRecvContext->Section);

    delete pRecvContext;
}

int main(int argc, char* argv[])
{
    WCHAR Command[200];
    SOCKET ServerSocket;
    RECEIVE_CONTEXT* pRecvContext;
    DEVICE_CONTEXT *pDevContext;

    ServerSocket = CreateServerSocket();
    pRecvContext = NULL;

    while (TRUE)
    {
        wscanf(L"%s", Command);

        if (!wcscmp(Command, L"Connect"))
        {
            if (!pRecvContext)
            {
                pRecvContext = CreateReceiveContext(ServerSocket);
                ResumeThread(pRecvContext->hThread);

                wprintf(L"Connected\n");
            }
        }
        else if (!wcscmp(Command, L"Disconnect"))
        {
            if (pRecvContext)
            {
                DeleteReceiveContext(pRecvContext);
                pRecvContext = NULL;

                wprintf(L"Disconnected\n");
            }
        }
        else if (!wcscmp(Command, L"List"))
        {
            PrintDevices();
        }
        else if (!wcscmp(Command, L"Exit"))
        {
            if (pRecvContext) DeleteReceiveContext(pRecvContext);

            break;
        }
        else if (!wcscmp(Command, L"Share"))
        {
            wscanf(L"%s", Command);

            AddDeviceContext(pRecvContext, Command);
        }
        else if (!wcscmp(Command, L"Unshare"))
        {
            wscanf(L"%s", Command);

            pDevContext = FindDeviceContextByHardwareId(pRecvContext, Command);

            if (pDevContext)
            {
                pDevContext->End = TRUE;
                WaitForSingleObject(pDevContext->hThread, INFINITE);

                RemoveDeviceContext(pRecvContext, pDevContext);
            }
        }
        else
        {
            wprintf(L"Unknown command\n");
        }
    }

    DeleteServerSocket(ServerSocket);

    return 0;
}

Client side code (truncated):

C++
#define IOCTL_PLUGIN_DEVICE    CTL_CODE(FILE_DEVICE_CONTROLLER, 0, 
                               METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_PLUGOUT_DEVICE   CTL_CODE(FILE_DEVICE_CONTROLLER, 1, 
                               METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_FETCH_REQUEST    CTL_CODE(FILE_DEVICE_CONTROLLER, 2, 
                               METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
#define IOCTL_COMPLETE_REQUEST CTL_CODE(FILE_DEVICE_CONTROLLER, 3, 
                               METHOD_IN_DIRECT, FILE_ANY_ACCESS)

#define BUFFER_SIZE                    40 * 4096

#define OP_PLUGIN                      1
#define OP_PLUGOUT                     2
#define OP_REQUEST                     3

struct REQUEST_HEADER
{
    ULONG Id;
    ULONG Op;
    ULONG Irp[2];
    ULONG Length;
    ULONG MajorFunction;
    ULONG MinorFunction;
    ULONG IoControlCode;
    ULONG InputBufferLength;
    ULONG OutputBufferLength;
    NTSTATUS Status;
    ULONG Information;
};

struct DEVICE_CONTEXT
{
    LIST_ENTRY ListEntry;
    ULONG Id;
    HANDLE hThread;
    HANDLE hReceiveBeginEvent;
    HANDLE hReceiveEndEvent;
    REQUEST_HEADER* pHeader;
    REQUEST_HEADER* pBuffer;
    SOCKET Socket;
    BOOL End;
};

struct RECEIVE_CONTEXT
{
    HANDLE hThread;
    SOCKET Socket;
    LIST_ENTRY DeviceList;
    CRITICAL_SECTION Section;
    REQUEST_HEADER Header;
};

void UnlockDeviceContext(RECEIVE_CONTEXT *pRecvContext, DEVICE_CONTEXT *pDevContext)
{
    pDevContext->pHeader = &pRecvContext->Header;

    SetEvent(pDevContext->hReceiveBeginEvent);

    WaitForSingleObject(pDevContext->hReceiveEndEvent, INFINITE);
}

void DeleteDeviceContext(DEVICE_CONTEXT *pDevContext)
{
    CloseHandle(pDevContext->hReceiveBeginEvent);

    CloseHandle(pDevContext->hReceiveEndEvent);

    CloseHandle(pDevContext->hThread);

    delete[] (UCHAR*)pDevContext->pBuffer;

    delete pDevContext;
}

DEVICE_CONTEXT* CreateDeviceContext
(PTHREAD_START_ROUTINE StartRoutine, ULONG Id, SOCKET Socket)
{
    DEVICE_CONTEXT *pDevContext;

    pDevContext = new DEVICE_CONTEXT;

    pDevContext->pBuffer = (REQUEST_HEADER*)new UCHAR[BUFFER_SIZE];

    pDevContext->hThread = CreateThread(NULL, 0, StartRoutine, pDevContext, CREATE_SUSPENDED, NULL);

    pDevContext->hReceiveBeginEvent = CreateEventW(NULL, FALSE, FALSE, NULL);

    pDevContext->hReceiveEndEvent = CreateEventW(NULL, FALSE, FALSE, NULL);

    pDevContext->Id = Id;
    pDevContext->Socket = Socket;
    pDevContext->End = FALSE;

    return pDevContext;
}

BOOL Send(DEVICE_CONTEXT *pDevContext)
{
    int iResult;

    iResult = send(pDevContext->Socket, 
    (CHAR*)pDevContext->pBuffer, pDevContext->pBuffer->Length, 0);

    return (iResult > 0);
}

BOOL Receive(DEVICE_CONTEXT *pDevContext)
{
    int iResult;
    ULONG Length;

    WaitForSingleObject(pDevContext->hReceiveBeginEvent, INFINITE);

    memcpy(pDevContext->pBuffer, pDevContext->pHeader, sizeof(REQUEST_HEADER));

    Length = pDevContext->pHeader->Length - sizeof(REQUEST_HEADER);

    if (Length) iResult = recv(pDevContext->Socket, 
    (CHAR*)((UCHAR*)pDevContext->pBuffer + sizeof(REQUEST_HEADER)), Length, 0);
    else iResult = 1;

    SetEvent(pDevContext->hReceiveEndEvent);

    return (iResult > 0);
}

BOOL Receive(RECEIVE_CONTEXT *pRecvContext)
{
    int iResult;

    iResult = recv(pRecvContext->Socket, 
    (CHAR*)&pRecvContext->Header, sizeof(REQUEST_HEADER), 0);

    return (iResult > 0);
}

DWORD WINAPI DeviceThread(PVOID Parameter)
{
    HANDLE Handle;
    WCHAR *pDevicePath;
    OVERLAPPED Overlapped;
    DEVICE_CONTEXT *pDevContext;
    DWORD Bytes;

    pDevContext = (DEVICE_CONTEXT*)Parameter;

    pDevicePath = GetDevicePath(&g_ClassGuid, L"VirtualSCSIBus");

    Handle = CreateFileW(pDevicePath, GENERIC_READ | GENERIC_WRITE, 
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, 
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);

    if (Receive(pDevContext))
    {
        if (pDevContext->pBuffer->Op == OP_PLUGIN)
        {
            DeviceIoControl(Handle, IOCTL_PLUGIN_DEVICE, 
            pDevContext->pBuffer, pDevContext->pBuffer->Length, NULL, 0, NULL, NULL);

            memset(&Overlapped, 0, sizeof(OVERLAPPED));

            if (!DeviceIoControl(Handle, IOCTL_FETCH_REQUEST, NULL, 0, 
            pDevContext->pBuffer, BUFFER_SIZE, NULL, &Overlapped))
            {
                /* ERROR_IO_PENDING */
                GetOverlappedResultEx(Handle, &Overlapped, &Bytes, 3000, FALSE);
            }

            pDevContext->pBuffer->Op = OP_REQUEST;

            if (Send(pDevContext))
            {
                while (Receive(pDevContext))
                {
                    if (pDevContext->pBuffer->Op == OP_PLUGOUT)
                    {
                        break;
                    }
                    else if (pDevContext->pBuffer->Op == OP_REQUEST)
                    {
                        if (pDevContext->End)
                        {
                            pDevContext->pBuffer->Op = OP_PLUGOUT;
                            pDevContext->pBuffer->Length = sizeof(REQUEST_HEADER);

                            Send(pDevContext);
                            break;
                        }
                        else
                        {
                            DeviceIoControl(Handle, IOCTL_COMPLETE_REQUEST, 
                            pDevContext->pBuffer, pDevContext->pBuffer->Length, NULL, 0, NULL, NULL);

                            memset(&Overlapped, 0, sizeof(OVERLAPPED));

                            if (!DeviceIoControl(Handle, IOCTL_FETCH_REQUEST, 
                            NULL, 0, pDevContext->pBuffer, BUFFER_SIZE, NULL, &Overlapped))
                            {
                                /* ERROR_IO_PENDING */
                                GetOverlappedResultEx(Handle, &Overlapped, &Bytes, 3000, FALSE);
                            }

                            if (!Send(pDevContext)) break;
                        }
                    }
                    else DbgRaiseAssertionFailure();
                }
            }

            DeviceIoControl(Handle, IOCTL_PLUGOUT_DEVICE, NULL, 0, NULL, 0, NULL, NULL);
        }
        else DbgRaiseAssertionFailure();
    }

    CloseHandle(Handle);

    delete[] pDevicePath;

    return 0;
}

DEVICE_CONTEXT* AddDeviceContext(RECEIVE_CONTEXT *pRecvContext, ULONG Id)
{
    DEVICE_CONTEXT *pDevContext;

    pDevContext = CreateDeviceContext(DeviceThread, Id, pRecvContext->Socket);

    EnterCriticalSection(&pRecvContext->Section);

    InsertToList(&pRecvContext->DeviceList, (LIST_ENTRY*)pDevContext);

    LeaveCriticalSection(&pRecvContext->Section);

    ResumeThread(pDevContext->hThread);

    return pDevContext;
}

void RemoveDeviceContext(RECEIVE_CONTEXT *pRecvContext, DEVICE_CONTEXT *pDevContext)
{
    EnterCriticalSection(&pRecvContext->Section);

    RemoveFromList((LIST_ENTRY*)pDevContext);

    LeaveCriticalSection(&pRecvContext->Section);

    DeleteDeviceContext(pDevContext);
}

DWORD WINAPI ReceiveThread(PVOID Parameter)
{
    DEVICE_CONTEXT *pDevContext;
    RECEIVE_CONTEXT *pRecvContext;

    pRecvContext = (RECEIVE_CONTEXT*)Parameter;

    while (Receive(pRecvContext))
    {
        switch (pRecvContext->Header.Op)
        {
        case OP_PLUGIN:
            pDevContext = AddDeviceContext(pRecvContext, pRecvContext->Header.Id);

            UnlockDeviceContext(pRecvContext, pDevContext);
            break;
        case OP_PLUGOUT:
            pDevContext = FindDeviceContextById(pRecvContext, pRecvContext->Header.Id);

            if (pDevContext)
            {
                UnlockDeviceContext(pRecvContext, pDevContext);

                WaitForSingleObject(pDevContext->hThread, INFINITE);

                RemoveDeviceContext(pRecvContext, pDevContext);
            }
            else DbgRaiseAssertionFailure();

            break;
        case OP_REQUEST:
            pDevContext = FindDeviceContextById(pRecvContext, pRecvContext->Header.Id);

            if (pDevContext) UnlockDeviceContext(pRecvContext, pDevContext);
            else DbgRaiseAssertionFailure();

            break;
        default:
            DbgRaiseAssertionFailure();
        }
    }

    return 0;
}

RECEIVE_CONTEXT* CreateReceiveContext()
{
    RECEIVE_CONTEXT *pRecvContext;

    pRecvContext = new RECEIVE_CONTEXT;

    InitializeCriticalSection(&pRecvContext->Section);

    InitializeList(&pRecvContext->DeviceList);

    pRecvContext->Socket = Connect();

    pRecvContext->hThread = 
    CreateThread(NULL, 0, ReceiveThread, (PVOID)pRecvContext, CREATE_SUSPENDED, NULL);

    return pRecvContext;
}

void DeleteReceiveContext(RECEIVE_CONTEXT* pRecvContext)
{
    DEVICE_CONTEXT *pDevContext;

    while (pDevContext = GetFirstDeviceContext(pRecvContext))
    {
        pDevContext->End = TRUE;
        WaitForSingleObject(pDevContext->hThread, INFINITE);

        RemoveDeviceContext(pRecvContext, pDevContext);
    }

    Disconnect(pRecvContext->Socket);

    WaitForSingleObject(pRecvContext->hThread, INFINITE);

    CloseHandle(pRecvContext->hThread);

    DeleteCriticalSection(&pRecvContext->Section);

    delete pRecvContext;
}

int main(int argc, char* argv[])
{
    WCHAR Command[200];
    RECEIVE_CONTEXT* pRecvContext;

    pRecvContext = NULL;

    while (TRUE)
    {
        wscanf(L"%s", Command);

        if (!wcscmp(Command, L"Connect"))
        {
            if (!pRecvContext)
            {
                pRecvContext = CreateReceiveContext();
                ResumeThread(pRecvContext->hThread);

                wprintf(L"Connected\n");
            }
        }
        else if (!wcscmp(Command, L"Disconnect"))
        {
            if (pRecvContext)
            {
                DeleteReceiveContext(pRecvContext);
                pRecvContext = NULL;

                wprintf(L"Disconnected\n");
            }
        }
        else if (!wcscmp(Command, L"Exit"))
        {
            if (pRecvContext) DeleteReceiveContext(pRecvContext);

            break;
        }
        else
        {
            wprintf(L"Unknown command\n");
        }
    }

    return 0;
}

Now let's turn our attention to kernel mode part. See this article on how to prepare guest OS for debugging.

To debug driver in Visual Studio, go to Driver -> Test -> Configure Computers...

Image 10

Select Add New Computer:

Image 11

Enter computer name (can be anything) and select Manually configure debuggers and do not provision.

Image 12

Enter these settings and click Next.

Image 13

And click Finish.

Now add both driver projects (client and server) to the solution. Launch guest OS and wait for OS selection window. In Visual Studio, go to Debug -> Attach to Process...

Image 14

Click Refresh if process is not in the list. After that, select Attach. Visual Studio will open Debugger Immediate Window:

Image 15

Now return to guest OS and choose boot entry with debugger enabled:

Image 16

Allow system to boot, go to Debug -> Break All:

Image 17

For client side driver, DriverEntry will be called during program installation or during system boot (afterwards). For server side, DriverEntry will be called when we initiate device sharing. After DriverEntry, driver's AddDevice routine will be called followed by a bunch of pnp requests.

For the module that is not loaded yet (server driver), we need to use unresolved breakpoint. Enter the following command:

C++
bu scsiserver!DispatchDeviceControl

For the module that is already loaded (client driver), we can use ordinary breakpoint. We can enter the following command:

C++
bp scsiclient!DispatchDeviceControl

Image 18

Or you can set breakpoint in code editor as usual. Note that corresponding code line must become red (sometimes, it does not happen immediately so we need to wait). Now go to Debug -> Continue. Initiate device sharing process as described above, our unresolved breakpoint will be hit (the corresponding code line will become yellow):

Image 19

Now you can follow the execution flow, set breakpoints on another dispatch routines, and debug both drivers. The request flow is as follows:

  1. start:
    {
        server: <code>IOCTL_GET_PNP_INFO</code> request from corresponding user mode program
        client: <code>IOCTL_PLUGIN_DEVICE</code> request for bus FDO from corresponding user mode program
        client: pnp requests from OS for newly created device PDO 
                to get information about it and to start it
    }
  2. loop:
    {
        client: <code>device io control</code> requests / <code>device internal io control</code> 
                request for device PDO from cdrom class driver
        client: <code>IOCTL_FETCH_REQUEST</code> request for bus FDO from 
                corresponding user mode program
        server: <code>IOCTL_EXECUTE_REQUEST</code> request from corresponding user mode program
        client: <code>IOCTL_COMPLETE_REQUEST</code> request for bus FDO from 
                corresponding user mode program
    }
  3. finish:
    {
        client: <code>IOCTL_PLUGOUT_DEVICE</code> request for bus FDO from corresponding user mode program
        client: <code>IRP_MN_QUERY_DEVICE_RELATIONS</code> request (bus relations) from OS for bus FDO
        client: <code>IRP_MN_SURPRISE_REMOVAL</code> request from OS for device PDO
        client: <code>IRP_MN_REMOVE_DEVICE</code> request from OS for device PDO
    }

Server side code (truncated):

C++
#define IOCTL_GET_PNP_INFO      CTL_CODE(FILE_DEVICE_CONTROLLER, 0, METHOD_BUFFERED,
                                FILE_ANY_ACCESS)
#define IOCTL_EXECUTE_REQUEST   CTL_CODE(FILE_DEVICE_CONTROLLER, 1, METHOD_OUT_DIRECT,
                                FILE_ANY_ACCESS)

struct PNP_ENTRY
{
    ULONG InfoOffset;
    ULONG InfoLength;
    NTSTATUS Status;
};

struct PNP_INFO
{
    PNP_ENTRY irp_mn_query_id[6];
    PNP_ENTRY irp_mn_query_device_text[2];
    PNP_ENTRY irp_mn_query_capabilities;
};

struct PACKED_SRB
{
    UCHAR                      Function;
    UCHAR                      SrbStatus;
    UCHAR                      ScsiStatus;
    UCHAR                      PathId;
    UCHAR                      TargetId;
    UCHAR                      Lun;
    UCHAR                      QueueTag;
    UCHAR                      QueueAction;
    UCHAR                      CdbLength;
    UCHAR                      SenseInfoBufferLength;
    ULONG                      SrbFlags;
    ULONG                      DataTransferLength;
    ULONG                      TimeOutValue;
    ULONG                       QueueSortKey;
    UCHAR                      Cdb[16];
};

struct REQUEST_HEADER
{
    ULONG Id;
    ULONG Op;
    ULONG Irp[2];
    ULONG Length;
    ULONG MajorFunction;
    ULONG MinorFunction;
    ULONG IoControlCode;
    ULONG InputBufferLength;
    ULONG OutputBufferLength;
    NTSTATUS Status;
    ULONG Information;
};

struct DEVICE_EXTENSION
{
    DEVICE_OBJECT *pDeviceObject;
    IO_REMOVE_LOCK RemoveLock;
    DEVICE_OBJECT *pLowerDeviceObject;
    UNICODE_STRING SymbolicLinkName;
    MDL *pMdl;
    ULONG PnpLength;
    PNP_INFO PnpInfo;
};

#define IRP_TO_LIST_ENTRY(_Irp)    ((LIST_ENTRY*)(((UCHAR*)_Irp) +
                                     FIELD_OFFSET(IRP, Tail.Overlay.ListEntry)))
#define IRP_FROM_LIST_ENTRY(_ListEntry)    ((IRP*)(((UCHAR*)_ListEntry) -
                                       FIELD_OFFSET(IRP, Tail.Overlay.ListEntry)))

struct PNP_CONTEXT
{
    KEVENT Event;
    DEVICE_EXTENSION *pDeviceExtension;
    IO_STACK_LOCATION Stack;
};

NTSTATUS AllocatePnpRequest(PNP_CONTEXT *pPnpContext, IRP **ppIrp)
{
    IRP *pIrp;
    NTSTATUS Status;
    DEVICE_OBJECT *pDeviceObject;
    IO_STACK_LOCATION *pStackDst, *pStackSrc;

    pDeviceObject = pPnpContext->pDeviceExtension->pLowerDeviceObject;
    pStackSrc = &pPnpContext->Stack;

    pIrp = IoAllocateIrp(pDeviceObject->StackSize, FALSE);

    if (pIrp)
    {
        pStackDst = IoGetNextIrpStackLocation(pIrp);

        pStackDst->DeviceObject = pDeviceObject;

        pStackDst->MajorFunction = IRP_MJ_PNP;
        pStackDst->MinorFunction = pStackSrc->MinorFunction;

        switch (pStackSrc->MinorFunction)
        {
        case IRP_MN_QUERY_CAPABILITIES:
            pStackDst->Parameters.DeviceCapabilities.Capabilities =
                          pStackSrc->Parameters.DeviceCapabilities.Capabilities;
            break;
        case IRP_MN_QUERY_ID:
            pStackDst->Parameters.QueryId.IdType = pStackSrc->Parameters.QueryId.IdType;
            break;
        case IRP_MN_QUERY_DEVICE_TEXT:
            pStackDst->Parameters.QueryDeviceText.DeviceTextType =
                          pStackSrc->Parameters.QueryDeviceText.DeviceTextType;
            pStackDst->Parameters.QueryDeviceText.LocaleId =
                          pStackSrc->Parameters.QueryDeviceText.LocaleId;
            break;
        default:
            DbgRaiseAssertionFailure();
            break;
        }

        *ppIrp = pIrp;
        Status = STATUS_SUCCESS;
    }
    else Status = STATUS_INSUFFICIENT_RESOURCES;

    return Status;
}

NTSTATUS IoCompletionRoutine2(DEVICE_OBJECT *pDeviceObject, IRP *pIrp, void *pContext)
{
    PNP_INFO *pPnpInfo;
    PNP_ENTRY *pPnpEntry;
    IO_STACK_LOCATION *pStack;
    BUS_QUERY_ID_TYPE BusQueryIdType;
    DEVICE_TEXT_TYPE DeviceTextType;
    DEVICE_EXTENSION *pDeviceExtension;
    PNP_CONTEXT *pPnpContext;
    WCHAR *pString;

    pPnpContext = (PNP_CONTEXT*)pContext;

    pStack = &pPnpContext->Stack;
    pDeviceExtension = pPnpContext->pDeviceExtension;
    pPnpInfo = &pDeviceExtension->PnpInfo;

    switch (pStack->MinorFunction)
    {
    case IRP_MN_QUERY_CAPABILITIES:
        pPnpEntry = &pPnpInfo->irp_mn_query_capabilities;

        pPnpEntry->InfoOffset = pDeviceExtension->PnpLength;
        pPnpEntry->InfoLength = sizeof(DEVICE_CAPABILITIES);

        memcpy((UCHAR*)pPnpInfo + pPnpEntry->InfoOffset,
              pStack->Parameters.DeviceCapabilities.Capabilities, pPnpEntry->InfoLength);
        pDeviceExtension->PnpLength += pPnpEntry->InfoLength;

        pPnpEntry->Status = pIrp->IoStatus.Status;
        break;
    case IRP_MN_QUERY_ID:
        BusQueryIdType = pStack->Parameters.QueryId.IdType;
        pPnpEntry = &pPnpInfo->irp_mn_query_id[BusQueryIdType];

        pString = (WCHAR*)pIrp->IoStatus.Information;

        if (pString)
        {
            pPnpEntry->InfoOffset = pDeviceExtension->PnpLength;

            if ((BusQueryIdType == BusQueryHardwareIDs) ||
                           (BusQueryIdType == BusQueryCompatibleIDs))
            {
                pPnpEntry->InfoLength = MultiStringSize(pString);
            }
            else
            {
                pPnpEntry->InfoLength = StringSize(pString);
            }

            memcpy((UCHAR*)pPnpInfo + pPnpEntry->InfoOffset, pString, pPnpEntry->InfoLength);
            pDeviceExtension->PnpLength += pPnpEntry->InfoLength;

            ExFreePool(pString);
        }

        pPnpEntry->Status = pIrp->IoStatus.Status;
        break;
    case IRP_MN_QUERY_DEVICE_TEXT:
        DeviceTextType = pStack->Parameters.QueryDeviceText.DeviceTextType;
        pPnpEntry = &pPnpInfo->irp_mn_query_device_text[DeviceTextType];

        pString = (WCHAR*)pIrp->IoStatus.Information;

        if (pString)
        {
            pPnpEntry->InfoOffset = pDeviceExtension->PnpLength;
            pPnpEntry->InfoLength = StringSize(pString);

            memcpy((UCHAR*)pPnpInfo + pPnpEntry->InfoOffset, pString, pPnpEntry->InfoLength);
            pDeviceExtension->PnpLength += pPnpEntry->InfoLength;

            ExFreePool(pString);
        }

        pPnpEntry->Status = pIrp->IoStatus.Status;
        break;
    default:
        DbgRaiseAssertionFailure();
        break;
    }

    IoFreeIrp(pIrp);

    KeSetEvent(&pPnpContext->Event, IO_NO_INCREMENT, FALSE);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS IoCompletionRoutine3(DEVICE_OBJECT *pDeviceObject, IRP *pIrp2, void *pContext)
{
    IRP *pIrp;
    REQUEST_HEADER *pHeader;
    DEVICE_EXTENSION *pDeviceExtension;
    PACKED_SRB *pPackedSrb;
    SCSI_REQUEST_BLOCK *pSrb;
    SCSI_PASS_THROUGH *pPassThrough;

    pIrp = (IRP*)pContext;

    pDeviceExtension = (DEVICE_EXTENSION*)pIrp->Tail.Overlay.DriverContext[0];

    pHeader = (REQUEST_HEADER*)MmGetSystemAddressForMdlSafe
              (pIrp->MdlAddress, NormalPagePriority);

    pHeader->Status = pIrp2->IoStatus.Status;
    pHeader->Information = pIrp2->IoStatus.Information;

    if (pHeader->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
    {
        pPackedSrb = (PACKED_SRB*)((UCHAR*)pHeader + sizeof(REQUEST_HEADER));
        pSrb = (SCSI_REQUEST_BLOCK*)((UCHAR*)pPackedSrb + (sizeof(PACKED_SRB) +
                pPackedSrb->DataTransferLength + pPackedSrb->SenseInfoBufferLength));

        pPackedSrb->SrbStatus = pSrb->SrbStatus;
        pPackedSrb->ScsiStatus = pSrb->ScsiStatus;

        pPackedSrb->SenseInfoBufferLength = pSrb->SenseInfoBufferLength;
        pPackedSrb->DataTransferLength = pSrb->DataTransferLength;

        pHeader->Length = sizeof(REQUEST_HEADER) + sizeof(PACKED_SRB);

        if (NT_SUCCESS(pHeader->Status))
        {
            if ((pSrb->DataTransferLength) && (pSrb->SrbFlags & SRB_FLAGS_DATA_IN))
            {
                pHeader->Length += pPackedSrb->DataTransferLength;
            }
        }
        else
        {
            if ((pSrb->SenseInfoBufferLength) &&
                         (pSrb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID))
            {
                pHeader->Length += pPackedSrb->SenseInfoBufferLength;

                if (pSrb->DataTransferLength)
                {
                    memmove((UCHAR*)pPackedSrb + sizeof(PACKED_SRB),
                       (UCHAR*)pPackedSrb + (sizeof(PACKED_SRB) +
                        pPackedSrb->DataTransferLength), pPackedSrb->SenseInfoBufferLength);
                }
            }
        }
    }
    else
    {
        pHeader->Length = sizeof(REQUEST_HEADER);

        if ((pHeader->IoControlCode == IOCTL_SCSI_GET_ADDRESS) ||
            (pHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY))
        {
            pHeader->Length += pHeader->Information;
        }
        else if (pHeader->IoControlCode == IOCTL_SCSI_PASS_THROUGH)
        {
            pPassThrough = (SCSI_PASS_THROUGH*)((UCHAR*)pHeader + sizeof(REQUEST_HEADER));

            if (NT_SUCCESS(pHeader->Status))
            {
                if ((pPassThrough->DataTransferLength) &&
                           (pPassThrough->DataIn == SCSI_IOCTL_DATA_IN))
                {
                    pHeader->Length += pPassThrough->DataBufferOffset +
                                           pPassThrough->DataTransferLength;
                }
                else pHeader->Length += sizeof(SCSI_PASS_THROUGH);
            }
            else
            {
                if (pPassThrough->SenseInfoLength)
                {
                    pHeader->Length += pPassThrough->SenseInfoOffset +
                                               pPassThrough->SenseInfoLength;
                }
                else pHeader->Length += sizeof(SCSI_PASS_THROUGH);
            }
        }
    }

    IoFreeIrp(pIrp2);

    CompleteRequest(pIrp, STATUS_SUCCESS, 0);
    IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS AllocateRequest(DEVICE_EXTENSION *pDeviceExtension, MDL *pMdl, IRP **ppIrp2)
{
    IRP *pIrp2;
    PVOID pBuffer;
    NTSTATUS Status;
    IO_STACK_LOCATION *pStack;
    SCSI_REQUEST_BLOCK *pSrb;
    PACKED_SRB *pPackedSrb;
    DEVICE_OBJECT *pDeviceObject;
    REQUEST_HEADER *pHeader;

    pHeader = (REQUEST_HEADER*)MmGetSystemAddressForMdlSafe(pMdl, NormalPagePriority);
    pDeviceObject = pDeviceExtension->pLowerDeviceObject;

    pIrp2 = IoAllocateIrp(pDeviceObject->StackSize, FALSE);

    if (pIrp2)
    {
        pStack = IoGetNextIrpStackLocation(pIrp2);

        pStack->DeviceObject = pDeviceObject;

        pStack->MajorFunction = pHeader->MajorFunction;
        pStack->MinorFunction = pHeader->MinorFunction;

        if (pHeader->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
        {
            pPackedSrb = (PACKED_SRB*)((UCHAR*)pHeader + sizeof(REQUEST_HEADER));
            pSrb = (SCSI_REQUEST_BLOCK*)((UCHAR*)pPackedSrb +
                       (sizeof(PACKED_SRB) + pPackedSrb->DataTransferLength +
                        pPackedSrb->SenseInfoBufferLength));

            pSrb->Length = sizeof(SCSI_REQUEST_BLOCK);
            pSrb->Function = pPackedSrb->Function;
            pSrb->SrbStatus = pPackedSrb->SrbStatus;
            pSrb->ScsiStatus = pPackedSrb->ScsiStatus;
            pSrb->PathId = pPackedSrb->PathId;
            pSrb->TargetId = pPackedSrb->TargetId;
            pSrb->Lun = pPackedSrb->Lun;
            pSrb->QueueTag = pPackedSrb->QueueTag;
            pSrb->QueueAction = pPackedSrb->QueueAction;
            pSrb->CdbLength = pPackedSrb->CdbLength;
            pSrb->SenseInfoBufferLength = pPackedSrb->SenseInfoBufferLength;
            pSrb->SrbFlags = pPackedSrb->SrbFlags;
            pSrb->DataTransferLength = pPackedSrb->DataTransferLength;
            pSrb->TimeOutValue = pPackedSrb->TimeOutValue;

            if (pPackedSrb->DataTransferLength)
            {
                pSrb->DataBuffer = (UCHAR*)pMdl->StartVa + pMdl->ByteOffset +
                                   (sizeof(REQUEST_HEADER) + sizeof(PACKED_SRB));

                IoBuildPartialMdl(pMdl, pDeviceExtension->pMdl, pSrb->DataBuffer,
                                   pPackedSrb->DataTransferLength);

                pIrp2->MdlAddress = pDeviceExtension->pMdl;
            }
            else pSrb->DataBuffer = NULL;

            if (pPackedSrb->SenseInfoBufferLength)
            {
                pSrb->SenseInfoBuffer = (UCHAR*)pPackedSrb + (sizeof(PACKED_SRB) +
                                         pPackedSrb->DataTransferLength);
            }
            else pSrb->SenseInfoBuffer = NULL;

            pSrb->NextSrb = NULL;
            pSrb->OriginalRequest = pIrp2;
            pSrb->SrbExtension = NULL;

            pSrb->QueueSortKey = pPackedSrb->QueueSortKey;
            memcpy(pSrb->Cdb, pPackedSrb->Cdb, sizeof(pSrb->Cdb));

            pStack->Parameters.Scsi.Srb = pSrb;
        }
        else
        {
            pStack->Parameters.DeviceIoControl.IoControlCode = pHeader->IoControlCode;

            pBuffer = (UCHAR*)pHeader + sizeof(REQUEST_HEADER);

            if (pHeader->IoControlCode == IOCTL_SCSI_GET_ADDRESS)
            {
                pStack->Parameters.DeviceIoControl.OutputBufferLength =
                                                pHeader->OutputBufferLength;
                pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
            }
            else if (pHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY)
            {
                pStack->Parameters.DeviceIoControl.InputBufferLength =
                                                 pHeader->InputBufferLength;
                pStack->Parameters.DeviceIoControl.OutputBufferLength =
                                                 pHeader->OutputBufferLength;
                pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
            }
            else if (pHeader->IoControlCode == IOCTL_STORAGE_ENABLE_IDLE_POWER)
            {
                pStack->Parameters.DeviceIoControl.InputBufferLength =
                                                   pHeader->InputBufferLength;
                pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
            }
            else if (pHeader->IoControlCode == IOCTL_SCSI_PASS_THROUGH)
            {
                pStack->Parameters.DeviceIoControl.InputBufferLength =
                                                      pHeader->InputBufferLength;
                pStack->Parameters.DeviceIoControl.OutputBufferLength =
                                                      pHeader->OutputBufferLength;
                pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
            }
        }

        *ppIrp2 = pIrp2;
        Status = STATUS_SUCCESS;
    }
    else Status = STATUS_INSUFFICIENT_RESOURCES;

    return Status;
}

NTSTATUS DispatchDeviceControl(DEVICE_OBJECT *pDeviceObject, IRP *pIrp)
{
    NTSTATUS Status;
    ULONG IoControlCode;
    IO_STACK_LOCATION *pStack;
    DEVICE_EXTENSION *pDeviceExtension;
    REQUEST_HEADER *pHeader;
    ULONG Information;
    IRP *pIrp2;

    Information = 0;

    pDeviceExtension = (DEVICE_EXTENSION*)pDeviceObject->DeviceExtension;
    pStack = IoGetCurrentIrpStackLocation(pIrp);

    Status = IoAcquireRemoveLock(&pDeviceExtension->RemoveLock, pIrp);

    if (NT_SUCCESS(Status))
    {
        IoControlCode = pStack->Parameters.DeviceIoControl.IoControlCode;

        if (IoControlCode == IOCTL_GET_PNP_INFO)
        {
            pHeader = (REQUEST_HEADER*)pIrp->AssociatedIrp.SystemBuffer;

            pHeader->Length = sizeof(REQUEST_HEADER)+pDeviceExtension->PnpLength;

            memcpy((UCHAR*)pHeader + sizeof(REQUEST_HEADER),
                       &pDeviceExtension->PnpInfo, pDeviceExtension->PnpLength);

            Information = pHeader->Length;
        }
        else if (IoControlCode == IOCTL_EXECUTE_REQUEST)
        {
            Status = AllocateRequest(pDeviceExtension, pIrp->MdlAddress, &pIrp2);

            if (NT_SUCCESS(Status))
            {
                pIrp->Tail.Overlay.DriverContext[0] = pDeviceExtension;

                IoSetCompletionRoutine(pIrp2, IoCompletionRoutine3, pIrp, TRUE, TRUE, TRUE);

                IoMarkIrpPending(pIrp);

                IoCallDriver(pDeviceExtension->pLowerDeviceObject, pIrp2);

                Status = STATUS_PENDING;
            }
        }
        else Status = STATUS_NOT_SUPPORTED;

        if (Status != STATUS_PENDING)
        {
            CompleteRequest(pIrp, Status, Information);
            IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
        }
    }
    else
    {
        Status = STATUS_DEVICE_DOES_NOT_EXIST;
        CompleteRequest(pIrp, Status, Information);
    }

    return Status;
}

NTSTATUS DispatchPnp(DEVICE_OBJECT *pDeviceObject, IRP *pIrp)
{
    NTSTATUS Status;
    IO_STACK_LOCATION *pStack;
    DEVICE_EXTENSION *pDeviceExtension;
    POWER_STATE PowerState;
    ULONG_PTR Information;
    KEVENT Event;

    Information = 0;

    pDeviceExtension = (DEVICE_EXTENSION*)pDeviceObject->DeviceExtension;
    pStack = IoGetCurrentIrpStackLocation(pIrp);

    Status = IoAcquireRemoveLock(&pDeviceExtension->RemoveLock, pIrp);

    if (NT_SUCCESS(Status))
    {
        switch (pStack->MinorFunction)
        {
        case IRP_MN_START_DEVICE:
            KeInitializeEvent(&Event, NotificationEvent, FALSE);

            IoCopyCurrentIrpStackLocationToNext(pIrp);

            IoSetCompletionRoutine(pIrp, IoCompletionRoutine, &Event, TRUE, TRUE, TRUE);

            Status = IoCallDriver(pDeviceExtension->pLowerDeviceObject, pIrp);

            if (Status == STATUS_PENDING) KeWaitForSingleObject
                             (&Event, Executive, KernelMode, FALSE, NULL);

            Status = pIrp->IoStatus.Status;
            Information = pIrp->IoStatus.Information;

            if (NT_SUCCESS(Status))
            {
                Information = 0;

                Status = IoSetDeviceInterfaceState(&pDeviceExtension->SymbolicLinkName, TRUE);

                if (NT_SUCCESS(Status))
                {
                    PowerState.DeviceState = PowerDeviceD0;
                    PoSetPowerState(pDeviceObject, DevicePowerState, PowerState);
                }
            }

            CompleteRequest(pIrp, Status, Information);

            IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
            break;
        case IRP_MN_REMOVE_DEVICE:
            PowerState.DeviceState = PowerDeviceD3;
            PoSetPowerState(pDeviceObject, DevicePowerState, PowerState);

            pIrp->IoStatus.Status = Status;
            pIrp->IoStatus.Information = Information;

            IoSkipCurrentIrpStackLocation(pIrp);
            Status = IoCallDriver(pDeviceExtension->pLowerDeviceObject, pIrp);

            IoReleaseRemoveLockAndWait(&pDeviceExtension->RemoveLock, pIrp);

            IoSetDeviceInterfaceState(&pDeviceExtension->SymbolicLinkName, FALSE);

            RtlFreeUnicodeString(&pDeviceExtension->SymbolicLinkName);

            IoFreeMdl(pDeviceExtension->pMdl);
            IoDetachDevice(pDeviceExtension->pLowerDeviceObject);
            IoDeleteDevice(pDeviceObject);
            break;
        case IRP_MN_SURPRISE_REMOVAL:
        case IRP_MN_QUERY_REMOVE_DEVICE:
        case IRP_MN_CANCEL_REMOVE_DEVICE:
        case IRP_MN_QUERY_STOP_DEVICE:
        case IRP_MN_CANCEL_STOP_DEVICE:
        case IRP_MN_STOP_DEVICE:
            pIrp->IoStatus.Status = Status;
            pIrp->IoStatus.Information = Information;

            IoSkipCurrentIrpStackLocation(pIrp);
            Status = IoCallDriver(pDeviceExtension->pLowerDeviceObject, pIrp);

            IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
            break;
        default:
            Status = STATUS_NOT_SUPPORTED;

            pIrp->IoStatus.Status = Status;
            pIrp->IoStatus.Information = Information;

            IoSkipCurrentIrpStackLocation(pIrp);
            Status = IoCallDriver(pDeviceExtension->pLowerDeviceObject, pIrp);

            IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
            break;
        }
    }
    else
    {
        Status = STATUS_DEVICE_DOES_NOT_EXIST;
        CompleteRequest(pIrp, Status, Information);
    }

    return Status;
}

NTSTATUS AddDevice(DRIVER_OBJECT *pDriverObject, DEVICE_OBJECT *pPhysicalDeviceObject)
{
    IRP *pIrp;
    MDL *pMdl;
    ULONG i, j;
    NTSTATUS Status;
    DEVICE_EXTENSION *pDeviceExtension;
    DEVICE_OBJECT *pDeviceObject, *pLowerDeviceObject;
    DEVICE_CAPABILITIES DeviceCapabilities;
    PNP_CONTEXT PnpContext;

    Status = IoCreateDevice(pDriverObject,
        sizeof(DEVICE_EXTENSION) - sizeof(PNP_INFO) + 2000,
        NULL,
        FILE_DEVICE_CONTROLLER,
        FILE_DEVICE_SECURE_OPEN,
        FALSE,
        &pDeviceObject);

    if (NT_SUCCESS(Status))
    {
        pDeviceExtension = (DEVICE_EXTENSION*)pDeviceObject->DeviceExtension;

        memset(pDeviceExtension, 0, sizeof(DEVICE_EXTENSION));

        pDeviceExtension->PnpLength = sizeof(PNP_INFO);

        pDeviceExtension->pDeviceObject = pDeviceObject;

        IoInitializeRemoveLock(&pDeviceExtension->RemoveLock, 0, 0, 0);

        pMdl = IoAllocateMdl(NULL, (32 + 1) * 4096, FALSE, FALSE, NULL);

        if (pMdl)
        {
            pLowerDeviceObject = IoAttachDeviceToDeviceStack
                                      (pDeviceObject, pPhysicalDeviceObject);

            if (pLowerDeviceObject)
            {
                Status = IoRegisterDeviceInterface(pPhysicalDeviceObject,
                          &g_ClassGuid, NULL, &pDeviceExtension->SymbolicLinkName);

                if (NT_SUCCESS(Status))
                {
                    pDeviceExtension->pMdl = pMdl;
                    pDeviceExtension->pLowerDeviceObject = pLowerDeviceObject;

                    PnpContext.pDeviceExtension = pDeviceExtension;
                    KeInitializeEvent(&PnpContext.Event, NotificationEvent, FALSE);

                    i = 0;
                    j = 0;

                    while (i < 3)
                    {
                        memset(&PnpContext.Stack, 0, sizeof(IO_STACK_LOCATION));

                        if (i == 0)
                        {
                            PnpContext.Stack.MinorFunction = IRP_MN_QUERY_CAPABILITIES;
                            PnpContext.Stack.Parameters.DeviceCapabilities.Capabilities =
                                                        &DeviceCapabilities;

                            memset(&DeviceCapabilities, 0, sizeof(DEVICE_CAPABILITIES));

                            DeviceCapabilities.Size = sizeof(DEVICE_CAPABILITIES);
                            DeviceCapabilities.Version = 1;

                            Status = AllocatePnpRequest(&PnpContext, &pIrp);

                            ++i;
                        }
                        else if (i == 1)
                        {
                            PnpContext.Stack.MinorFunction = IRP_MN_QUERY_ID;
                            PnpContext.Stack.Parameters.QueryId.IdType = (BUS_QUERY_ID_TYPE)j;

                            Status = AllocatePnpRequest(&PnpContext, &pIrp);

                            if (j == 5) { ++i; j = 0; }
                            else ++j;
                        }
                        else
                        {
                            PnpContext.Stack.MinorFunction = IRP_MN_QUERY_DEVICE_TEXT;
                            PnpContext.Stack.Parameters.QueryDeviceText.DeviceTextType =
                                                          (DEVICE_TEXT_TYPE)j;
                            PnpContext.Stack.Parameters.QueryDeviceText.LocaleId = 1033;

                            Status = AllocatePnpRequest(&PnpContext, &pIrp);

                            if (j == 1) { ++i; j = 0; }
                            else ++j;
                        }

                        if (NT_SUCCESS(Status))
                        {
                            IoSetCompletionRoutine
                             (pIrp, IoCompletionRoutine2, &PnpContext, TRUE, TRUE, TRUE);

                            Status = IoCallDriver(pLowerDeviceObject, pIrp);

                            if (Status == STATUS_PENDING)
                            {
                                KeWaitForSingleObject(&PnpContext.Event, Executive,
                                                         KernelMode, FALSE, NULL);
                            }

                            KeClearEvent(&PnpContext.Event);
                        }
                    }

                    pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
                    Status = STATUS_SUCCESS;
                }
                else IoDetachDevice(pLowerDeviceObject);
            }
            else Status = STATUS_NO_SUCH_DEVICE;
        }
        else Status = STATUS_UNSUCCESSFUL;

        if (!NT_SUCCESS(Status))
        {
            if (pMdl) IoFreeMdl(pMdl);

            IoDeleteDevice(pDeviceObject);
        }
    }

    return Status;
}

Client side code (truncated):

C++
#define IOCTL_PLUGIN_DEVICE    CTL_CODE(FILE_DEVICE_CONTROLLER, 0, METHOD_BUFFERED,
                               FILE_ANY_ACCESS)
#define IOCTL_PLUGOUT_DEVICE   CTL_CODE(FILE_DEVICE_CONTROLLER, 1, METHOD_BUFFERED,
                               FILE_ANY_ACCESS)
#define IOCTL_FETCH_REQUEST    CTL_CODE(FILE_DEVICE_CONTROLLER, 2, METHOD_OUT_DIRECT,
                               FILE_ANY_ACCESS)
#define IOCTL_COMPLETE_REQUEST CTL_CODE(FILE_DEVICE_CONTROLLER, 3, METHOD_IN_DIRECT,
                               FILE_ANY_ACCESS)

struct PNP_ENTRY
{
    ULONG InfoOffset;
    ULONG InfoLength;
    NTSTATUS Status;
};

struct PNP_INFO
{
    PNP_ENTRY irp_mn_query_id[6];
    PNP_ENTRY irp_mn_query_device_text[2];
    PNP_ENTRY irp_mn_query_capabilities;
};

struct PACKED_SRB
{
    UCHAR                      Function;
    UCHAR                      SrbStatus;
    UCHAR                      ScsiStatus;
    UCHAR                      PathId;
    UCHAR                      TargetId;
    UCHAR                      Lun;
    UCHAR                      QueueTag;
    UCHAR                      QueueAction;
    UCHAR                      CdbLength;
    UCHAR                      SenseInfoBufferLength;
    ULONG                      SrbFlags;
    ULONG                      DataTransferLength;
    ULONG                      TimeOutValue;
    ULONG                       QueueSortKey;
    UCHAR                      Cdb[16];
};

struct REQUEST_HEADER
{
    ULONG Id;
    ULONG Op;
    ULONG Irp[2];
    ULONG Length;
    ULONG MajorFunction;
    ULONG MinorFunction;
    ULONG IoControlCode;
    ULONG InputBufferLength;
    ULONG OutputBufferLength;
    NTSTATUS Status;
    ULONG Information;
};

struct DEVICE_EXTENSION
{
    DEVICE_OBJECT *pDeviceObject;
    IO_REMOVE_LOCK RemoveLock;
    BOOLEAN Bus;
};

struct BUS_EXTENSION : public DEVICE_EXTENSION
{
    LIST_ENTRY StorageList;
    DEVICE_OBJECT *pPhysicalDeviceObject;
    DEVICE_OBJECT *pLowerDeviceObject;
    UNICODE_STRING SymbolicLinkName;
};

struct STORAGE_EXTENSION : public DEVICE_EXTENSION
{
    LIST_ENTRY IrpQueue;
    LIST_ENTRY IrpQueue2;
    LIST_ENTRY IrpQueue3;
    LIST_ENTRY ListEntry;
    BOOLEAN Marked;
    BOOLEAN Included;
    MDL *pMdl;
    PNP_INFO PnpInfo;
};

#define IRP_TO_LIST_ENTRY(_Irp)    ((LIST_ENTRY*)(((UCHAR*)_Irp) +
                                 FIELD_OFFSET(IRP, Tail.Overlay.ListEntry)))
#define IRP_FROM_LIST_ENTRY(_ListEntry)    ((IRP*)(((UCHAR*)_ListEntry) -
                                  FIELD_OFFSET(IRP, Tail.Overlay.ListEntry)))

#define STORAGE_EXTENSION_TO_LIST_ENTRY(_Extension)
   ((LIST_ENTRY*)(((UCHAR*)_Extension) + FIELD_OFFSET(STORAGE_EXTENSION, ListEntry)))
#define STORAGE_EXTENSION_FROM_LIST_ENTRY(_ListEntry)
   ((STORAGE_EXTENSION*)(((UCHAR*)_ListEntry) - FIELD_OFFSET(STORAGE_EXTENSION, ListEntry)))

void UnpackRequest(REQUEST_HEADER *pHeader, IRP *pIrp, STORAGE_EXTENSION *pStorageExtension)
{
    BOOLEAN Unmap;
    PVOID pBuffer;
    IO_STACK_LOCATION *pStack;
    PACKED_SRB *pPackedSrb;
    SCSI_REQUEST_BLOCK *pSrb;
    SCSI_PASS_THROUGH *pPassThrough;

    pStack = IoGetCurrentIrpStackLocation(pIrp);

    if (pHeader->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
    {
        pPackedSrb = (PACKED_SRB*)((UCHAR*)pHeader + sizeof(REQUEST_HEADER));
        pSrb = pStack->Parameters.Scsi.Srb;

        pSrb->SrbStatus = pPackedSrb->SrbStatus;
        pSrb->ScsiStatus = pPackedSrb->ScsiStatus;

        pSrb->SenseInfoBufferLength = pPackedSrb->SenseInfoBufferLength;
        pSrb->DataTransferLength = pPackedSrb->DataTransferLength;

        if (NT_SUCCESS(pHeader->Status))
        {
            if ((pPackedSrb->DataTransferLength) && (pPackedSrb->SrbFlags & SRB_FLAGS_DATA_IN))
            {
                pBuffer = GetBuffer(pIrp->MdlAddress,
                          pStorageExtension->pMdl, pSrb->DataBuffer,
                          pPackedSrb->DataTransferLength, &Unmap);

                memcpy(pBuffer, (UCHAR*)pPackedSrb + sizeof(PACKED_SRB),
                                        pPackedSrb->DataTransferLength);

                if (Unmap) MmUnmapLockedPages(pBuffer, pStorageExtension->pMdl);
            }
            else
            {
                if (pPackedSrb->Function == SRB_FUNCTION_CLAIM_DEVICE)
                                    pSrb->DataBuffer = pStack->DeviceObject;
            }
        }
        else
        {
            if ((pPackedSrb->SenseInfoBufferLength) && (pPackedSrb->SrbStatus &
                                                 SRB_STATUS_AUTOSENSE_VALID))
            {
                memcpy(pSrb->SenseInfoBuffer, (UCHAR*)pPackedSrb + sizeof(PACKED_SRB),
                                           pPackedSrb->SenseInfoBufferLength);
            }
        }
    }
    else
    {
        if (pHeader->IoControlCode == IOCTL_SCSI_PASS_THROUGH)
        {
            pPassThrough = (SCSI_PASS_THROUGH*)((UCHAR*)pHeader + sizeof(REQUEST_HEADER));

            memcpy(pIrp->AssociatedIrp.SystemBuffer, pPassThrough, sizeof(SCSI_PASS_THROUGH));

            if (NT_SUCCESS(pHeader->Status))
            {
                if ((pPassThrough->DataTransferLength) &&
                                   (pPassThrough->DataIn == SCSI_IOCTL_DATA_IN))
                {
                    memcpy((UCHAR*)pIrp->AssociatedIrp.SystemBuffer +
                           pPassThrough->DataBufferOffset, (UCHAR*)pPassThrough +
                           pPassThrough->DataBufferOffset, pPassThrough->DataTransferLength);
                }
            }
            else
            {
                memcpy((UCHAR*)pIrp->AssociatedIrp.SystemBuffer +
                     pPassThrough->SenseInfoOffset, (UCHAR*)pPassThrough +
                     pPassThrough->SenseInfoOffset, pPassThrough->SenseInfoLength);
            }
        }
        else
        {
            if (NT_SUCCESS(pHeader->Status))
            {
                if ((pHeader->IoControlCode == IOCTL_SCSI_GET_ADDRESS) ||
                    (pHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY))
                {
                    memcpy(pIrp->AssociatedIrp.SystemBuffer, (UCHAR*)pHeader +
                              sizeof(REQUEST_HEADER), pHeader->Information);
                }
            }
        }
    }
}

void PackRequest(IRP *pIrp, REQUEST_HEADER *pHeader, STORAGE_EXTENSION *pStorageExtension)
{
    BOOLEAN Unmap;
    PVOID pBuffer;
    IO_STACK_LOCATION *pStack;
    PACKED_SRB *pPackedSrb;
    SCSI_REQUEST_BLOCK *pSrb;
    SCSI_PASS_THROUGH *pPassThrough;

    pStack = IoGetCurrentIrpStackLocation(pIrp);

    *((IRP**)pHeader->Irp) = pIrp;

    pHeader->Status = 0;
    pHeader->Information = 0;

    pHeader->MajorFunction = pStack->MajorFunction;
    pHeader->MinorFunction = pStack->MinorFunction;

    if (pStack->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
    {
        pHeader->IoControlCode = 0;
        pHeader->InputBufferLength = 0;
        pHeader->OutputBufferLength = 0;
        pHeader->Length = sizeof(REQUEST_HEADER) + sizeof(PACKED_SRB);

        pPackedSrb = (PACKED_SRB*)((UCHAR*)pHeader + sizeof(REQUEST_HEADER));
        pSrb = pStack->Parameters.Scsi.Srb;

        pPackedSrb->Function = pSrb->Function;
        pPackedSrb->SrbStatus = pSrb->SrbStatus;
        pPackedSrb->ScsiStatus = pSrb->ScsiStatus;
        pPackedSrb->PathId = pSrb->PathId;
        pPackedSrb->TargetId = pSrb->TargetId;
        pPackedSrb->Lun = pSrb->Lun;
        pPackedSrb->QueueTag = pSrb->QueueTag;
        pPackedSrb->QueueAction = pSrb->QueueAction;
        pPackedSrb->CdbLength = pSrb->CdbLength;
        pPackedSrb->SenseInfoBufferLength = pSrb->SenseInfoBufferLength;
        pPackedSrb->SrbFlags = pSrb->SrbFlags;
        pPackedSrb->DataTransferLength = pSrb->DataTransferLength;
        pPackedSrb->TimeOutValue = pSrb->TimeOutValue;

        if ((pSrb->DataTransferLength) && (pSrb->SrbFlags & SRB_FLAGS_DATA_OUT))
        {
            pBuffer = GetBuffer(pIrp->MdlAddress, pStorageExtension->pMdl,
                         pSrb->DataBuffer, pSrb->DataTransferLength, &Unmap);

            memcpy((UCHAR*)pPackedSrb +
                  sizeof(PACKED_SRB), pBuffer, pSrb->DataTransferLength);

            if (Unmap) MmUnmapLockedPages(pBuffer, pStorageExtension->pMdl);

            pHeader->Length += pSrb->DataTransferLength;
        }

        pPackedSrb->QueueSortKey = pSrb->QueueSortKey;
        memcpy(pPackedSrb->Cdb, pSrb->Cdb, sizeof(pSrb->Cdb));
    }
    else
    {
        pHeader->IoControlCode = pStack->Parameters.DeviceIoControl.IoControlCode;
        pHeader->InputBufferLength = pStack->Parameters.DeviceIoControl.InputBufferLength;
        pHeader->OutputBufferLength = pStack->Parameters.DeviceIoControl.OutputBufferLength;

        pHeader->Length = sizeof(REQUEST_HEADER);

        if ((pHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY) ||
            (pHeader->IoControlCode == IOCTL_STORAGE_ENABLE_IDLE_POWER))
        {
            pHeader->Length += pHeader->InputBufferLength;
            memcpy((UCHAR*)pHeader + sizeof(REQUEST_HEADER),
                    pIrp->AssociatedIrp.SystemBuffer, pHeader->InputBufferLength);
        }
        else if (pHeader->IoControlCode == IOCTL_SCSI_PASS_THROUGH)
        {
            pPassThrough = (SCSI_PASS_THROUGH*)pIrp->AssociatedIrp.SystemBuffer;

            memcpy((UCHAR*)pHeader + sizeof(REQUEST_HEADER), pPassThrough,
                                     sizeof(SCSI_PASS_THROUGH));

            if ((pPassThrough->DataTransferLength) &&
                           (pPassThrough->DataIn == SCSI_IOCTL_DATA_OUT))
            {
                pHeader->Length += pPassThrough->DataBufferOffset +
                                          pPassThrough->DataTransferLength;
                memcpy((UCHAR*)pHeader + sizeof(REQUEST_HEADER) +
                              pPassThrough->DataBufferOffset, (UCHAR*)pPassThrough +
                              pPassThrough->DataBufferOffset, pPassThrough->DataTransferLength);
            }
            else
            {
                pHeader->Length += sizeof(SCSI_PASS_THROUGH);
            }
        }
        else if ((pHeader->IoControlCode != IOCTL_STORAGE_POWER_ACTIVE) &&
            (pHeader->IoControlCode != IOCTL_SCSI_GET_ADDRESS))
        {
            DbgRaiseAssertionFailure();
        }
    }
}

NTSTATUS DispatchPnp(DEVICE_OBJECT *pDeviceObject, IRP *pIrp)
{
    NTSTATUS Status;
    IO_STACK_LOCATION *pStack;
    DEVICE_EXTENSION *pDeviceExtension;
    BUS_EXTENSION *pBusExtension;
    STORAGE_EXTENSION *pStorageExtension;
    POWER_STATE PowerState;
    ULONG_PTR Information;
    DEVICE_RELATIONS *pDeviceRelations;
    DEVICE_TEXT_TYPE DeviceTextType;
    BUS_QUERY_ID_TYPE BusQueryIdType;
    LIST_ENTRY *pListEntry;
    PNP_ENTRY *pPnpEntry;
    KEVENT Event;
    ULONG Count, i;

    Information = 0;

    pDeviceExtension = (DEVICE_EXTENSION*)pDeviceObject->DeviceExtension;
    pStack = IoGetCurrentIrpStackLocation(pIrp);

    Status = IoAcquireRemoveLock(&pDeviceExtension->RemoveLock, pIrp);

    if (NT_SUCCESS(Status))
    {
        if (pDeviceExtension->Bus)
        {
            pBusExtension = (BUS_EXTENSION*)pDeviceExtension;

            switch (pStack->MinorFunction)
            {
            case IRP_MN_START_DEVICE:
                KeInitializeEvent(&Event, NotificationEvent, FALSE);

                IoCopyCurrentIrpStackLocationToNext(pIrp);

                IoSetCompletionRoutine(pIrp, IoCompletionRoutine, &Event, TRUE, TRUE, TRUE);

                Status = IoCallDriver(pBusExtension->pLowerDeviceObject, pIrp);

                if (Status == STATUS_PENDING) KeWaitForSingleObject
                              (&Event, Executive, KernelMode, FALSE, NULL);

                Status = pIrp->IoStatus.Status;
                Information = pIrp->IoStatus.Information;

                if (NT_SUCCESS(Status))
                {
                    Information = 0;

                    Status = IoSetDeviceInterfaceState(&pBusExtension->SymbolicLinkName, TRUE);

                    if (NT_SUCCESS(Status))
                    {
                        PowerState.DeviceState = PowerDeviceD0;
                        PoSetPowerState(pDeviceObject, DevicePowerState, PowerState);
                    }
                }

                CompleteRequest(pIrp, Status, Information);

                IoReleaseRemoveLock(&pBusExtension->RemoveLock, pIrp);
                break;
            case IRP_MN_QUERY_DEVICE_RELATIONS:
                if (pStack->Parameters.QueryDeviceRelations.Type == BusRelations)
                {
                    Count = 0;
                    pListEntry = pBusExtension->StorageList.Flink;

                    while (pListEntry != &pBusExtension->StorageList)
                    {
                        pStorageExtension = STORAGE_EXTENSION_FROM_LIST_ENTRY(pListEntry);

                        if (!pStorageExtension->Marked) ++Count;

                        pListEntry = pListEntry->Flink;
                    }

                    pDeviceRelations = (DEVICE_RELATIONS*)ExAllocatePool
                   (PagedPool, sizeof(DEVICE_RELATIONS) + (sizeof(PDEVICE_OBJECT) * Count));

                    if (pDeviceRelations)
                    {
                        pDeviceRelations->Count = Count;

                        i = 0;
                        pListEntry = pBusExtension->StorageList.Flink;

                        while (pListEntry != &pBusExtension->StorageList)
                        {
                            pStorageExtension = STORAGE_EXTENSION_FROM_LIST_ENTRY(pListEntry);

                            if (!pStorageExtension->Marked)
                            {
                                ObReferenceObject(pStorageExtension->pDeviceObject);
                                pDeviceRelations->Objects[i] = pStorageExtension->pDeviceObject;
                                pStorageExtension->Included = TRUE;
                                ++i;
                            }
                            else
                            {
                                pStorageExtension->Included = FALSE;
                                RemoveEntryList(pListEntry);
                            }

                            pListEntry = pListEntry->Flink;
                        }

                        Information = (ULONG_PTR)pDeviceRelations;
                    }
                    else Status = STATUS_INSUFFICIENT_RESOURCES;
                }
                else Status = STATUS_NOT_SUPPORTED;

                pIrp->IoStatus.Status = Status;
                pIrp->IoStatus.Information = Information;

                IoSkipCurrentIrpStackLocation(pIrp);
                Status = IoCallDriver(pBusExtension->pLowerDeviceObject, pIrp);

                IoReleaseRemoveLock(&pBusExtension->RemoveLock, pIrp);
                break;
            case IRP_MN_REMOVE_DEVICE:
                PowerState.DeviceState = PowerDeviceD3;
                PoSetPowerState(pDeviceObject, DevicePowerState, PowerState);

                pIrp->IoStatus.Status = Status;
                pIrp->IoStatus.Information = Information;

                IoSkipCurrentIrpStackLocation(pIrp);
                Status = IoCallDriver(pBusExtension->pLowerDeviceObject, pIrp);

                IoReleaseRemoveLockAndWait(&pBusExtension->RemoveLock, pIrp);

                IoSetDeviceInterfaceState(&pBusExtension->SymbolicLinkName, FALSE);

                RtlFreeUnicodeString(&pBusExtension->SymbolicLinkName);

                IoDetachDevice(pBusExtension->pLowerDeviceObject);
                IoDeleteDevice(pDeviceObject);
                break;
            case IRP_MN_SURPRISE_REMOVAL:
            case IRP_MN_QUERY_REMOVE_DEVICE:
            case IRP_MN_CANCEL_REMOVE_DEVICE:
            case IRP_MN_QUERY_STOP_DEVICE:
            case IRP_MN_CANCEL_STOP_DEVICE:
            case IRP_MN_STOP_DEVICE:
                pIrp->IoStatus.Status = Status;
                pIrp->IoStatus.Information = Information;

                IoSkipCurrentIrpStackLocation(pIrp);
                Status = IoCallDriver(pBusExtension->pLowerDeviceObject, pIrp);

                IoReleaseRemoveLock(&pBusExtension->RemoveLock, pIrp);
                break;
            default:
                Status = STATUS_NOT_SUPPORTED;

                pIrp->IoStatus.Status = Status;
                pIrp->IoStatus.Information = Information;

                IoSkipCurrentIrpStackLocation(pIrp);
                Status = IoCallDriver(pBusExtension->pLowerDeviceObject, pIrp);

                IoReleaseRemoveLock(&pBusExtension->RemoveLock, pIrp);
                break;
            }
        }
        else
        {
            pStorageExtension = (STORAGE_EXTENSION*)pDeviceExtension;

            switch (pStack->MinorFunction)
            {
            case IRP_MN_START_DEVICE:
                PowerState.DeviceState = PowerDeviceD0;
                PoSetPowerState(pDeviceObject, DevicePowerState, PowerState);

                CompleteRequest(pIrp, Status, Information);
                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                break;
            case IRP_MN_QUERY_REMOVE_DEVICE:
            case IRP_MN_SURPRISE_REMOVAL:
                CompleteOutstandingRequests(pStorageExtension);
                pStorageExtension->Marked = TRUE;

                CompleteRequest(pIrp, Status, Information);
                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                break;
            case IRP_MN_CANCEL_REMOVE_DEVICE:
                pStorageExtension->Marked = FALSE;

                CompleteRequest(pIrp, Status, Information);
                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                break;
            case IRP_MN_QUERY_STOP_DEVICE:
            case IRP_MN_CANCEL_STOP_DEVICE:
            case IRP_MN_STOP_DEVICE:
                CompleteRequest(pIrp, Status, Information);
                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                break;
            case IRP_MN_REMOVE_DEVICE:
                if (pStorageExtension->Included)
                {
                    CompleteRequest(pIrp, Status, Information);
                    IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                }
                else
                {
                    PowerState.DeviceState = PowerDeviceD3;
                    PoSetPowerState(pDeviceObject, DevicePowerState, PowerState);

                    CompleteRequest(pIrp, Status, Information);
                    IoReleaseRemoveLockAndWait(&pStorageExtension->RemoveLock, pIrp);

                    IoFreeMdl(pStorageExtension->pMdl);
                    IoDeleteDevice(pDeviceObject);
                }

                break;
            case IRP_MN_QUERY_DEVICE_RELATIONS:
                if (pStack->Parameters.QueryDeviceRelations.Type == TargetDeviceRelation)
                {
                    pDeviceRelations = (DEVICE_RELATIONS*)ExAllocatePool
                                       (PagedPool, sizeof(DEVICE_RELATIONS));

                    if (pDeviceRelations)
                    {
                        ObReferenceObject(pDeviceObject);

                        pDeviceRelations->Count = 1;
                        pDeviceRelations->Objects[0] = pDeviceObject;

                        Information = (ULONG_PTR)pDeviceRelations;
                    }
                    else Status = STATUS_INSUFFICIENT_RESOURCES;
                }
                else Status = STATUS_NOT_SUPPORTED;

                CompleteRequest(pIrp, Status, Information);
                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                break;
            case IRP_MN_QUERY_DEVICE_TEXT:
                DeviceTextType = pStack->Parameters.QueryDeviceText.DeviceTextType;
                pPnpEntry =
                   &pStorageExtension->PnpInfo.irp_mn_query_device_text[DeviceTextType];

                Status = pPnpEntry->Status;

                if (NT_SUCCESS(Status) && pPnpEntry->InfoOffset)
                {
                    Information = (ULONG_PTR)ExAllocatePool(PagedPool, pPnpEntry->InfoLength);

                    if (Information)
                    {
                        memcpy((UCHAR*)Information, (UCHAR*)&pStorageExtension->PnpInfo +
                                      pPnpEntry->InfoOffset, pPnpEntry->InfoLength);
                    }
                    else Status = STATUS_INSUFFICIENT_RESOURCES;
                }

                CompleteRequest(pIrp, Status, Information);
                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                break;
            case IRP_MN_QUERY_ID:
                BusQueryIdType = pStack->Parameters.QueryId.IdType;
                pPnpEntry = &pStorageExtension->PnpInfo.irp_mn_query_id[BusQueryIdType];

                Status = pPnpEntry->Status;

                if (NT_SUCCESS(Status) && pPnpEntry->InfoOffset)
                {
                    Information = (ULONG_PTR)ExAllocatePool(PagedPool, pPnpEntry->InfoLength);

                    if (Information)
                    {
                        memcpy((UCHAR*)Information, (UCHAR*)&pStorageExtension->PnpInfo +
                                   pPnpEntry->InfoOffset, pPnpEntry->InfoLength);
                    }
                    else Status = STATUS_INSUFFICIENT_RESOURCES;
                }

                CompleteRequest(pIrp, Status, Information);
                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                break;
            case IRP_MN_QUERY_CAPABILITIES:
                pPnpEntry = &pStorageExtension->PnpInfo.irp_mn_query_capabilities;

                Status = pPnpEntry->Status;

                if (NT_SUCCESS(Status) && pPnpEntry->InfoOffset)
                {
                    memcpy(pStack->Parameters.DeviceCapabilities.Capabilities,
                       (UCHAR*)&pStorageExtension->PnpInfo + pPnpEntry->InfoOffset,
                       pPnpEntry->InfoLength);
                }

                CompleteRequest(pIrp, Status, Information);
                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                break;
            default:
                Status = STATUS_NOT_SUPPORTED;

                CompleteRequest(pIrp, Status, Information);
                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                break;
            }
        }
    }
    else
    {
        Status = STATUS_DEVICE_DOES_NOT_EXIST;
        CompleteRequest(pIrp, Status, Information);
    }

    return Status;
}

NTSTATUS DispatchDeviceControl(DEVICE_OBJECT *pDeviceObject, IRP *pIrp)
{
    NTSTATUS Status;
    ULONG IoControlCode;
    IO_STACK_LOCATION *pStack;
    DEVICE_EXTENSION *pDeviceExtension;
    STORAGE_EXTENSION *pStorageExtension;
    BUS_EXTENSION *pBusExtension;
    PNP_INFO *pPnpInfo;
    LIST_ENTRY *pListEntry;
    IRP *pIrp2;
    MDL *pMdl;
    REQUEST_HEADER *pHeader;

    pDeviceExtension = (DEVICE_EXTENSION*)pDeviceObject->DeviceExtension;
    pStack = IoGetCurrentIrpStackLocation(pIrp);

    Status = IoAcquireRemoveLock(&pDeviceExtension->RemoveLock, pIrp);

    if (NT_SUCCESS(Status))
    {
        if (pDeviceExtension->Bus)
        {
            pBusExtension = (BUS_EXTENSION*)pDeviceExtension;
            IoControlCode = pStack->Parameters.DeviceIoControl.IoControlCode;

            if (IoControlCode == IOCTL_PLUGIN_DEVICE)
            {
                pHeader = (REQUEST_HEADER*)pIrp->AssociatedIrp.SystemBuffer;
                pPnpInfo = (PNP_INFO*)((UCHAR*)pHeader + sizeof(REQUEST_HEADER));

                Status = IoCreateDevice(pDeviceObject->DriverObject,
                    sizeof(STORAGE_EXTENSION) + (pHeader->Length -
                           (sizeof(REQUEST_HEADER) + sizeof(PNP_INFO))),
                    NULL,
                    FILE_DEVICE_MASS_STORAGE,
                    FILE_DEVICE_SECURE_OPEN | FILE_AUTOGENERATED_DEVICE_NAME,
                    FALSE,
                    &pDeviceObject);

                if (NT_SUCCESS(Status))
                {
                    pMdl = IoAllocateMdl(NULL, (32 + 1) * 4096, FALSE, FALSE, NULL);

                    if (pMdl)
                    {
                        pStorageExtension = (STORAGE_EXTENSION*)pDeviceObject->DeviceExtension;

                        memset(pStorageExtension, 0, sizeof(STORAGE_EXTENSION) - sizeof(PNP_INFO));
                        memcpy(&pStorageExtension->PnpInfo, 
                        pPnpInfo, pHeader->Length - sizeof(REQUEST_HEADER));

                        pStorageExtension->pDeviceObject = pDeviceObject;
                        pStorageExtension->Bus = FALSE;
                        pStorageExtension->Included = FALSE;
                        pStorageExtension->Marked = FALSE;

                        InitializeListHead(&pStorageExtension->IrpQueue);
                        InitializeListHead(&pStorageExtension->IrpQueue2);
                        InitializeListHead(&pStorageExtension->IrpQueue3);

                        IoInitializeRemoveLock(&pStorageExtension->RemoveLock, 0, 0, 0);

                        InsertTailList(&pBusExtension->StorageList, 
                        STORAGE_EXTENSION_TO_LIST_ENTRY(pStorageExtension));

                        pStorageExtension->pMdl = pMdl;

                        ObReferenceObject(pDeviceObject);
                        pStack->FileObject->FsContext = pDeviceObject;

                        pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

                        IoInvalidateDeviceRelations(pBusExtension->pPhysicalDeviceObject, BusRelations);
                    }
                    else
                    {
                        Status = STATUS_INSUFFICIENT_RESOURCES;
                        IoDeleteDevice(pDeviceObject);
                    }
                }
            }
            else if (IoControlCode == IOCTL_PLUGOUT_DEVICE)
            {
                pDeviceObject = (DEVICE_OBJECT*)pStack->FileObject->FsContext;

                if (pDeviceObject)
                {
                    pStorageExtension = (STORAGE_EXTENSION*)pDeviceObject->DeviceExtension;

                    CompleteOutstandingRequests(pStorageExtension);
                    pStorageExtension->Marked = TRUE;

                    IoInvalidateDeviceRelations(pBusExtension->pPhysicalDeviceObject, BusRelations);
                }
                else Status = STATUS_DEVICE_DOES_NOT_EXIST;
            }
            else if (IoControlCode == IOCTL_FETCH_REQUEST)
            {
                pDeviceObject = (DEVICE_OBJECT*)pStack->FileObject->FsContext;

                if (pDeviceObject)
                {
                    pStorageExtension = (STORAGE_EXTENSION*)pDeviceObject->DeviceExtension;

                    Status = IoAcquireRemoveLock(&pStorageExtension->RemoveLock, pIrp);

                    if (NT_SUCCESS(Status))
                    {
                        if (!pStorageExtension->Marked)
                        {
                            if (!IsListEmpty(&pStorageExtension->IrpQueue))
                            {
                                pListEntry = pStorageExtension->IrpQueue.Flink;
                                RemoveEntryList(pListEntry);
                                InsertTailList(&pStorageExtension->IrpQueue2, pListEntry);

                                pHeader = (REQUEST_HEADER*)
                                MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
                                PackRequest(IRP_FROM_LIST_ENTRY(pListEntry), pHeader, pStorageExtension);

                                IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                            }
                            else
                            {
                                InsertTailList(&pStorageExtension->IrpQueue3, IRP_TO_LIST_ENTRY(pIrp));

                                IoMarkIrpPending(pIrp);
                                Status = STATUS_PENDING;
                            }
                        }
                        else
                        {
                            IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp);
                            Status = STATUS_DEVICE_DOES_NOT_EXIST;
                        }
                    }
                    else Status = STATUS_DEVICE_DOES_NOT_EXIST;
                }
                else Status = STATUS_DEVICE_DOES_NOT_EXIST;
            }
            else if (IoControlCode == IOCTL_COMPLETE_REQUEST)
            {
                pDeviceObject = (DEVICE_OBJECT*)pStack->FileObject->FsContext;

                if (pDeviceObject)
                {
                    pStorageExtension = (STORAGE_EXTENSION*)pDeviceObject->DeviceExtension;

                    if (!pStorageExtension->Marked)
                    {
                        pHeader = (REQUEST_HEADER*)pIrp->AssociatedIrp.SystemBuffer;

                        pIrp2 = *((IRP**)pHeader->Irp);
                        RemoveEntryList(IRP_TO_LIST_ENTRY(pIrp2));

                        UnpackRequest(pHeader, pIrp2, pStorageExtension);

                        CompleteRequest(pIrp2, pHeader->Status, pHeader->Information);
                        IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp2);
                    }
                    else Status = STATUS_DEVICE_DOES_NOT_EXIST;
                }
                else Status = STATUS_DEVICE_DOES_NOT_EXIST;
            }
            else Status = STATUS_NOT_SUPPORTED;

            if (Status != STATUS_PENDING) CompleteRequest(pIrp, Status, 0);

            IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
        }
        else
        {
            pStorageExtension = (STORAGE_EXTENSION*)pDeviceExtension;

            if (!pStorageExtension->Marked)
            {
                if (RequestSupported(pIrp))
                {
                    if (!IsListEmpty(&pStorageExtension->IrpQueue3))
                    {
                        pListEntry = pStorageExtension->IrpQueue3.Flink;
                        RemoveEntryList(pListEntry);

                        InsertTailList(&pStorageExtension->IrpQueue2, IRP_TO_LIST_ENTRY(pIrp));

                        pIrp2 = IRP_FROM_LIST_ENTRY(pListEntry);
                        pHeader = (REQUEST_HEADER*)
                        MmGetSystemAddressForMdlSafe(pIrp2->MdlAddress, NormalPagePriority);
                        PackRequest(pIrp, pHeader, pStorageExtension);

                        CompleteRequest(pIrp2, STATUS_SUCCESS, 0);
                        IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp2);
                    }
                    else
                    {
                        InsertTailList(&pStorageExtension->IrpQueue, IRP_TO_LIST_ENTRY(pIrp));
                    }

                    IoMarkIrpPending(pIrp);
                    Status = STATUS_PENDING;
                }
                else
                {
                    Status = STATUS_NOT_SUPPORTED;

                    CompleteRequest(pIrp, Status, 0);
                    IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
                }
            }
            else
            {
                Status = STATUS_DEVICE_DOES_NOT_EXIST;

                CompleteRequest(pIrp, Status, 0);
                IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
            }
        }
    }
    else
    {
        Status = STATUS_DEVICE_DOES_NOT_EXIST;
        CompleteRequest(pIrp, Status, 0);
    }

    return Status;
}

NTSTATUS DispatchInternalDeviceControl(DEVICE_OBJECT *pDeviceObject, IRP *pIrp)
{
    NTSTATUS Status;
    IO_STACK_LOCATION *pStack;
    SCSI_REQUEST_BLOCK *pSrb;
    DEVICE_EXTENSION *pDeviceExtension;
    STORAGE_EXTENSION *pStorageExtension;
    LIST_ENTRY *pListEntry;
    IRP *pIrp2;
    REQUEST_HEADER *pHeader;

    pDeviceExtension = (DEVICE_EXTENSION*)pDeviceObject->DeviceExtension;
    pStack = IoGetCurrentIrpStackLocation(pIrp);

    Status = IoAcquireRemoveLock(&pDeviceExtension->RemoveLock, pIrp);

    if (NT_SUCCESS(Status))
    {
        if (!pDeviceExtension->Bus)
        {
            pStorageExtension = (STORAGE_EXTENSION*)pDeviceExtension;
            pSrb = pStack->Parameters.Scsi.Srb;

            if (!pStorageExtension->Marked)
            {
                if (!IsListEmpty(&pStorageExtension->IrpQueue3))
                {
                    pListEntry = pStorageExtension->IrpQueue3.Flink;
                    RemoveEntryList(pListEntry);

                    InsertTailList(&pStorageExtension->IrpQueue2, IRP_TO_LIST_ENTRY(pIrp));

                    pIrp2 = IRP_FROM_LIST_ENTRY(pListEntry);
                    pHeader = (REQUEST_HEADER*)MmGetSystemAddressForMdlSafe
                    (pIrp2->MdlAddress, NormalPagePriority);
                    PackRequest(pIrp, pHeader, pStorageExtension);

                    CompleteRequest(pIrp2, STATUS_SUCCESS, 0);
                    IoReleaseRemoveLock(&pStorageExtension->RemoveLock, pIrp2);
                }
                else
                {
                    InsertTailList(&pStorageExtension->IrpQueue, IRP_TO_LIST_ENTRY(pIrp));
                }

                IoMarkIrpPending(pIrp);

                pSrb->SrbStatus = SRB_STATUS_PENDING;
                Status = STATUS_PENDING;
            }
            else
            {
                pSrb->SrbStatus = SRB_STATUS_NO_DEVICE;
                Status = STATUS_DEVICE_DOES_NOT_EXIST;

                CompleteRequest(pIrp, Status, 0);
                IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
            }
        }
        else
        {
            Status = STATUS_NOT_SUPPORTED;

            CompleteRequest(pIrp, Status, 0);
            IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
        }
    }
    else
    {
        if (!pDeviceExtension->Bus)
        {
            pSrb = pStack->Parameters.Scsi.Srb;
            pSrb->SrbStatus = SRB_STATUS_NO_DEVICE;
        }

        Status = STATUS_DEVICE_DOES_NOT_EXIST;
        CompleteRequest(pIrp, Status, 0);
    }

    return Status;
}

NTSTATUS DispatchCreate(DEVICE_OBJECT *pDeviceObject, IRP *pIrp)
{
    NTSTATUS Status;

    Status = STATUS_SUCCESS;
    CompleteRequest(pIrp, Status, 0);
    return Status;
}

NTSTATUS DispatchCleanup(DEVICE_OBJECT *pDeviceObject, IRP *pIrp)  // handle reference
                                               // count on a file object has reached zero
{
    NTSTATUS Status;
    BUS_EXTENSION *pBusExtension;
    DEVICE_EXTENSION *pDeviceExtension;
    STORAGE_EXTENSION *pStorageExtension;
    IO_STACK_LOCATION *pStack;

    pDeviceExtension = (DEVICE_EXTENSION*)pDeviceObject->DeviceExtension;
    pStack = IoGetCurrentIrpStackLocation(pIrp);

    Status = IoAcquireRemoveLock(&pDeviceExtension->RemoveLock, pIrp);

    if (NT_SUCCESS(Status))
    {
        if (pDeviceExtension->Bus)
        {
            pBusExtension = (BUS_EXTENSION*)pDeviceObject->DeviceExtension;

            pDeviceObject = (DEVICE_OBJECT*)pStack->FileObject->FsContext;

            if (pDeviceObject)
            {
                pStorageExtension = (STORAGE_EXTENSION*)pDeviceObject->DeviceExtension;

                CompleteOutstandingRequests(pStorageExtension);
                pStorageExtension->Marked = TRUE;

                IoInvalidateDeviceRelations(pBusExtension->pPhysicalDeviceObject, BusRelations);
            }
        }

        CompleteRequest(pIrp, Status, 0);
        IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);
    }
    else
    {
        Status = STATUS_SUCCESS;
        CompleteRequest(pIrp, Status, 0);
    }

    return Status;
}

NTSTATUS DispatchClose(DEVICE_OBJECT *pDeviceObject, IRP *pIrp)   // reference count
                                                      // on a file object has reached zero
{
    NTSTATUS Status;
    DEVICE_EXTENSION *pDeviceExtension;
    IO_STACK_LOCATION *pStack;

    pDeviceExtension = (DEVICE_EXTENSION*)pDeviceObject->DeviceExtension;
    pStack = IoGetCurrentIrpStackLocation(pIrp);

    if (pDeviceExtension->Bus)
    {
        pDeviceObject = (DEVICE_OBJECT*)pStack->FileObject->FsContext;

        if (pDeviceObject) ObDereferenceObject(pDeviceObject);
    }

    Status = STATUS_SUCCESS;
    CompleteRequest(pIrp, Status, 0);
    return Status;
}

NTSTATUS AddDevice(DRIVER_OBJECT *pDriverObject, DEVICE_OBJECT *pPhysicalDeviceObject)
{
    NTSTATUS Status;
    BUS_EXTENSION *pBusExtension;
    DEVICE_OBJECT *pDeviceObject, *pLowerDeviceObject;

    Status = IoCreateDevice(pDriverObject,
        sizeof(BUS_EXTENSION),
        NULL,
        FILE_DEVICE_CONTROLLER,
        FILE_DEVICE_SECURE_OPEN,
        FALSE,
        &pDeviceObject);

    if (NT_SUCCESS(Status))
    {
        pBusExtension = (BUS_EXTENSION*)pDeviceObject->DeviceExtension;

        memset(pBusExtension, 0, sizeof(BUS_EXTENSION));

        pBusExtension->pDeviceObject = pDeviceObject;
        pBusExtension->Bus = TRUE;

        IoInitializeRemoveLock(&pBusExtension->RemoveLock, 0, 0, 0);

        InitializeListHead(&pBusExtension->StorageList);

        pBusExtension->pPhysicalDeviceObject = pPhysicalDeviceObject;

        pLowerDeviceObject = IoAttachDeviceToDeviceStack(pDeviceObject, pPhysicalDeviceObject);

        if (pLowerDeviceObject)
        {
            pBusExtension->pLowerDeviceObject = pLowerDeviceObject;

            Status = IoRegisterDeviceInterface(pPhysicalDeviceObject,
            &g_ClassGuid, NULL, &pBusExtension->SymbolicLinkName);

            if (NT_SUCCESS(Status)) pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
            else IoDetachDevice(pLowerDeviceObject);
        }
        else Status = STATUS_NO_SUCH_DEVICE;

        if (!NT_SUCCESS(Status)) IoDeleteDevice(pDeviceObject);
    }

    return Status;
}

Gotchas

One note about buffer size. On my system, IOCTL_STORAGE_QUERY_PROPERTY (property id = StorageAdapterProperty) returns STORAGE_ADAPTER_DESCRIPTOR with MaximumTransferLength field set to 32 * page size (page size = 4096). I set BUFFER_SIZE to 40 * page size. Also, when we allocate mdl:

C++
pMdl = IoAllocateMdl(NULL, (32 + 1) * 4096, FALSE, FALSE, NULL);

Instead, we need to set these lengths accordingly to MaximumTransferLength field. Also, drivers lack synchronized access to device extension, however on virtual machine, everything works fine (probably because we have only one core by default).

I will fix it when I get proper rest.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)