WDFREQUESTs are for sharing in KMDF v1.9

In my last post I described why a WDFREQUEST is unique to a particular WDFDEVICE.  There is one particular programming pattern where this is not the behavior you want.  This pattern is when you have each PDO accepting IO requests which it then forwards on to the parent WDFDEVICE for processing. One great in box example of this is usbhub.sys.  Each usbhub PDO receives URBs which are then forwarded to the parent FDO and the FDO is where all IO processing occurs. 

If you want to apply this pattern to a KMDF driver written to a v1.7 or earlier and take advantage of WDFQUEUEs you had to send the requests from the PDO to the FDO with WdfRequestSend so that they were represented to the FDO. The easiest way to do this is to create a WDFIOTARGET for the FDO itself and then have each PDO send IO to that WDFIOTARGET as shown in the following 3 code snippets.

EvtDriverDeviceAdd for the FDO

 NTSTATUS EvtDriverDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit) 
{
   WDFDEVICE device;

   WDF_OBJECT_ATTRIBUTES woa;
   WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&woa, FDO_EXTENSION);

   // ...initialize the DeviceInit...
   status = WdfDeviceCreate(&DeviceInit, &woa, &device);

   if (!NT_SUCCESS(status)) {
      return status;
   }

   PFDO_EXTENSION pFdoExt = GetFdoExt(device);
   status = WdfIoTargetCreate(device, WDF_NO_OBJECT_ATTRIBUTES, &pFdoExt->SelfTarget);
   if (!NT_SUCCESS(status)) {
      return status;
   }

   // open the WDFIOTARGET to point our own PDEVICE_OBJECT
   WDF_IO_TARGET_OPEN_PARAMS openParams;
   WDF_IO_TARGET_OPEN_PARAMS_INIT_EXISTING_DEVICE(openParams, WdfDeviceWdmGetDeviceObject(device));

   status = WdfIoTargetOpen(pFdoExt->SelfTarget, &openParams);
   if (!NT_SUCCESS(status)) {
      return status;
   }
   ...
   return status;
}

EvtChildListCreateDevice for the PDO

 NTSTATUS EvtChildListCreateDevice(
    WDFCHILDLIST ChildList, 
    PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER IdentificationDescription,
    PWDFDEVICE_INIT ChildInit
    )
{
   WDFDEVICE pdo;
   NTSTATUS status;

   WDF_OBJECT_ATTRIBUTES woa;
   WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&woa, PDO_EXTENSION);

   status = WdfDeviceCreate(&ChildInit, &woa, &device);
   if (!NT_SUCCESS(status)) {
      return status;
   }

   PPDO_EXTENSION pPdoExt = GetPdoExt(device);
   PFDO_EXTENSION pFdoExt = GetFdoExt(WdfPdoGetParent(device));

   pPdoExt->ParentTarget = pFdoExt->SelfTarget;
   ...
   return status;
}

EvtIoDefault for the PDO

 typedef
VOID EvtIoDefault(WDFQUEUE Queue, WDFREQUEST Request)
 {
    // ...  extract Request type ...
    if (RequestShouldBeSentToParent()) {
       WdfRequestFormatRequestUsingCurrentType(Request);
       WdfRequestSend(Request, GetPdoExt(WdfIoQueueGetDevice(Queue))->ParentTarget);
    }
}

As you can see, this is a bit cumbersome!  While it works, it is not ideal.  The KMDF team addressed this issue in v1.9 by adding 2 new DDIs that must be used together

  1. WdfPdoInitAllowForwardingRequestToParent which tells KMDF that you will be forwarding IO from a PDO to the parent FDO.  Internally this sets up some bookkeeping and, more importantly, sets the PDO’s PDEVICE_OBJECT’s StackSize to the FDO’s PDEVICE_OBJECT StackSize+1 so that there will be enough stack locations in the underlying PIRP for both the parent and child stacks
  2. WdfRequestForwardToParentDeviceIoQueue which removes the need for the custom WDFIOTARGET and WdfRequestSend and directly presents the PDO’s WDFREQUEST to the FDO’s WDFQUEUE

Let’s now rewrite the 3 code snippets to make use of these new DDIs, new code in red

NEW EvtDriverDeviceAdd for the FDO

 NTSTATUS EvtDriverDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit) 
{
   WDFDEVICE device;

   WDF_OBJECT_ATTRIBUTES woa;
   WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&woa, FDO_EXTENSION);

   // ...initialize the DeviceInit...
   status = WdfDeviceCreate(&DeviceInit, &woa, &device);

   if (!NT_SUCCESS(status)) {
      return status;
   }

   PFDO_EXTENSION pFdoExt = GetFdoExt(device);

   status = WdfIoTargetCreate(device, WDF_NO_OBJECT_ATTRIBUTES, &pFdoExt->SelfTarget);
   if (!NT_SUCCESS(status)) {
      return status;
   }

   // open the WDFIOTARGET to point our own PDEVICE_OBJECT
   WDF_IO_TARGET_OPEN_PARAMS openParams;
   WDF_IO_TARGET_OPEN_PARAMS_INIT_EXISTING_DEVICE(openParams, WdfDeviceWdmGetDeviceObject(device));

   status = WdfIoTargetOpen(pFdoExt->SelfTarget, &openParams);
   if (!NT_SUCCESS(status)) {
      return status;
   }



   // initialize a WDF_IO_QUEUE_CONFIG to your needs
   status = WdfIoQueueCreate(device, ..., &pFdoExt->>ChildProcessingQueue);
   if (!NT_SUCCESS(status)) {
      return status;
   }

   ...
   return status;
}

NEW EvtChildListCreateDevice for the PDO

 NTSTATUS EvtChildListCreateDevice(
    WDFCHILDLIST ChildList, 
    PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER IdentificationDescription,
    PWDFDEVICE_INIT ChildInit
    )
{
   WDFDEVICE pdo;
   NTSTATUS status;

   WDF_OBJECT_ATTRIBUTES woa;
   WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&woa, PDO_EXTENSION);


   WdfPdoInitAllowForwardingRequestToParent(ChildInit);
   

   status = WdfDeviceCreate(&ChildInit, &woa, &device);
   if (!NT_SUCCESS(status)) {
      return status;
   }

   PPDO_EXTENSION pPdoExt = GetPdoExt(device);
   PFDO_EXTENSION pFdoExt = GetFdoExt(WdfPdoGetParent(device));

   pPdoExt->ParentTarget = pFdoExt->SelfTarget;
   ...
   return status;
}

NEW EvtIoDefault for the PDO

 VOID EvtIoDefault(WDFQUEUE Queue, WDFREQUEST Request)
 {
    // ...  extract Request type ...
    if (RequestShouldBeSentToParent()) {

       WdfRequestFormatRequestUsingCurrentType(Request);
       WdfRequestSend(Request, GetPdoExt(WdfIoQueueGetDevice(Queue))->ParentTarget);


       WDF_REQUEST_FORWARD_OPTIONS options;
       WDF_REQUEST_FORWARD_OPTIONS_INIT(&options);
       options.Flags = WDF_REQUEST_FORWARD_OPTIONS_FLAGS;
 
       status = WdfRequestForwardToParentDeviceIoQueue(
           Request, 
           GetFdoExt(WdfPdoGetParent(WdfIoQueueGetDevice(Queue)))->ChildProcessingQueue,
           &options);

    }
}