[updated/completed 16 April 2014]
As we were putting the finishing touches on the material for our new Advanced WDF Seminar, we got to discussing some details of how the EvtIoInCallerContext Event Processing Callback works. Our discussion centered around whether the way the function works is helpful or difficult…. working as expected or surprising in its activity.
Let’s back up a minute and discuss how we got here. As you probably know, by default EvtIo Event Processing Callbacks (such as EvtIoRead,EvtIoWrite, and EvtIoDeviceControl) are called in an arbitrary process and thread context and at an IRQL <= DISPATCH_LEVEL. But if you’re the first driver entered from user-mode (that is, you sit on top of the device branch of which your driver is a part) you just might need to be called in the context of the process and thread that’s issuing the I/O operation. You might need to do this, for example, if you have user-mode virtual addresses to deal with, such as if you’re using Neither I/O or if you’re processing an IOCTL that has a user-mode pointer embedded in a data buffer.
If you want your KMDF driver to be called in the context of the requesting process and thread, you can optionally specify an EvtIoInCallerContext Event Processing Callback. This callback is guaranteed to be called in the context of whatever thread and process is sending you the request. For I/O operations coming directly from user mode, this will be the process and thread that issued the I/O request. This callback is called with a handle to WDFREQUEST describing the I/O operation and a handle to the WDFDEVICE to which the I/O operation is directed.
You also know, I’m sure, that the classic way of receiving I/O Requests in a WDF driver is via a WDF Queue. Requests from WDF Queues are delivered to EvtIo Event Processing Callbacks defined in your driver. By configuring your Queues you can quite easily indicate to the Framework which types of I/O Requests your driver does and does not handle. So, to over-simplify for a minute, if your driver handles read and write operations but not IOCTLs you would specify EvtIo Event Processing Callbacks for read and write, but you would not provide an EvtIo event processing callback for IOCTLs. Assuming your driver is function driver, this tells the Framework to pass along to you any reads and writes that it receives, but — since you don’t support IOCTLs — to automatically complete any IOCTLs it receives back to the requestor with STATUS_INVALID_DEVICE_REQUEST. This is just one way you could configure things, but it’s the most common way. Again, I’m over-simplifying here. Stay with me here, be patient, this is all review.
What you may not know is that if you specify an EvtIoInCallerContext Event Processing Callback, that callback will be called for all I/O types supported by the Framework, whether your driver’s Queues handle that request type or not. That means that if your driver only supplies valid I/O Event Processing Callbacks for read and write, and does not expect to receive any IOCTLs, your EvtIoInCallerContext Event Processing Callback will still be called for IOCTLs that are directed to your driver.
From within your EvtIoInCallerContext event processing callback, you can really only do two things with the Request you receive:
- You can call WdfDeviceEnqueueRequest, which passes the Request back to the Framework and instructs it to continue to process it.
- You can call WdfRequestComplete, which (of course) completes the Request back to the caller.
So, finally… to the discussion we were having here at OSR: Our discussion centered around whether getting Requests in your EvtIoInCallerContext that you don’t handle via a WDFQUEUE was a good thing, or an annoyance.
One thought was that getting these Requests in EvtIoInCallerContext was cool. You could, after all, potentially process the requests there… synchronously… right in-line. Consider for a moment an IOCTL request to retrieve some simple statistic. You could just grab the value from your Device Context, slap it in the user’s data buffer (right within EvtIoInCallerContext), call WdfRequestComplete with a success status, and be done with it. Now, granted… this type of processing is one of the things that EvtIoInCallerContext is designed for — even if this approach isn’t really that well documented. And if you’re going to handle this type of I/O operation entirely within your InCallerContext callback you wouldn’t need or in fact even use a Queue or an EvtIo processing callback. So why would you have it? And if you had to had it but never use it, just to enable you to get I/O operations of a particular type in your EvtIoInCallerContext Event Processing Callback, you can be sure somebody at OSR would be writing about it and saying how stupid this was.
The other point of view was getting what are essentially unexpected (and, to your driver, unsupported) Requests in EvtIoInCallerContext was an annoyance. For goodness sakes, the Framework is building a WDFREQUEST, allocating a Request Context if one is specified, and calling your callback… all for a Request that would have been rejected by your Queues in the first place. Note that if you call WdfDeviceEnqueueRequest for a Request that’s rejected by your Queues, the call will fail with a status of STATUS_INVALID_DEVICE_REQUEST. So, at least you do get a hint. But if you don’t expect a given request type, this is at least a waste of resources, and at worst liable to cause headaches.
One thing we all agreed on was that this behavior was something you needed to know about, understand, and plan for. It could be a Very Bad Thing if you expected the Framework to limit the Requests you receive in this callback according to your Queue configuration. Because that’s not how it works. And if you do process Requests to completion in your EvtIoInCallerContext routine, be sure to document this clearly, probably at the very least in your EvtDriverDeviceAdd event processing callback where you create your Queues. In fact, in the drivers that I write I usually make it a point to document the driver’s Request processing strategy in a comment just prior to calling WDF_IO_QUEUE_CONFIG_INIT. I figure it could save somebody time, effort, and annoyance in the future.
Now you know. If you’re using EvtIoInCallerContext, be sure you check the received request type in each arriving Request. You don’t want an unpleasant surprise in your processing.