KMDF driver doesn't receive IRP_MJ_DEVICE_CONTROL from child

Hello,

I’m porting an old driver from WDM to KMDF, but have run into some issues.
The driver I’m porting enumerates a child device, to which a mouse driver will be installed. I don’t want to rewrite the mouse driver as well, I’m trying to leave that as much as it is.

I am enumerating a child device using WdfFdoAddStaticChild, and the device is installed as expected. It can be seen in device manager, and when the mouse driver is installed, it starts as expected. But when the mouse driver tries to send an IRP_MJ_DEVICE_CONTROL using IoBuildDeviceIoControlRequest and then IoCallDriver, IoCallDriver fails with status STATUS_INVALID_DEVICE_REQUEST.
This works fine in the old WDM driver, so I don’t think anything should be wrong in this code, pretty sure the issue is that the KMDF driver isn’t set up right to receive the IRP.

In the KMDF driver i have installed EvtIoDeviceControl and EvtIoDefault handlers, none of which are ever called.

Is there anything else needed to be able to receive these IRP’s from a child device?
I have been using the Toaster example for reference, but cannot find anything that seems related to this there.

Did you install EvtIoDeviceControl and EvtIoDefault on the child or the parent device? You can either process these requests directly in the child device or you can forward the request so that it is processed in the parent device…see WdfPdoInitAllowForwardingRequestToParent , WdfRequestForwardToParentDeviceIoQueue

@Doron_Holan said:
Did you install EvtIoDeviceControl and EvtIoDefault on the child or the parent device? You can either process these requests directly in the child device or you can forward the request so that it is processed in the parent device…see WdfPdoInitAllowForwardingRequestToParent , WdfRequestForwardToParentDeviceIoQueue

I installed it on the parent device.
The idea is to send these IRP’s from the child device, to the parent device.

The functions you are refering to, aren’t they supposed to be used in the device driver for the child device? i.e. the WDM device driver I already have and want to touch as little as possible. And as this is working with the old WDM parent driver I’m porting, I think it should be possible to get it working with the KMDF driver as well.

I now tried installing a queue with EvtIoDeviceControl and EvtIoDefault from the parent driver, after creating the child device, before calling WdfFdoAddStaticChild. But that causes the entire driver to not work properly, so I guess you aren’t supposed to do that…

This is the function I’m using to create the child device:

`NTSTATUS
Bus_CreatePdo(
In WDFDEVICE Device,
In PWSTR HardwareId,
ULONG DeviceType
)
{

PWDFDEVICE_INIT             pDeviceInit = NULL;
NTSTATUS                    status;
DECLARE_UNICODE_STRING_SIZE(deviceId, 100);
UNICODE_STRING              compatId;
WDFDEVICE                   hChild = NULL;
WDF_DEVICE_PNP_CAPABILITIES pnpCaps;
WDF_DEVICE_POWER_CAPABILITIES powerCaps;

PAGED_CODE();

pDeviceInit = WdfPdoInitAllocate(Device);

if (pDeviceInit == NULL) {
    status = STATUS_INSUFFICIENT_RESOURCES;
    return status;
}

//
// Set DeviceType
//
WdfDeviceInitSetDeviceType(pDeviceInit, DeviceType);

RtlUnicodeStringPrintf(&deviceId, L"DosDevices\\%s", HardwareId);

status = WdfPdoInitAssignDeviceID(pDeviceInit, &deviceId);
if (!NT_SUCCESS(status)) {
    goto Cleanup;
}

status = WdfPdoInitAddHardwareID(pDeviceInit, &deviceId);
if (!NT_SUCCESS(status)) {
    goto Cleanup;
}

RtlInitUnicodeString(&compatId, HardwareId);

status = WdfPdoInitAddCompatibleID(pDeviceInit, &compatId);
if (!NT_SUCCESS(status)) {
    goto Cleanup;
}

status = WdfDeviceCreate(&pDeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &hChild);
if (!NT_SUCCESS(status)) {
    WdfDeviceInitFree(pDeviceInit);
    pDeviceInit = NULL;
    goto Cleanup;

}

WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps);
pnpCaps.Removable = WdfFalse;
pnpCaps.EjectSupported = WdfFalse;
pnpCaps.SurpriseRemovalOK = WdfFalse;

pnpCaps.UINumber = 0xFFFFFFFF;

WdfDeviceSetPnpCapabilities(hChild, &pnpCaps);

WDF_DEVICE_POWER_CAPABILITIES_INIT(&powerCaps);

powerCaps.DeviceD1 = WdfFalse;
powerCaps.DeviceD2 = WdfFalse;
powerCaps.WakeFromD0 = WdfFalse;
powerCaps.WakeFromD1 = WdfFalse;
powerCaps.WakeFromD2 = WdfFalse;
powerCaps.WakeFromD3 = WdfFalse;
powerCaps.DeviceWake = PowerDeviceMaximum;

powerCaps.DeviceState[PowerSystemWorking] = PowerDeviceD0;
powerCaps.DeviceState[PowerSystemSleeping1] = PowerDeviceD3;
powerCaps.DeviceState[PowerSystemSleeping2] = PowerDeviceD3;
powerCaps.DeviceState[PowerSystemSleeping3] = PowerDeviceD3;
powerCaps.DeviceState[PowerSystemHibernate] = PowerDeviceD3;
powerCaps.DeviceState[PowerSystemShutdown] = PowerDeviceD3;

WdfDeviceSetPowerCapabilities(hChild, &powerCaps);

status = WdfFdoAddStaticChild(Device, hChild);
if (!NT_SUCCESS(status)) {
    WdfObjectDelete(hChild);
    goto Cleanup;
}


Cleanup:
return status;

}`

Okay so I found some info that adding a queue to the child device should be what I need.
https://www.winvistatips.com/threads/receiving-ioctls-in-a-kmdf-bus-driver.191820/

So I tried again.
Now, after adding a queue with EvtIoDeviceControl and EvtIoDefault handlers for the child device, IoCallDriver in the child driver doesn’t fail but returns STATUS_PENDING. The child device driver then waits for the Irp to finish with KeWaitForSingleObject which never returns.
Neither EvtIoDeviceControl nor EvtIoDefault handlers in the parent driver are ever called.

Windbg says the following about the irp:

3: kd> !irp FFFFD90BE526CEA0
Irp is active with 1 stacks 1 is current (= 0xffffd90be526cf70)
No Mdl: No System Buffer: Thread ffffd90bdee4e040: Irp stack trace.
cmd flg cl Device File Completion-Context

[IRP_MJ_DEVICE_CONTROL(e), N/A(0)]
0 1 ffffd90be1a0cd00 00000000 00000000-00000000 pending
\Driver\tscrport
Args: 00000000 00000010 0x220043 ffffed8ab7110448

Irp Extension present at 0xffffd90be526cfb8:

At least the Irp is successfully created now, but why doesn’t the parent KMDF driver receive it?

  1. Don’t use DosDevices as the bus identifier for your hardware ID. Use a GUID or another unique string
  2. WdfDeviceSetPowerCapabilities on the child is completely unnecessary.
  3. you can run !wdfkd.wdfdevicequeues <<>> to see the state of the child’s queues and if there is a request (the pended one specifically) in one of them.
  4. Post the code that creates the WDFQUEUEs for the child

Request from above are going to arrive at the PDO. I see that you are now getting requests there. What happens next is entirely up to you. You can complete the requests there, you can queue them up, or you can send them down to the parent. If the PDO driver returns STATUS_PENDING, that means your PDO ioctl handler returned without completing the request. If you didn’t queue it, that means it is lost forever.

Note that KMDF will not automatically send the IRP farther down the stack. If you want that, then YOU have to do it.

@Doron_Holan said:

  1. Don’t use DosDevices as the bus identifier for your hardware ID. Use a GUID or another unique string
  2. WdfDeviceSetPowerCapabilities on the child is completely unnecessary.
  3. you can run !wdfkd.wdfdevicequeues <<>> to see the state of the child’s queues and if there is a request (the pended one specifically) in one of them.
  4. Post the code that creates the WDFQUEUEs for the child
  1. The hardware ID is DosDevices\{A-GUID-HERE}, not only DosDevices. This is copied from the old driver I’m porting, but sure I could probably remove the DosDevices part.
  2. Thanks. I just added it since it is in the toaster example, I’ll remove it.
  3. Here is some output from wdfdevicequeues and more:

1: kd> !wdfkd.wdfdevicequeues 00006671a9246378
Treating handle as a KMDF handle!

Dumping queues of WDFDEVICE 0x00006671a9246378

Number of queues: 1

Queue: 1 !wdfqueue 0x00006671a8ce0648
Parallel, Auto, Power-managed, PowerOff, Can accept, Can dispatch, ExecutionLevelDispatch, SynchronizationScopeNone
Number of driver owned requests: 0
Number of waiting requests: 1
!wdfrequest 0x00006671aad7fa88 !irp 0xffff998e5537eba0

EvtIoDefault: (0xfffff80e93899420) TscrPort!TscrDeviceEvtIoDefault
EvtIoDeviceControl: (0xfffff80e93899450) TscrPort!TscrDevice_EvtIoDeviceControl

1: kd> !wdfrequest 0x00006671aad7fa88
Treating handle as a KMDF handle!
!irp 0xffff998e5537eba0

!wdfqueue 0x00006671a8ce0648
State: Pending, Allocated by WDF for incoming IRP
System Buffer 0xfffff50fde3964d8, Length 0x10, !wdfmemory 0x00006671aad7f9b1
1: kd> !irp 0xffff998e5537eba0
Irp is active with 1 stacks 1 is current (= 0xffff998e5537ec70)
No Mdl: No System Buffer: Thread ffff998e56ed5040: Irp stack trace.
cmd flg cl Device File Completion-Context

[IRP_MJ_DEVICE_CONTROL(e), N/A(0)]
0 1 ffff998e56dbab50 00000000 00000000-00000000 pending
\Driver\TscrPort
Args: 00000000 00000010 0x220043 fffff50fde3964d8

So indeed there is a pending IRP, and there are handlers installed. But why aren’t they ever called??

  1. Here is the code for creation of the queue, I added this between WdfDeviceSetPowerCapabilities and WdfDoAddStaticChild in the code previously posted:
    WDFQUEUE queue;
    WDF_IO_QUEUE_CONFIG queueConfig;

    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
        &queueConfig,
        WdfIoQueueDispatchParallel
    );

    queueConfig.EvtIoDeviceControl = TscrDevice_EvtIoDeviceControl;
    queueConfig.EvtIoDefault = TscrDeviceEvtIoDefault;

    __analysis_assume(queueConfig.EvtIoStop != 0);

    status = WdfIoQueueCreate(
        hChild,
        &queueConfig,
        WDF_NO_OBJECT_ATTRIBUTES,
        &queue
    );

    __analysis_assume(queueConfig.EvtIoStop == 0);

TscrDevice_EvtIoDeviceControl and TscrDeviceEvtIoDefault are currently just made up of a KdPrint and WdfRequestComplete. But the text from KdPrint is never printed.

@Tim_Roberts said:
Request from above are going to arrive at the PDO. I see that you are now getting requests there. What happens next is entirely up to you. You can complete the requests there, you can queue them up, or you can send them down to the parent. If the PDO driver returns STATUS_PENDING, that means your PDO ioctl handler returned without completing the request. If you didn’t queue it, that means it is lost forever.

Note that KMDF will not automatically send the IRP farther down the stack. If you want that, then YOU have to do it.

I would very much like to complete or send the IRP down the stack, if I could only get the IRP handlers to run. :#

When is the FDO (loaded on your PDO) sending the IO request? If you look at the wdfqueue state dumped by the debugger extension, it tells you why they irp is not being presented

Queue: 1 !wdfqueue 0x00006671a8ce0648
Parallel, Auto, Power-managed, PowerOff, Can accept, Can dispatch, ExecutionLevelDispatch, SynchronizationScopeNone

which leads me to guess that the FDO is sending the io request in its AddDevice or before the pnp start state transition (the PDO’s queue will power on when the PDO successfully processes the pnp start). Does the queue need to be power managed? Since it is a virtual PDO and it sounds like you are not idling the device and powering down when not in use, make the queue not power managed.

@Doron_Holan said:
which leads me to guess that the FDO is sending the io request in its AddDevice or before the pnp start state transition (the PDO’s queue will power on when the PDO successfully processes the pnp start). Does the queue need to be power managed? Since it is a virtual PDO and it sounds like you are not idling the device and powering down when not in use, make the queue not power managed.

Spot on!
I added queueConfig.PowerManaged = WdfFalse, and now it works. Thank you very much!
There is no need for the queue to be power managed, I just wasn’t aware of the option to make it not power managed.