Call methods on an object that was allocated in parent driver

I have a USB driver that fetches and sends data to an audio hardware device. I also have a WDM audio driver based on the SimpleAudioSample example.

The USB driver creates a child, and the INF of the audio driver matches on the hardware id of the child. When the child is created I pass a WDF_OBJECT_ATTRIBUTES to allocate some context space. I then allocate an object of type Foo in the context space. When the audio driver is started I retrieve the context space, cast it to the AudioDriverPdoContext struct and then call methods on the object allocated in the USB driver. I also pass object to the USB driver and callback into the audio driver from the USB driver.

The classes that are shared (in the example it’s IBar, Bar and Foo) are built into a separate library, and I link to this library from both the USB and the audio driver. All object are deleted in the same driver that made the allocation.

I wonder if it is ok to share pointers to allocated objectes like this, and call back and fourth between two different drivers using pointer to the objects?

I have looked at using Driver Defined Intefaces, which takes care of versioning and provides a recognisable way of sharing interfaces between drivers, which is of course something good.

class IBar {
public:
  virtual ~IBar() = default;
  virtual void callback() = 0;
};

class Foo {
public:
  void doThings() {
    // ...
  }

  void doOtherThings(IBar* ptr) {
    ptr->callback();
  }
};

class Bar : public IBar {
public:
    void callback() override {
        // ...
  }
};

struct AudioDriverPdoContext {
  Foo* foo = nullptr;
};

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(AudioDriverPdoContext, getAudioDriverPdoContext)

When the child is created:

WDF_OBJECT_ATTRIBUTES pdoAttributes{};
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pdoAttributes, AudioDriverPdoContext);

WDFDEVICE hChild = nullptr;
if (const auto status = WdfDeviceCreate(&DeviceInit, &pdoAttributes, &hChild);
    !NT_SUCCESS(status)) {
  return status;
}

// new is implemented to return non-pagable memory
getAudioDriverPdoContext(hChild)->foo = new Foo();

In the StartDevice function of the WDM audio driver

 auto bar = new Bar();

  if (PDEVICE_OBJECT pdo = nullptr;
      NT_SUCCESS(PcGetPhysicalDeviceObject(DeviceObject, &pdo)) && pdo) {
    if (auto* const audioDriverPdoContext =
          static_cast<AudioDriverPdoContext*>(pdo->DeviceExtension);) {

       audioDriverPdoContext->doThings(); // Is this ok?
       audioDriverPdoContext->doOtherThings(bar); // Is this ok?
    }
  }

There are two issues to consider. One is lifetime. If your audio device is a child of the USB device, then the lifetime issue should be OK – the audio device will be deleted first.

The other issue is KMDF-related. KMDF handles are bound to the DRIVER, not to the device. You are scrambling that relationship here, and it can certainly lead to issues.

I would like to know more about the second thing that you mentioned. Can you give me more details about which handles that are KMDF handles in my example?

Looks like no kmdf handles are shared, but keep it in mind as you evolve this design. Reaching directly into the pdo extension and depending on the layout is fragile and requires you to deploy both drivers together every time. And implicitly assumes both will be serviced correctly and the tightly coupled versions both load (if you have an unload problem with one or torn state across driver packages they won’t) . Far better would be to query the pdo for the object with a query interface request. It could be one function Foo* GetFoo() if you want, but at least you have decoupled acquiring Foo* from where it is stored in the parent and now more loosely coupled the two drivers. Obviously you still have coupling with the class and interface layout, but that is slightly easier to manage.

Essentially, anything that is created with a WdfXxxxCreate call is a handle. They all start with WDF (but not WDF_). That means WDFDRIVER, WDFDEVICE, WDFREQUEST, WDFQUEUE, WDFCOMMONBUFFER, and so on. Those are all handles, not structures.

@Doron_Holan Sounds good. I will look into using a query interface request.

@Tim_Roberts ok, do I understand correctly that in my example I need to careful not to hang on to the handle produced by WdfDeviceCreate because it might look like that device is still alive (since the handle is bound to the driver), but it might the be case that the device has been deleted?

No, WDF handles don’t have that kind of power. The issue is that there are tables that KMDF maintains that are local to the driver. If you pass a KMDF handle to another driver and try to use it, things don’t line up and disaster ensues. It works fine if you have multiple device instances handled by the same driver, but when you cross to another driver, that’s bad.

@Doron_Holan, I looked into how to query the parent with an interface request.

My plan would be to use WdfDeviceAddQueryInterface in the EVT_WDF_DRIVER_DEVICE_ADD of the WDF USB driver.

The audio driver is a WDM driver (based on the SimpleAudioSample, and when this starts I would use WdfFdoQueryForInterface from AddDevice or StartDevice. WdfFdoQueryForInterface takes a WDFDEVICE as first parameter. I guess this needs to be the WDFDEVICE for the USB driver that made the call to WdfDeviceAddQueryInterface? How can I get a handle to this WDFDEVICE. I have seen some functions to get WDM objects from a WDF object, but not the other way around.

I had one idea about passing the reference to the WDFDEVICE of the USB driver in the context space that I created in the first post, but I wonder if something could go wrong then relating to what @Tim_Roberts mentioned about using handles to other drivers.

If the driver initiating the query interface is wdm, you build and send the query interface request the old fashioned way. Allocate an irp, format it as a irp_mj_pnp/irp_mn_query_interface and synchronously send it down the stack

Ah, ok thanks. I found this which seems to do that. Code:

QueryForInterface(
    _In_ PDEVICE_OBJECT TopOfStack,
    _In_ const GUID* InterfaceType,
    _Out_ PINTERFACE Interface,
    _In_ USHORT Size,
    _In_ USHORT Version,
    _In_opt_ PVOID InterfaceSpecificData
    )
{
    PAGED_CODE();

    PIRP pIrp;
    NTSTATUS status;

    if (TopOfStack == nullptr)
    {
        return STATUS_INVALID_PARAMETER;
    }

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

    if (pIrp != NULL)
    {
        PIO_STACK_LOCATION stack;
        KEVENT event;

        KeInitializeEvent(&event, NotificationEvent, FALSE);

        IoSetCompletionRoutine(pIrp,
            IrpSynchronousCompletion,
            &event,
            TRUE,
            TRUE,
            TRUE);

        pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;

        stack = IoGetNextIrpStackLocation(pIrp);

        stack->MajorFunction = IRP_MJ_PNP;
        stack->MinorFunction = IRP_MN_QUERY_INTERFACE;

        stack->Parameters.QueryInterface.Interface = Interface;
        stack->Parameters.QueryInterface.InterfaceSpecificData = InterfaceSpecificData;
        stack->Parameters.QueryInterface.Size = Size;
        stack->Parameters.QueryInterface.Version = Version;
        stack->Parameters.QueryInterface.InterfaceType = InterfaceType;

        status = IoCallDriver(TopOfStack, pIrp);

        if (status == STATUS_PENDING)
        {
            KeWaitForSingleObject(
                &event,
                Executive,
                KernelMode,
                FALSE, // Not alertable
                NULL
                );

            status = pIrp->IoStatus.Status;
        }

        IoFreeIrp(pIrp);
    }
    else {
        status = STATUS_INSUFFICIENT_RESOURCES;
    }

    return status;
}

In the example I can’t see that anyone calls the QueryForInterface, so I don’t know what to pass as PDEVICE_OBJECT TopOfStack

in other words… How can I send this down the stack? I know this is probably a basic question but I never had to do this before.

I’m guessing PDEVICE_OBJECT TopOfStack should be the FDO of the USB driver that created the child WDMdriver. But the FDO of the parent USB driver is not passed to the PDRIVER_ADD_DEVICE function nor to the PCPFNSTARTDEVICE function of the WDM driver.

That’s what WdfDeviceWdmGetAttachedDevice is for. It gets you the device object of your I/O target – the next driver below you.

TopOfStack is the top of your devices stack, not the parent stack. You can retrieve the top of stack by calling IoGetAttachedDeviceReference. Remember to ObDereference the returned value no matter the result of the query interface request.