Formatting a WDFREQUEST for any IRP_MJ code

In KMDF, the WDFIOTARGET object provides an abstraction for sending I/O to another
PDEVICE_OBJECT and tracking all pending I/O.
The WDFIOTARGET also provides formatting for specific types of I/O: read, write, IOCTL, and internal IOCTL.
The WDFUSBDEVICE and WDFUSBPIPE objects are WDFIOTARGET objects with specific
domain knowledge about USB and know how to format USB specific requests. All of
these formatting functions also maintain a reference count on the WDFMEMORYs that
are used in the format to make sure that the memory remains valid for the lifetime
of the I/O.

But what if you wanted to format a request with a format that KMDF does not current support?

KMDF suports formatting a request with any format. You still get the WDFIOTARGET
functionality of tracking I/O, but you may lose the additional reference count
on any memory used in that I/O. There is a very generic way of specifying your
own format as well as a more specific way which is applicable to internal IOCTLs.

KMDF allows you to specify the format of the WDFREQUEST with the
WdfRequestWdmFormatUsingStackLocation() DDI. You format an
IO_STACK_LOCATION and KMDF will copy the contents
from your buffer into the WDFREQUEST's next stack location. This looks very much
like WDM code except that you are not retrieving the next stack location by calling
IoGetNextIrpStackLocation, instead you are letting
KMDF set it. Since the
IO_STACK_LOCATION structure has no concept of a WDFMEMORY,
KMDF cannot determine which fields in the stack location buffer correspond to
a WDFMEMORY object, so no reference counts are taken. You are responsible for
making sure that the buffer provided as a part of the format remains valid for the
lifetime of the I/O.
Here is a code snippet on how to format a WDFREQUEST for a
IRP_MJ_PNP query capabilities and sending it down the stack:

 
    NTSTATUS GetCaps(WDFDEVICE Device)
    {
        WDFREQUEST request;
        WDFIOTARGET target;
        WDF_REQUEST_SEND_OPTIONS options;
        WDF_REQUEST_REUSE_PARAMS reuse;
        IO_STACK_LOCATION stack;
        DEVICE_CAPABILITIES caps;
        NTSTATUS status;

        target = WdfDeviceGetIoTarget(Device);

        status = WdfRequestCreate(
            WDF_NO_OBJECT_ATTRIBUTES, target, &request);
        if (!NT_SUCCESS(status)) {
            return status;
        }

        // All pnp IRPs must be initialized with STATUS_NOT_SUPPORTED
        WDF_REQUEST_REUSE_PARAMS_INIT(
            &reuse, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_NOT_SUPPORTED);
        WdfRequestReuse(request, &reuse);

        // Initialize device capabilities according to the DDK docs
        RtlZeroMemory(&caps, sizeof(DEVICE_CAPABILITIES));
        caps.Size = sizeof(DEVICE_CAPABILITIES);
        caps.Version  =  1;
        caps.Address  = (ULONG) -1;
        caps.UINumber = (ULONG) -1;

        RtlZeroMemory(&stack, sizeof(stack));
        stack.MajorFunction = IRP_MJ_PNP;
        stack.MinorFunction = IRP_MN_QUERY_CAPABILITIES;
        stack.Parameters.DeviceCapabilities.Capabilities = ∩︀

        WdfRequestWdmFormatUsingStackLocation(request, &stack); 

        WDF_REQUEST_SEND_OPTIONS_INIT(&options, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS);

        WdfRequestSend(request, target, &options);
        status = WdfRequestGetStatus(request);

        WdfObjectDelete(request);

        return status;
    }

This example is a little more complex then I would like it to be, but I wanted to
use a real world example. The example is sending synchronous I/O and the pattern
for sending asychronous I/O is about the same. Note that KMDF does not make any copies of the buffers
embedded in the provided IO_STACK_LOCATION, so do not provide embed a buffer that
is a local variable on the stack for a WDFREQUEST that will be completed asynchronously!
If you have a WDFREQUEST from an EvtIoXxx dispatch routine, you can use that
in your format call (assuming that you have made sure previously that your device's StackSize
is appropriately large enough to fwd I/O to another device) instead of allocating your
own WDFREQUEST.

One interesting subset of formatting your own I/O is if you are on a protocol stack that
uses request blocks to do its work. Instances of request blocks are URB (USB), SRB (SCSI, disk),
IRB (1394). Let's call a generic request block an XRB. XRBs are usually sent to
the bus driver using an interrnal IOCTL. In this case you can use
WdfIoTargetFormatRequestForInternalIoctlOthers() to
format the request and then send it. This is better then calling
WdfRequestWdmFormatUsingStackLocation in this
particular case because WdfIoTargetFormatRequestForInternalIoctlOthers()
will maintain the refcount on the WDFMEMORY for you, freeing you of that burden.

 
    NTSTATUS SendXrb(WDFDEVICE Device, WDFREQUEST Request)
    {
        WDF_OBJECT_ATTRIBUTES woa;
        NTSTATUS status;
        WDFMEMORY memory;
        PXRB pXrb;

        WDF_OBJECT_ATTRIBUTES_INIT(&woa);
        woa.ParentObject = Request;

        status = WdfMemoryCreate(WDF_NO_OBJECT_ATTRIBUTES,
                                 NonPagedPool,
                                 0,
                                 sizeof(XRB),
                                 &memory,
                                 (PVOID*) &pXrb);
        if (!NT_SUCCESS(status)) {
            return status;
        }

        [...Format the XRB...]

        // Parameters.Others.Argument3 is used by the IOCTL value
        status = WdfIoTargetFormatRequestForInternalIoctlOthers(
            WdfDeviceGetIoTarget(Device),
            Request,
            IOCTL_SUBMIT_XRB,
            memory, NULL,        // Parameters.Others.Argument1
            WDF_NO_HANDLE, NULL, // Parameters.Others.Argument2
            WDF_NO_HANDLE, NULL   // Parameters.Others.Argument4
            );

        if (NT_SUCCESS(status)) {
            WdfRequestSetCompletionRoutine(Request, XrbCompletionRoutine, NULL);

            // send async
            if (WdfRequestSend(Request,
                               WdfDeviceGetIoTarget(Device),
                               WDF_NO_SEND_OPTIONS) == FALSE) {
                // send failed
                WdfObjectDelete(memory);
                status = WdfRequestGetStatus(Request);
            }
            else {
                status = STATUS_PENDING;
            }
        }

        if (!NT_SUCCESS(status)) {
            WdfObjectDelete(memory);
        }

        return status;
    }