OSRLogo
OSRLogoOSRLogoOSRLogo x OSR Custom Development Services
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Thu, 14 Mar 2019     118020 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
The Basics
File Systems
Downloads
ListServer / Forum
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

Writing a Virtual Storport Miniport Driver (Part III)

This article is one in a series on writing virtual Storport miniport drivers for Windows. Please find links to each article in the series here:Part I Part II Part III

 

 Click Here to Download: Code Associated With This Article Zip Archive, 6 MB

In our previous articles we discussed that our example driver design was divided into 2 parts, the upper-edge which handled the Storport interaction, and the lower-edge that implemented the virtual SCSI Adapter and devices.  This article continues to discuss the specifics of our example driver's implementation.

Figure 1- OSR Virtual Storport Miniport Architecture

In our last article we ended our discussion mentioning 2 points:

  • The Virtual Storport Miniport does not useIOCTL_SCSI_MINIPORT requests for configuration since this handler would be called at IRQL DISPATCH_LEVEL, and
  • To define a SCSI device our driver must respond to Storport with an INQUIRYDATA, when requested.

Given those 2 points we'll continue by first describing how the driver handles user configuration requests and then describe how to respond to a Storport request for INQUIRYDATA.

Handling User Mode Requests

Because the example Virtual Storport Miniport driver that we're developing needs to respond to user mode requests to configure devices, it needs some way to receive those user requests and also be at IRQL PASSIVE_LEVEL.  Being at IRQL PASSIVE_LEVEL is required because our design needs to be able to interact with the Windows file systems.   SinceIOCTL_SCSI_MINPORT doesn't meet the IRQL criteria, we use a new IOCTL that was added for Storport called IOCTL_MINIPORT_PROCESS_SERVICE_IRP.   The documentation for this IOCTL indicates that it is used by a user-mode application or kernel-mode driver that requires notification when something of interest happens in the virtual miniport. Our use of this might fall a bit outside of its intended use, but it works.   Processing of these requests is handled by the HwProcessServiceRequest function that the driver set in the VIRTUAL_HW_INITIALIZATION_DATA before it called StorPortInitialize in its DriverEntry routine. The code for this function is shown in Figure 2.

VOID OsrHwProcessServiceRequest(IN PVOID PDevExt,IN PVOID PIrp)
{
        PIRP pIrp = (PIRP) PIrp;
        PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(pIrp);
        NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
        POSR_DEVICE_EXTENSION pDevExt = (POSR_DEVICE_EXTENSION) PDevExt;

        OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,
                                ("OsrHwProcessServiceRequest Enter\n"));

        if(irpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
                __try {
                        status = OsrUserProcessIoCtl(pDevExt->PUserGlobalInformation,pIrp);
                } __except(EXCEPTION_EXECUTE_HANDLER) {
            status = GetExceptionCode();
        }
    }

    if(status != STATUS_PENDING) {
        pIrp->IoStatus.Status = status;
        IoCompleteRequest(pIrp,IO_NO_INCREMENT);
    }

    OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,
                ("OsrHwProcessServiceRequest Exit\n"));
}

 

VOID OsrHwProcessServiceRequest(IN PVOID PDevExt,IN PVOID PIrp)
{
        PIRP                                pIrp = (PIRP) PIrp;
        PIO_STACK_LOCATION        irpSp = IoGetCurrentIrpStackLocation(pIrp);
        NTSTATUS                        status = STATUS_INVALID_DEVICE_REQUEST;
        POSR_DEVICE_EXTENSION        pDevExt = (POSR_DEVICE_EXTENSION) PDevExt;
 
        OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,
                                ("OsrHwProcessServiceRequest Enter\n"));
 
        if(irpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
                __try {
                        status = OsrUserProcessIoCtl(pDevExt->PUserGlobalInformation,pIrp);
                } __except(EXCEPTION_EXECUTE_HANDLER) {
            status = GetExceptionCode();
        }
    }
 
    if(status != STATUS_PENDING) {
        pIrp->IoStatus.Status = status;
        IoCompleteRequest(pIrp,IO_NO_INCREMENT);
    }
 
    OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,
                ("OsrHwProcessServiceRequest Exit\n"));
}
VOID OsrHwProcessServiceRequest(IN PVOID PDevExt,IN PVOID PIrp)
{
        PIRP                                pIrp = (PIRP) PIrp;
        PIO_STACK_LOCATION        irpSp = IoGetCurrentIrpStackLocation(pIrp);
        NTSTATUS                        status = STATUS_INVALID_DEVICE_REQUEST;
        POSR_DEVICE_EXTENSION        pDevExt = (POSR_DEVICE_EXTENSION) PDevExt;
 
        OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,
                                ("OsrHwProcessServiceRequest Enter\n"));
 
        if(irpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
                __try {
                        status = OsrUserProcessIoCtl(pDevExt->PUserGlobalInformation,pIrp);
                } __except(EXCEPTION_EXECUTE_HANDLER) {
            status = GetExceptionCode();
        }
    }
 
    if(status != STATUS_PENDING) {
        pIrp->IoStatus.Status = status;
        IoCompleteRequest(pIrp,IO_NO_INCREMENT);
    }
 
    OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,
                ("OsrHwProcessServiceRequest Exit\n"));
}
VOID OsrHwProcessServiceRequest(IN PVOID PDevExt,IN PVOID PIrp)
{
        PIRP                                pIrp = (PIRP) PIrp;
        PIO_STACK_LOCATION        irpSp = IoGetCurrentIrpStackLocation(pIrp);
        NTSTATUS                        status = STATUS_INVALID_DEVICE_REQUEST;
        POSR_DEVICE_EXTENSION        pDevExt = (POSR_DEVICE_EXTENSION) PDevExt;
 
        OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,
                                ("OsrHwProcessServiceRequest Enter\n"));
 
        if(irpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
                __try {
                        status = OsrUserProcessIoCtl(pDevExt->PUserGlobalInformation,pIrp);
                } __except(EXCEPTION_EXECUTE_HANDLER) {
            status = GetExceptionCode();
        }
    }
 
    if(status != STATUS_PENDING) {
        pIrp->IoStatus.Status = status;
        IoCompleteRequest(pIrp,IO_NO_INCREMENT);
    }
 
    OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,
                ("OsrHwProcessServiceRequest Exit\n"));
}
 

Figure 2- OsrHwProcessServiceRequest Routine

You will notice that the driver ensures that it has received an IRP_MJ_DEVICE_CONTROL request before passing it off to its lower-edge OsrUserProcessIoCtl routine. This routine processes user mode requests that have data in the buffer supplied in the AssociatedIrp.SystemBuffer field of the IRP.   This input buffer contains a command code as well as the parameters needed for the command.   The commands the example driver processes are:

  • IOCTL_OSRVMPORT_SCSIPORT - used to confirm that this is indeed the OSRVMPORT driver.
  • IOCTL_OSRVMPORT_CONNECT - used to connect a new SCSI device.
  • IOCTL_OSRVMPORT_DISCONNECT - used to disconnect an existing SCSI device.
  • IOCTL_OSRVMPORT_GETACTIVELIST - used to enumerate the list of active SCSI devices.

We'll discuss IOCTL_OSRVMPORT_CONNECT and IOCTL_OSRVMPORT_DISCONNECT in this article.  You can learn more about the implementation of the other IOCTLs by reading the code.

IOCTL_OSRVMPORT_CONNECT

This IOCTL is used by a caller to request the Virtual Storport Miniport Driver create a new SCSI device based upon provided input information.  The caller initializes a CONNECT_IN structure and passes it to our driver as the input buffer of the IOCTL_OSRVMPORT_CONNECT IOCTL sent via a Win32 DeviceIoControl request.   The CONNECT_IN structure is defined in Figure 3. The CONNECT_IN structure allows the user to specify the full path name of the file to be used to back the virtual SCSI device that's being created.  It also allows the user to indicate whether or not the device is to be CDROM, and if not, the driver treats the device as a disk. Finally, the user can specify whether or not the media is read-only (the example treats all CDROM devices as read-only by default).

#define MAX_NAME_LENGTH 256

typedef struct _CONNECT_IN {
    COMMAND_IN Command;
    WCHAR PathName[MAX_NAME_LENGTH];
    BOOLEAN ReadOnly;
    BOOLEAN Cdrom;
} CONNECT_IN, *PCONNECT_IN;

 

 

Figure 3- CONNECT_IN Structure

From this input information the driver, creates the CONNECTION_LIST_ENTRY structure, shown in  Figure 4, which is used to represent the connection to the media it is using to back the device.  The code then calls OsrSPCreateScsiDevice, a routine in the Virtual Storport Miniport Processing part of our driver (what we refer to as the upper-edge), to create an OSR_VM_DEVICE structure, shown in Figure 6, which is used to internally represent the device.  Notice that the OSR_VM_DEVICE structure contains a pointer to an INQUIRYDATA block, described in Part II of this series, which is used to describe our device to Storport.   Once these are done, all the driver has to do is announce to Storport that our virtual storage bus has changed by calling the upper-edge routine OsrSPAnnounceArrival, which calls StorportNotification indicating BusChangeDetected.  Sometime after doing this Storport will call back into our driver at the OsrHwStartIo handler with a SRB_FUNCTION_EXECUTE_SCSI request, which will in turn call our OsrVmExecuteScsi routine shown in Figure 5.

 

typedef struct _CONNECTION_LIST_ENTRY {
 
LIST_ENTRY                  ListEntry;
WCHAR                       FileName[MAX_NAME_LENGTH*2];
HANDLE                      FileHandle;
UCHAR                       FileAttributes[sizeof(FILE_ALL_INFORMATION)+MAX_NAME_LENGTH*2];
ULONG                       FileType;
PFILE_OBJECT                FileObject;
struct _USER_INSTANCE_INFO* PIInfo;
ULONG                       BusIndex;
ULONG                       TargetIndex;
ULONG                       LunIndex;
BOOLEAN                     HandleClosed;
BOOLEAN                     Connected;
BOOLEAN                     ContainingMediaRemoved;
BOOLEAN                     UNCConnection;
PVOID                       PnPNotificationEntry;
ULONG                      IdentifierIndex;
BOOLEAN                    Closing;  /* Indicates connection is closing */
CONNECT_IN                 ConnectionInfo;
 
} CONNECTION_LIST_ENTRY, *PCONNECTION_LIST_ENTRY;
 

Figure 4 - CONNECTION_LIST_ENTRY

 

UCHAR OsrVmExecuteScsi(IN POSR_DEVICE_EXTENSION PDevExt,

                                                               IN PSCSI_REQUEST_BLOCK PSrb,
                                                               IN PBOOLEAN PComplete)
{
    POSR_LU_EXTENSION    luExt;
    UCHAR                srbStatus = SRB_STATUS_INVALID_REQUEST;
    NTSTATUS             status;
    PCDB                 pCdb = (PCDB) &PSrb->Cdb;
    POSR_VM_DEVICE       pOsrDevice;
 
   *PComplete = TRUE;
 
    luExt = (POSR_LU_EXTENSION) StorPortGetLogicalUnit(PDevExt,
                                   PSrb->PathId,
                                   PSrb->TargetId,
                                   PSrb->Lun );
 
    if(!luExt) {
          return SRB_STATUS_NO_DEVICE;
    }
 
    pOsrDevice = FindOsrVmDevice(luExt,PDevExt,PSrb->PathId, PSrb->TargetId, PSrb->Lun,FALSE);
 
    if(pOsrDevice && pOsrDevice->PUserLocalInformation) {
           InterlockedIncrement(&pOsrDevice->OutstandingIoCount);
           status = OsrUserHandleSrb(pOsrDevice->PUserLocalInformation,PSrb);
           if(status == STATUS_PENDING) {
               *PComplete = FALSE;
               srbStatus = SRB_STATUS_PENDING;
           } else {
               InterlockedDecrement(&pOsrDevice->OutstandingIoCount);
               srbStatus = PSrb->SrbStatus;
           }
    } else {
           srbStatus = SRB_STATUS_NO_DEVICE;
   }
    return srbStatus;
}
 

Figure 5 - OsrVmExecuteScsi Routine

When OsrVmExecuteScsi is called it will call FindOsrVmDevice, shown in Figure 6.  This routine will find the device, i.e. an OSR_VM_DEVICE structure corresponding to the input PathId, TargetId, and Lun (Which was created when we called OsrSpCreateDevice).   Finding this structure will result in a call to OsrUserHandleSrb to handle the SRB targeted at the device.   We will talk about OsrUserHandleSrb later in this article.

 

 

//
// This represents a device that has been detected on a specific
// bus,target, and lun.  PUserLocalInformation represents the handle
// given to us by the lower layer of our code that implements the
// adapter and scsi devices.
//
typedef struct _OSR_VM_DEVICE {
ULONG          MagicNumber;
LIST_ENTRY     ListEntry;
PVOID          PUserLocalInformation;
ULONG          PathId;
ULONG          TargetId;
ULONG          Lun;
PINQUIRYDATA   PInquiryData;
BOOLEAN        BReadOnlyDevice;
BOOLEAN        Missing;
PVOID          PDevExt;
LONG           OutstandingIoCount;
BOOLEAN        ReportedMissing;
} OSR_VM_DEVICE, *POSR_VM_DEVICE;
 

Figure 6- OSR_VM_DEVICE

IOCTL_OSRVMPORT_DISCONNECT

This IOCTL is used by a caller to request that the Virtual Storport Miniport Driver disconnects an existing SCSI device based upon supplied input information. To do this the caller initializes a CONNECT_IN structure and passes it to the driver as the input buffer of the IOCTL_OSRVMPORT_DISCONNECT IOCTL sent via a Win32 DeviceIoControl request.   The CONNECT_IN structure, previously discussed and shown in Figure 3, is also used for DISCONNECT processing. It allows the user to specify the full path name of the file used to back the SCSI device that is to be disconnected. 

 If the input information is valid, the IOCTL handler will search thru the list of connected devices and return the CONNECT_LIST_ENTRY of the device to be disconnected.   Upon getting this entry, the code calls the OsrSPAnnounceDeparture in the upper-edge routines which will set the Missing field in the OSR_VM_DEVICE structure.  The driver then calls StorportNotification indicating BusChangeDetected.  As described in the previous section, calling the StorportNotification routine will result in Storport sending a request back into the driver at its OsrHwStartIo handler in attempt to identify all devices attached to the drivers virtual bus.   As the driver responds to each call, it will call FindOsrVmDevice, shown inFigure 7, to determine if it has a valid device corresponding to the PathId, TargetId, and Lun being queried.   As you can see below, if the driver finds a match in its list, it looks at the Missing field in the structure.   If this is set to TRUE, then the driver knows that the device is being deleted and will indicate that to Storport.  In addition, it will set the ReportedMissing field which indicates to DeleteDevicesThread that the structure can be removed from the list and deleted because Storport was notified that the device is no longer present.

 

POSR_VM_DEVICE FindOsrVmDevice(IN POSR_LU_EXTENSION LuExt,
                               IN POSR_DEVICE_EXTENSION PDevExt,
                               IN UCHAR PathId,
                               IN UCHAR TargetId,
                               IN UCHAR Lun,
                               IN BOOLEAN ReturnMissing)
{
    KIRQL    lockHandle;
    POSR_VM_DEVICE pDevice = NULL;
 
    OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,(__FUNCTION__": Entered\n"));
 
    OsrAcquireSpinLock(&PDevExt->DeviceListLock,&lockHandle);
 
    for(PLIST_ENTRY pEntry = PDevExt->DeviceList.Flink;
        pEntry != &PDevExt->DeviceList; pEntry = pEntry->Flink) {
 
        pDevice = (POSR_VM_DEVICE) CONTAINING_RECORD(pEntry,OSR_VM_DEVICE,ListEntry);
 
        OSR_VM_DEVICE_VALID(pDevice);
 
        if(pDevice->PathId == PathId && pDevice->TargetId == TargetId &&
            pDevice->Lun == Lun) {
            if(!pDevice->Missing) {
                if(LuExt && !LuExt->OsrVmDevice) {
                    LuExt->OsrVmDevice = pDevice;
                }
            } else if(!ReturnMissing) {
                if(!pDevice->ReportedMissing) {
                    OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRVMINIPT_DEBUG_PNP_INFO,
                        (__FUNCTION__": %p Reported Missing, signaling DeleteDevices Thread\n",
                        pDevice));
                    pDevice->ReportedMissing = TRUE;
                    KeSetEvent(&PDevExt->DeleteDevicesThreadWorkEvent,8,FALSE);
                }
                pDevice = NULL;
            }
            break;
        }
 
        pDevice = NULL;
        
    }
    OsrReleaseSpinLock(&PDevExt->DeviceListLock,lockHandle);
 
    OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRVMINIPT_DEBUG_FUNCTRACE,(__FUNCTION__": Exit\n"));
 
    return pDevice;
}
 

Figure 7- FindOsrVmDevice Routine

So now that we know how a SCSI device is added and removed, the only thing we really need to talk about is how the driver will handle a SRB_FUNCTION_EXECUTE_SCSI request for a device that is connected to the driver's virtual bus.   As mentioned previously, these requests are processed by the driver's lower-edge in OsrUserHandleSrb, which we'll talk about in the next section.

OsrUserHandleSrb

The OsrUserHandleSrb routine is the code in the lower-edge of the driver that performs the operation described by the CDB that's contained in the received SRB.  One issue to be aware of is that commands contained within the CDB come in different sizes.   The size of the CDB is contained within the SRB.  No matter what the size of the CDB is, the first byte of the CDB contains the operation code, and given the operation code, the code can determine how to interpret the rest of the CDB.  Another issue to be aware of is that the operations received depend on the type of device being exported, and the completion statuses that the driver returns must be a SRB_STATUS_XXXXX and be returned in the SrbStatus field of the SRB.

Before continuing, we should note an important limitation of the example code presented.  The example does not handle the necessary SCSI Operation codes for disks with capacities greater than 2.2 TB.  Without this support, the example will handle virtual volumes less than or equal to 2.2TB in size, which we expect will be large enough for just about all purposes.  Support for disks greater than 2.2TB first appeared in Windows (for secondary volumes only) starting in Windows Vista.  For the example to handle a disk larger than 2.2TB it would have to handle at least the 16-byte variants of the Read, Write and Read Capacity commands.   There may be others.  In order to determine which 16-byte CDB commands need to be supported, the reader can examine the Disk and ClassPnP code contained with the Win 7 WDK "SRC\STORAGE\CLASS" directory.

Let's take a look of some of the functions that we support in the driver implementation.   For the functions not discussed here, please read the source code.

SCSIOP_INQUIRY (0x12)

As mentioned previously, a SCSI device is described by an INQUIRYDATA structure, and this structure is retrieved by Storport via a SCSIOP_INQUIRY request.   The example driver code to handle this request is shown in Figure 8, below.  As you can see, the data buffer which is used to return the INQUIRYDATA is obtained from the SRB by calling the upper-edge function OsrSpGetSrbDataAddress. The data that the driver returns in that buffer depends upon the device(s) the driver is exporting, which in the example driver is either a disk or cdrom.  You may notice that the only real difference in the return data is the DeviceType, VendorId, and ProductId:  The drive returns READ_ONLY_DIRECT_ACCESS_DEVICE for cdrom versus DIRECT_ACCESS_DEVICE for disk.

       case SCSIOP_INQUIRY             : {// 0x12

            PCDB                    pCdb = (PCDB) &PSrb->Cdb;
            PUCHAR                  pBuffer = (PUCHAR) OsrSpGetSrbDataAddress(pIInfo->OsrSpLocalHandle,PSrb);
            PINQUIRYDATA            pInquiryData;
 
            if(!pBuffer || PSrb->DataTransferLength < INQUIRYDATABUFFERSIZE) {
                status = STATUS_INSUFFICIENT_RESOURCES;
                PSrb->SrbStatus = SRB_STATUS_ERROR;
                goto completeRequest;
            }
            pInquiryData = (PINQUIRYDATA) pBuffer;
            // Fill in the correct Inquiry Data based on the type of device we are emulating.
            if(pIInfo->StorageType == OsrCdrom) {
                pInquiryData->DeviceType = READ_ONLY_DIRECT_ACCESS_DEVICE;
                pInquiryData->DeviceTypeQualifier = DEVICE_CONNECTED;
                pInquiryData->DeviceTypeModifier = 0;
                pInquiryData->RemovableMedia = TRUE;
                pInquiryData->Versions = 2;             // SCSI-2 support
                pInquiryData->ResponseDataFormat = 2;   // Same as Version?? See SCSI book
                pInquiryData->Wide32Bit = TRUE;         // 32 bit wide transfers
                pInquiryData->Synchronous = TRUE;       // Synchronous commands
                pInquiryData->CommandQueue = FALSE;     // Does not support tagged commands
                pInquiryData->LinkedCommands = FALSE;   // No Linked Commands
                RtlCopyMemory((PUCHAR) &pInquiryData->VendorId[0],OSR_INQUIRY_VENDOR_ID_CDROM,
strlen(OSR_INQUIRY_VENDOR_ID_CDROM));
                RtlCopyMemory((PUCHAR) &pInquiryData->ProductId[0],OSR_INQUIRY_PRODUCT_ID_CDROM,
strlen(OSR_INQUIRY_PRODUCT_ID_CDROM));
                RtlCopyMemory((PUCHAR) &pInquiryData->ProductRevisionLevel[0],OSR_INQUIRY_PRODUCT_REVISION,
                                    strlen(OSR_INQUIRY_PRODUCT_REVISION));
            } else {
                // The media is now either an OSR Disk or a regular disk, 
                // either way we return the same information.
               ASSERT(pIInfo->StorageType == OsrDisk);
                pInquiryData->DeviceType = DIRECT_ACCESS_DEVICE;
                pInquiryData->DeviceTypeQualifier = DEVICE_CONNECTED;
                pInquiryData->DeviceTypeModifier = 0;
                pInquiryData->RemovableMedia = FALSE;
                pInquiryData->Versions = 2;             // SCSI-2 support
                pInquiryData->ResponseDataFormat = 2;   // Same as Version?? See SCSI book
                pInquiryData->Wide32Bit = TRUE;         // 32 bit wide transfers
                pInquiryData->Synchronous = TRUE;       // Synchronous commands
                pInquiryData->CommandQueue = FALSE;     // Does not support tagged commands
                pInquiryData->LinkedCommands = FALSE;   // No Linked Commands
                RtlCopyMemory((PUCHAR) &pInquiryData->VendorId[0],OSR_INQUIRY_VENDOR_ID,
strlen(OSR_INQUIRY_VENDOR_ID));
                RtlCopyMemory((PUCHAR) &pInquiryData->ProductId[0],OSR_INQUIRY_PRODUCT_ID,
strlen(OSR_INQUIRY_PRODUCT_ID));
                RtlCopyMemory((PUCHAR) &pInquiryData->ProductRevisionLevel[0],OSR_INQUIRY_PRODUCT_REVISION,
                                    strlen(OSR_INQUIRY_PRODUCT_REVISION));
            }
            status = STATUS_SUCCESS;
            PSrb->SrbStatus = SRB_STATUS_SUCCESS;
            goto completeRequest;
            }
 

Figure 8- OsrUserHandleSrb SCSIOP_INQUIRY Handling

SCSIOP_MODE_SENSE (0x1A)

The SCSIOP_MODE_SENSE command is used by the Windows class drivers to retrieve more detailed information about a detected device.  Since the device is either a generic disk or generic cdrom, we opted to support and return minimal information. We determined what that information was by looking at the Disk and Cdrom class driver implementations contained within the WDK's "SRC\STORAGE\CLASS" directories.  The driver's processing for this command is shown in Figure 9.   The important points to notice in this function are where the driver returns MODE_DSP_WRITE_PROTECT, which tells the requestor that this media is read-only and the MODE_PAGE_CAPABILITIES handler where the driver returns capabilities information if the device is a cdrom.

            case SCSIOP_MODE_SENSE          : // 0x1A

            {
                // We have received a MODE_SENSE command.  We need to 
                // jury rig something up here....  We're returning
                // the bare MINIMUM (as I know it now) information 
                // required.  If we need more we'll add it here.
                //
                PCDB                    pCdb = (PCDB) &PSrb->Cdb;
                PMODE_PARAMETER_HEADER  pModeHeader;
                PUCHAR                  pBuffer = (PUCHAR) OsrSpGetSrbDataAddress(pIInfo-           >OsrSpLocalHandle,PSrb);
 
                if(!pBuffer) {
                    status = STATUS_INSUFFICIENT_RESOURCES;
                    PSrb->SrbStatus = SRB_STATUS_ERROR;
                    goto completeRequest;
                }
                pModeHeader = (PMODE_PARAMETER_HEADER) pBuffer;
 
                switch(pCdb->MODE_SENSE.PageCode) {
                    case MODE_SENSE_CURRENT_VALUES:
                        {
                        pModeHeader->ModeDataLength = sizeof(MODE_PARAMETER_HEADER) + 
sizeof(MODE_PARAMETER_BLOCK);
                        pModeHeader->MediumType = 0;
                        __try {
                            if(OsrUserIsDeviceReadOnly(pIInfo)) {
                                if(pIInfo->ConnectionInformation->ConnectionInfo.Cdrom) {
                                    pModeHeader->DeviceSpecificParameter = MODE_DSP_WRITE_PROTECT; // readonly device
                                } else {
                                    pModeHeader->DeviceSpecificParameter = MODE_DSP_WRITE_PROTECT; // readonly device
                                }
                            } else {
                                pModeHeader->DeviceSpecificParameter = 0;  // Writeable Device
                            }
                        } __except(EXCEPTION_EXECUTE_HANDLER) {
                            status = GetExceptionCode();
                            PSrb->SrbStatus = SRB_STATUS_ERROR;
                            goto completeRequest;
                        }
                        pModeHeader->BlockDescriptorLength = sizeof(MODE_PARAMETER_BLOCK);
                        PMODE_PARAMETER_BLOCK pModeBlock = (PMODE_PARAMETER_BLOCK) 
(pBuffer + sizeof(MODE_PARAMETER_HEADER));
                        RtlZeroMemory(pModeBlock,sizeof(MODE_PARAMETER_BLOCK));
                        }
                        break;
 
                    case MODE_PAGE_CAPABILITIES: 
                        if(pIInfo->ConnectionInformation->ConnectionInfo.Cdrom) {
                            PCDVD_CAPABILITIES_PAGE pCapBlock = (PCDVD_CAPABILITIES_PAGE) 
(pBuffer +sizeof(MODE_PARAMETER_HEADER10));
                            pModeHeader->ModeDataLength = sizeof(MODE_PARAMETER_HEADER) + 
sizeof(CDVD_CAPABILITIES_PAGE);
                            RtlZeroMemory(pCapBlock,sizeof(CDVD_CAPABILITIES_PAGE));
                            pCapBlock->PageCode = MODE_PAGE_CAPABILITIES;
                            pCapBlock->PageLength = 0x18;
                            pCapBlock->CDRRead = TRUE;
                            pCapBlock->CDERead = TRUE;
                            pCapBlock->Method2 = TRUE;
                            break;
                        }
 
                    default:
                        pModeHeader->ModeDataLength = sizeof(MODE_PARAMETER_HEADER);
                        pModeHeader->MediumType = 0;
                        __try {
                            if(OsrUserIsDeviceReadOnly(pIInfo)) {
                                if(pIInfo->ConnectionInformation->ConnectionInfo.Cdrom) {
                                    pModeHeader->DeviceSpecificParameter =  MODE_DSP_WRITE_PROTECT; // readonly device
                                } else {
                                    pModeHeader->DeviceSpecificParameter = MODE_DSP_WRITE_PROTECT; // readonly device
                                }
                            } else {
                                pModeHeader->DeviceSpecificParameter = 0;  // Writeable Device
                            }
                        } __except(EXCEPTION_EXECUTE_HANDLER) {
                            NTSTATUS status = GetExceptionCode();
                            PSrb->SrbStatus = SRB_STATUS_ERROR;
                            goto completeRequest;
                        }
                        pModeHeader->BlockDescriptorLength = 0;
                        break;
                }
                status = STATUS_SUCCESS;
                PSrb->SrbStatus = SRB_STATUS_SUCCESS;
                goto completeRequest;
            }
 

Figure 9- OsrUserHandleSrb SCSIOP_MODE_SENSE Handling

SCSIOP_READ_CAPACITY (0x25)

This function is used by a caller to determine the capacity of the connected device.   The caller must return the number of blocks on the device and the block size.  As with every other command the information returned depends on the device.   The driver's handling for this function is shown in Figure 10.

         case SCSIOP_READ_CAPACITY       : // 0x25

            {
                ULONG numBlocks;
                ULONG bytesPerBlock;
                //
                // Someone has asked us to read the disk capacity of the device,
                // so here we need to return to the caller the information about
                // the SPECIAL disk we represent.   Sooo here we go.
                //
                PREAD_CAPACITY_DATA pCapacityData = (PREAD_CAPACITY_DATA) PSrb->DataBuffer;
                __try {
                    OsrUserGetDiskCapacity(pIInfo,&numBlocks,&bytesPerBlock);
                } __except(EXCEPTION_EXECUTE_HANDLER) {
                    status = GetExceptionCode();
                    PSrb->SrbStatus = SRB_STATUS_ERROR;
                    goto completeRequest;
                }
                REVERSE_BYTES(&pCapacityData->LogicalBlockAddress,&numBlocks);
                REVERSE_BYTES(&pCapacityData->BytesPerBlock,&bytesPerBlock);
                //
                // Set status in Irp and in the SRB to indicate that the
                // function was successful.
                //
                status = STATUS_SUCCESS;
                PSrb->SrbStatus = SRB_STATUS_SUCCESS;
                goto completeRequest;
            }
 

Figure 10- OsrUserHandleSrb SCSIOP_READ_CAPACITY Handling

SCSIOP_READ (0x28) and SCSIOP_WRITE (0x2A)

The SCSIOP_READ and SCSIOP_WRITE functions, as the names imply, are the functions issued to perform read and write functions on the device, respectively.  The caller specifies the logical block number from which to start the operation in the input CDB  along with the number of blocks to read or write.  The caller also specifies an MDL that describes a buffer used for the read/write data.   It will then be the driver's responsibility to translate the input information into something that makes sense for the device the driver is emulating.  Whether the driver is emulating a disk or CD-ROM, this will entail translating the operation into a read from or a write to the file that the driver is using to back the device. We have shown only the SCSIOP_READ handler in Figure 11 since the write handler is almost exactly the same.  We'll discuss the actual implementation of the read and write code later in this article, but until then there is one point for you to notice:   This function does not complete the SRB, but instead returns STATUS_PENDING, which will cause OsrUserHandleSrb to return SRB_STATUS_PENDING to Storport.  This status indicates to Storport that the command has been accepted by the driver but is not yet complete.  Any other return status would indicate that the command is complete.

 

        case SCSIOP_READ                : // 0x28
            {
            // We have received a read request.   Process the read.
            ULARGE_INTEGER  startingLbn = {0,0};
            ULONG           readLength = 0;
            PCDB            pReadCdb = (PCDB) &PSrb->Cdb[0];
            ULONG           bytesRead;
            ULONG           numBlocks;
            ULONG           bytesPerBlock;
 
            __try {
                OsrUserGetDiskCapacity(pIInfo,&numBlocks,&bytesPerBlock);
                // Convert the starting LBN back to little endian.
                // Convert the LBN to a byte offset instead of a block offset.
                REVERSE_BYTES(&startingLbn.LowPart,&pReadCdb->CDB10.LogicalBlockByte0)
                startingLbn.QuadPart *= bytesPerBlock;
                // Convert the read length back to little endian
                REVERSE_2BYTES(&readLength,&pReadCdb->CDB10.TransferBlocksMsb);
                readLength *= bytesPerBlock;
                // Issue the read to ScsiPortUser.
                PMDL readMdl = OsrSpGetSrbMdl(pIInfo->OsrSpLocalHandle,PSrb);
                if(!readMdl) {
                    status = STATUS_INSUFFICIENT_RESOURCES;
                    PSrb->SrbStatus = SRB_STATUS_ABORTED;
                } else {
                    status = OsrUserReadData(pIInfo,PSrb,readMdl,startingLbn,readLength,&bytesRead);
                }
            } __except(EXCEPTION_EXECUTE_HANDLER) {
                status = GetExceptionCode();
            }
            // Oh, the user did not want to complete the request, but pended it.  We'll
            // honor it and get out of here.  It is up to the user to complete the request
            // later on.
            if(status == STATUS_PENDING) {
                return status;
            }
            }
            goto completeRequest;
 

Figure 11- OsrUserHandleSrb SCSIOP_READ Handling

Doing Real Work

The previous two articles discussed how to integrate the driver with Storport to become a Virtual Storport Miniport Driver and when integrated, the functions that the driver receives, and how the driver processes them.   The current article has so far discussed how to programmatically request the driver to create a SCSI device (IOCTL_OSRVMPORT_CONNECT) and how the driver would respond to Storport requests to identify the device (SCSIOP_INQUIRY and SCSIOP_MODE_SENSE).  In addition, we've talked about the preliminary handling of read and write requests via the SCSIOP_READ and SCSIOP_WRITE operations.  So what we really need to do now is discuss where and how the real work is being done in the driver. 

As we have mentioned many times, one of the issues of working in the storage stack is that many of the functions are called by the Storport driver at IRQL DISPATCH_LEVEL.  We all know that if we are going to interact with the underlying file system containing the files that back the virtual SCSI devices, the driver can't do it at elevated IRQL.  This means that the real work in the driver will need to be done in worker threads, running at IRQL PASSIVE_LEVEL.   When you look at the implementation of the driver, you'll notice that functions like OsrUserWriteData and OsrUserReadData queue work items (Notice that when the driver specified the size of the SRB extension in the initialization code, the driver added the size of an OSR_WORK_ITEM) to a set of worker thread that the driver created on initialization (OsrUserAdapterStarted).

A worker thread in the driver will perform the operation specified in the work item.  Check out the DoWorkThreadStart routine (available in the downloadable code to our driver). It handles work item commands, DO_CREATE, DO_CLOSE, DO_READ, and DO_WRITE.   Let's discuss each work item command.

DO_CREATE

This work item is used to open a file that is going to back a device that the driver is creating.  You'll notice a few things about this handler.  The driver:

  • Uses SeImpersonateClientEx to ensure that it is using the security credentials of the caller when opening the file, since the system thread may not have the same access rights as the requestor.
  • Opens the file using ZwCreateFile for overlapped I/O, so that it can be doing multiple asynchronous operations against the file.
  • Converts the file handle returned by ZwCreateFile to a pointer to the referenced File Object by calling ObReferenceObjectByHandle.   This is done so that the driver can directly build and send Read and Write IRPS to the underlying file system.
  • Calls ZwQueryInformationFile, so that it can get information on the file for commands like SCSIOP_READ_CAPACITY.

Once this command succeeds, the driver can mark the device as connected so that the software knows that the device is usable.

DO_CLOSE

This work item is used to close a file that backed a device that has been disconnected.   Since the driver opened the file with ZwCreateFile, its closes the handle by calling ZwClose and dereferences the File Object that was returned by ObReferenceObjectByHandle.

DO_READ and DO_WRITE

These work items are used to read or write information to or from the file that backs the device.  The code in OsrUserHandleSrb has converted the SCSIOP_READ orSCSIOP_WRITE logical block offset and block count into a file offset and byte count that the DO_READ or DO_WRITE code can use for the operation.  Therefore all the DO_READ or DO_WRITE code has to do is issue a read/write to the underlying file system.   Since the driver has the file object for the file and an MDL for the user data, the driver implements the read, via the DoRead function and the write via the DoWrite function.  These functions build an IRP for the read or write operation, call the underlying file system directly, and wait for the response.  Once the operation completes the driver code can call OsrSpCompleteSrb in the upper-edge of the driver to notify Storport that the SRB has completed.

Wrapping up Processing

As you've seen, working with Storport to create a Virtual Storport Miniport is actually quite easy.   We've covered how to create a device, notify Storport that a device has arrived, process I/Os to the device and make that device go away.

We've also covered, in detail, the process of actually performing I/O operations.  This process is one of receiving SRB/CDB pairs, interpreting them, and then mapping them into some operation for the device the driver is emulating.   Whether the driver is going to map operations to a file or some other target, with a virtual Storport Miniport, the concepts we have discussed will help you on your way.   

So what we have shown through the 3 articles on Writing a Virtual Storport Miniport Driver is that the Storport environment is pretty easy to work in once you know the constraints.

Now let's wrap up this series by talking about installing and building the included sample code.

Installation

Well, I suppose that since we now have discussed how the driver works we had better discuss how to install it.  In the source code that we provide is the INF file used to install our Virtual Storport Miniport Driver.   We're going to assume that you've seen INF files before, so we're not going to explain them.   What we are going to do however is note some important points.

  • The INF file defines its Class as SCSIAdapter which will indicate to the PnP Manager that this INF file is for a SCSI Adapter type of device.
  • Since there is no hardware associated with this driver, the INF file indicates to the PnP Manager that this device is "ROOT" enumerated, in other words, the Pnp Manager must create a PDO for this device.   This detail is defined in the INFs' "Models" section where it specifies %rootstr%, which equates to "ROOT\OsrSVm"
  • The INF file adds this service to the SCSI Miniport LoadOrderGroup

How you actually get Windows to install the software depends on which platform you are on.   For Pre-Windows 7 systems, you can use the Add Hardware Manager from the control panel to perform this installation.   For Windows 7 however, the Add Hardware Manager is missing from the control panel, so in order to install the driver, you must go to the control panel, Select Administrative Tools, then select Computer Management.  When the Computer Management window opens select Device Manager, right click on your computer in the left window pane and then select Add Legacy Hardware.

Building

The sample source code comes with 3 directories which build 2 components.  The directories are:

  • OsrVmSample - which contains the OSR Virtual Storport Miniport Driver software
  • OsrVmSampleMgmt - which contains the Win32 MFC application that manages the Driver
  • OsrVmSampleInc - which contains the include files shared by the Driver and the Management code.

The Driver and the Management Application can all be built with the Windows 7 WDK and have been tested on Vista and Win7.   The included project contains a "dirs" file which will build both components of the software and it also contains the INF file which can be used to install the software.

Summary

In the three articles on Writing a Virtual Storport Miniport Driver we have tried to cover all the important aspects of architecture, design, and implementation of the software.   Hopefully with these articles and the downloaded software example, you will be able to fully understand our discussion.   Included in the sample is the driver source, INF, and the controlling user mode application. 

User Comments
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment

"Fix for disable/uninstall hang"
Once a service IRP has been completed by OsrHwProcessServiceRequest, device manager will hang when you try to disable or uininstall the storage adapter device. You have to reboot and not run the management GUI (OsrVmSampleMgmt.exe) to be able to uninstall or disable the storage adapter. This is because it calls IoCompleteRequest to complete the service IRP. If you change it to StorPortCompleteServiceIrp, it will work.

Rating:
20-Jan-17, Jonathan Ludwig


"Building with Visual Studio 2013"
Great series on VMiniport !! I tried this Visual Studio solution with VS2013 and WDK8.0 and it failed do we have VS20123 solution for this project.

Rating:
21-Aug-14, Summer Jhon


"Build issue!!!!"
Can we build this core with VS2013 and WDK8.1? When I tried the existing solution to build it gave me multiple errors :(

Rating:
20-Aug-14, Summer Jhon


"why the sample can't be downloaded by IE"
when use IE to download the sample, it never ends, hope the problem be fixed.

Rating:
01-Aug-12, yuncheng li


"The driver Hangs when Unistallation"
Hi I found the driver very useful. But I have observed the driver is hanging, when I add a device usong the OSRSampleVMMMgmt.exe.

If I am not creating any disk there is no issue.

Rating:
18-Jul-12, Sony Pappan


"Can't build"
First,thank you for providing such great article

But I've problem when I compile the source code you provided. following is the error message 2>NMAKE : fatal error U1073: don't know how to make 'd:\osrvmm~1\osrvmsamplemgmt \objchk_win7_amd64\amd64\osrvmsamplemgmt.o_manifest'

But osrvmsamplemgmt.o_manifest is not exist in your zip file you provided.

Note: My WDK is the latest version 7600.16385.1,and OS is WIN7-64bit

Rating:
01-Dec-11, Tung Yang Joe


"MODE_SENSE_CURRENT_VALUES isn't a page code"
in your Mode Sense code you do this: switch(pCdb->MODE_SENSE.PageCode) { case MODE_SENSE_CURRENT_VALUES:

Unless i'm missing something MODE_SENSE_CURRENT_VALUES is a possible value of the PageControl(PC) field in the Mode Sense CDB.

Was that intentional?

Rating:
12-Aug-11, Eric Wittmayer


"how to use and modify for USB disk"
hi, 1. I could not compile out the exe, why? some body ask the qeustion, but no one answer, do you could compile good for exe?

2. if i want to use this storport driver for a usb disk(dongle) which replace the windows USBSTOR.sys, what should I do for it? and can it be done?

Rating:
08-Aug-11, jeff ekin


"Great Articles"
Thanks for writing these great articles. I met a problem, when I try to uninstall the driver from device manager. I failed. the uninstall window still running and can't shutdown pc, Could you tell me some ways can let me solve this problen. Thanks. Sorry, my English is not good.

Rating:
07-Aug-11, Chuan-Liang Teng


"issues with building and installing"
great article, but i can't try out the driver because of these issues: using windows 7, I enter "ddkbuild.bat -WIN7 chk ." but while the the driver is built it won't install (using the inf).

also no executable was created so that i could manage the driver.

what am I missing? Thanks

Rating:
19-Jul-11, hjgi ygiuyg


"Thanks, but there is a bug"
Thanks for the great sample you provided. I found there's one bug: IoCompleteRequest() is used in function OsrHwProcessServiceRequest(). You maybe want use StorPortCompleteServiceIrp() instead. Else it will cause a hang if you want disable/remove the driver.

Thanks -grandleaf

Rating:
16-Nov-10, grand leaf


"Build errors"
When I try to build I get some errors. Does anyone know what could be causing this? I selected the x86 Win7 but I'm using an x86 XP system for the build system Below is a past.

W:\Microsoft_DDKs\Virtual_Storeport_Sample_from_NT_Insider>build path contains nonexistant d:\eddy, removing BUILD: Compile and Link for x86 BUILD: Loading w:\microsoft_ddks\wdk\7600.16385.1\build.dat... BUILD: Computing Include file dependencies: BUILD: Start time: Wed Jul 14 15:03:19 2010 BUILD: Examining w:\microsoft_ddks\virtual_storeport_sample_from_nt_insider directory tree for files to compile. BUILD: Saving w:\microsoft_ddks\wdk\7600.16385.1\build.dat... BUILD: Compiling and Linking w:\microsoft_ddks\virtual_storeport_sample_from_nt_insider\osrvmsample directory Configuring OACR for 'root:x86chk' - 1>errors in directory w:\microsoft_ddks\virtual_storeport_sample_from_nt_insider\osrvmsample 1>w:\microsoft_ddks\wdk\7600.16385.1\bin\makefile.new(7117) : error U1087: cannot have : and :: dependents for same target 1>nmake.exe /nologo BUILDMSG=Stop. -i BUILD_PASS=PASS2 LINKONLY=1 NOPASS0=1 MAKEDIR_RELATIVE_TO_BASEDIR= failed - rc = 2

BUILD: Compiling and Linking w:\microsoft_ddks\virtual_storeport_sample_from_nt_insider\osrvmsamplemgmt directory 1>errors in directory w:\microsoft_ddks\virtual_storeport_sample_from_nt_insider\osrvmsamplemgmt 1>w:\microsoft_ddks\wdk\7600.16385.1\bin\makefile.new(7114) : error U1087: cannot have : and :: dependents for same target 1>nmake.exe /nologo BUILDMSG=Stop. -i BUILD_PASS=PASS2 LINKONLY=1 NOPASS0=1 MAKEDIR_RELATIVE_TO_BASEDIR= failed - rc = 2

BUILD: Finish time: Wed Jul 14 15:03:41 2010 BUILD: Done

0 files compiled - 1 Warning - 4 Errors

Rating:
14-Jul-10, Eddy Quicksall


"Great article"
Really GREAT ARTICLE! Thanks. But seems source code attached is not align with the content a little.

Rating:
26-May-10, WANGPING HE


"Update to the article coming"
FYI, we will be providing an update to this article in the next NT INSIDER decribing some changes that were made based on feedback received and analysis of the miniports SCSI operation processing.

Rating:
17-May-10, Mark Cariddi


"Great article"
Thanks for writing these great articles.

I noticed that the sample code does not match the content. The article refers to a file backed virtual disk, whereas the sample code is for memory backed one.

Any plan to release the file backing sample code?

Rating:
16-May-10, Huihong Luo


"Thank You!"
Hey, I just wanted to thank those who wrote this series of articles. Very fun stuff!

Rating:
13-May-10, Nate Bushman


"Feedback"
Over all the article is very informative, but I think there should be a references section added to this article 1. Which describes where to go from here (what next). 2. Provide links to the material that might hve been probably.

Rating:
12-May-10, mohan bisht


"more on typo"
sorry to be a bother

it appears by looking the page source that "'" is being interpreted as a questionmark in various places on the page. My environment is W2k3 sp2 browser is IE8.

thanks Ted

Rating:
07-May-10, ted cooper


"typo"
I know this is picky but (just below fig 2)

"We?ll discuss IOCTL_OSRVMPORT_CONNECT "

s/b

"We'll discuss IOCTL_OSRVMPORT_CONNECT "

unless this was intentional, then I appologize for being anoying :}

Rating:
07-May-10, ted cooper


Post Your Comments.
Print this article.
Email this article.
bottom nav links